diff --git a/changelog.d/6735.bugfix b/changelog.d/6735.bugfix new file mode 100644 index 0000000000..814bf3f47c --- /dev/null +++ b/changelog.d/6735.bugfix @@ -0,0 +1 @@ +Fixes crash when entering non ascii characters during account creation diff --git a/changelog.d/6768.bugfix b/changelog.d/6768.bugfix new file mode 100644 index 0000000000..764386132b --- /dev/null +++ b/changelog.d/6768.bugfix @@ -0,0 +1 @@ +Fix crash when biometric key is used when coming back to foreground and KeyStore reports that the device is still locked. diff --git a/changelog.d/6769.bugfix b/changelog.d/6769.bugfix new file mode 100644 index 0000000000..5d65bff449 --- /dev/null +++ b/changelog.d/6769.bugfix @@ -0,0 +1 @@ +Catch all exceptions on lockscreen system key migrations. diff --git a/dependencies_groups.gradle b/dependencies_groups.gradle index f60a77a92d..d5972ed846 100644 --- a/dependencies_groups.gradle +++ b/dependencies_groups.gradle @@ -74,6 +74,7 @@ ext.groups = [ 'com.github.javaparser', 'com.github.piasy', 'com.github.shyiko.klob', + 'com.github.rubensousa', 'com.google', 'com.google.android', 'com.google.api.grpc', diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/failure/Extensions.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/failure/Extensions.kt index 6e198fb98c..68b931b33c 100644 --- a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/failure/Extensions.kt +++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/failure/Extensions.kt @@ -62,7 +62,10 @@ fun Throwable.isUsernameInUse() = this is Failure.ServerError && error.code == MatrixError.M_USER_IN_USE fun Throwable.isInvalidUsername() = this is Failure.ServerError && - error.code == MatrixError.M_INVALID_USERNAME + (error.code == MatrixError.M_INVALID_USERNAME || usernameContainsNonAsciiCharacters()) + +private fun Failure.ServerError.usernameContainsNonAsciiCharacters() = error.code == MatrixError.M_UNKNOWN && + error.message == "Query parameter \'username\' must be ascii" fun Throwable.isInvalidPassword() = this is Failure.ServerError && error.code == MatrixError.M_FORBIDDEN && diff --git a/tools/release/pushPlayStoreMetaData.sh b/tools/release/pushPlayStoreMetaData.sh index 2d8fd9b36a..5c2f69cb7b 100755 --- a/tools/release/pushPlayStoreMetaData.sh +++ b/tools/release/pushPlayStoreMetaData.sh @@ -28,6 +28,7 @@ mv ./fastlane/metadata/android/fy ./fastlane_tmp mv ./fastlane/metadata/android/ga ./fastlane_tmp mv ./fastlane/metadata/android/kab ./fastlane_tmp mv ./fastlane/metadata/android/nb ./fastlane_tmp +mv ./fastlane/metadata/android/gl ./fastlane_tmp # Fastlane / PlayStore require longDescription and shortDescription file to be set, so copy the default # one for languages where they are missing diff --git a/vector/build.gradle b/vector/build.gradle index 0edaf5424e..e5bd835a8f 100644 --- a/vector/build.gradle +++ b/vector/build.gradle @@ -427,6 +427,9 @@ dependencies { implementation libs.airbnb.epoxyPaging implementation libs.airbnb.mavericks + // Snap Helper https://github.com/rubensousa/GravitySnapHelper + implementation 'com.github.rubensousa:gravitysnaphelper:2.2.2' + // Nightly // API-only library gplayImplementation libs.google.appdistributionApi diff --git a/vector/src/androidTest/java/im/vector/app/features/pin/lockscreen/biometrics/BiometricHelperTests.kt b/vector/src/androidTest/java/im/vector/app/features/pin/lockscreen/biometrics/BiometricHelperTests.kt index 53c154ae30..30520dd44f 100644 --- a/vector/src/androidTest/java/im/vector/app/features/pin/lockscreen/biometrics/BiometricHelperTests.kt +++ b/vector/src/androidTest/java/im/vector/app/features/pin/lockscreen/biometrics/BiometricHelperTests.kt @@ -31,7 +31,6 @@ import androidx.test.filters.SdkSuppress import androidx.test.platform.app.InstrumentationRegistry import im.vector.app.TestBuildVersionSdkIntProvider import im.vector.app.features.pin.lockscreen.configuration.LockScreenConfiguration -import im.vector.app.features.pin.lockscreen.configuration.LockScreenConfiguratorProvider import im.vector.app.features.pin.lockscreen.configuration.LockScreenMode import im.vector.app.features.pin.lockscreen.crypto.LockScreenCryptoConstants import im.vector.app.features.pin.lockscreen.crypto.LockScreenKeyRepository @@ -40,6 +39,7 @@ import im.vector.app.features.pin.lockscreen.ui.fallbackprompt.FallbackBiometric import im.vector.app.features.pin.lockscreen.utils.DevicePromptCheck import io.mockk.clearAllMocks import io.mockk.every +import io.mockk.justRun import io.mockk.mockk import io.mockk.mockkObject import io.mockk.mockkStatic @@ -54,8 +54,10 @@ import kotlinx.coroutines.flow.flowOf import kotlinx.coroutines.flow.receiveAsFlow import kotlinx.coroutines.launch import kotlinx.coroutines.test.runTest +import org.amshove.kluent.coInvoking import org.amshove.kluent.shouldBeFalse import org.amshove.kluent.shouldBeTrue +import org.amshove.kluent.shouldThrow import org.junit.Before import org.junit.Ignore import org.junit.Test @@ -239,36 +241,35 @@ class BiometricHelperTests { @Test @SdkSuppress(minSdkVersion = Build.VERSION_CODES.R) // Due to some issues with mockk and CryptoObject initialization - fun authenticateCreatesSystemKeyIfNeededOnSuccessOnAndroidM() = runTest { + fun enableAuthenticationDeletesSystemKeyOnFailure() = runTest { buildVersionSdkIntProvider.value = Build.VERSION_CODES.M - every { lockScreenKeyRepository.isSystemKeyValid() } returns true val mockAuthChannel = Channel(capacity = 1) val biometricUtils = spyk(createBiometricHelper(createDefaultConfiguration(isBiometricsEnabled = true))) { every { createAuthChannel() } returns mockAuthChannel every { authenticateWithPromptInternal(any(), any(), any()) } returns mockk() } + justRun { lockScreenKeyRepository.deleteSystemKey() } val latch = CountDownLatch(1) val intent = Intent(InstrumentationRegistry.getInstrumentation().targetContext, LockScreenTestActivity::class.java) ActivityScenario.launch(intent).onActivity { activity -> activity.lifecycleScope.launch { + val exception = IllegalStateException("Some error") launch { - mockAuthChannel.send(true) - mockAuthChannel.close() + mockAuthChannel.close(exception) } - biometricUtils.authenticate(activity).collect() + coInvoking { biometricUtils.enableAuthentication(activity).collect() } shouldThrow exception latch.countDown() } } latch.await(1, TimeUnit.SECONDS) - verify { lockScreenKeyRepository.ensureSystemKey() } + verify { lockScreenKeyRepository.deleteSystemKey() } } private fun createBiometricHelper(configuration: LockScreenConfiguration): BiometricHelper { val context = InstrumentationRegistry.getInstrumentation().targetContext - val configProvider = LockScreenConfiguratorProvider(configuration) - return BiometricHelper(context, lockScreenKeyRepository, configProvider, biometricManager, buildVersionSdkIntProvider) + return BiometricHelper(configuration, context, lockScreenKeyRepository, biometricManager, buildVersionSdkIntProvider) } private fun createDefaultConfiguration( diff --git a/vector/src/androidTest/java/im/vector/app/features/pin/lockscreen/crypto/LockScreenKeyRepositoryTests.kt b/vector/src/androidTest/java/im/vector/app/features/pin/lockscreen/crypto/LockScreenKeyRepositoryTests.kt index 924dbfee9e..8d14ca9153 100644 --- a/vector/src/androidTest/java/im/vector/app/features/pin/lockscreen/crypto/LockScreenKeyRepositoryTests.kt +++ b/vector/src/androidTest/java/im/vector/app/features/pin/lockscreen/crypto/LockScreenKeyRepositoryTests.kt @@ -17,8 +17,6 @@ package im.vector.app.features.pin.lockscreen.crypto import androidx.test.platform.app.InstrumentationRegistry -import im.vector.app.features.pin.lockscreen.crypto.migrations.LegacyPinCodeMigrator -import im.vector.app.features.settings.VectorPreferences import io.mockk.clearAllMocks import io.mockk.every import io.mockk.mockk @@ -44,8 +42,6 @@ class LockScreenKeyRepositoryTests { } private lateinit var lockScreenKeyRepository: LockScreenKeyRepository - private val legacyPinCodeMigrator: LegacyPinCodeMigrator = mockk(relaxed = true) - private val vectorPreferences: VectorPreferences = mockk(relaxed = true) private val keyStore: KeyStore by lazy { KeyStore.getInstance(LockScreenCryptoConstants.ANDROID_KEY_STORE).also { it.load(null) } diff --git a/vector/src/main/java/im/vector/app/VectorApplication.kt b/vector/src/main/java/im/vector/app/VectorApplication.kt index b1bd0fc308..ca7f1b6c8e 100644 --- a/vector/src/main/java/im/vector/app/VectorApplication.kt +++ b/vector/src/main/java/im/vector/app/VectorApplication.kt @@ -25,17 +25,21 @@ import android.content.res.Configuration import android.os.Handler import android.os.HandlerThread import android.os.StrictMode +import android.view.Gravity import androidx.core.provider.FontRequest import androidx.core.provider.FontsContractCompat import androidx.lifecycle.DefaultLifecycleObserver import androidx.lifecycle.LifecycleOwner import androidx.lifecycle.ProcessLifecycleOwner import androidx.multidex.MultiDex +import androidx.recyclerview.widget.SnapHelper +import com.airbnb.epoxy.Carousel import com.airbnb.epoxy.EpoxyAsyncUtil import com.airbnb.epoxy.EpoxyController import com.airbnb.mvrx.Mavericks import com.facebook.stetho.Stetho import com.gabrielittner.threetenbp.LazyThreeTen +import com.github.rubensousa.gravitysnaphelper.GravitySnapHelper import com.mapbox.mapboxsdk.Mapbox import com.vanniktech.emoji.EmojiManager import com.vanniktech.emoji.google.GoogleEmojiProvider @@ -141,8 +145,9 @@ class VectorApplication : logInfo() LazyThreeTen.init(this) Mavericks.initialize(debugMode = false) - EpoxyController.defaultDiffingHandler = EpoxyAsyncUtil.getAsyncBackgroundHandler() - EpoxyController.defaultModelBuildingHandler = EpoxyAsyncUtil.getAsyncBackgroundHandler() + + configureEpoxy() + registerActivityLifecycleCallbacks(VectorActivityLifecycleCallbacks(popupAlertManager)) val fontRequest = FontRequest( "com.google.android.gms.fonts", @@ -198,6 +203,16 @@ class VectorApplication : Mapbox.getInstance(this) } + private fun configureEpoxy() { + EpoxyController.defaultDiffingHandler = EpoxyAsyncUtil.getAsyncBackgroundHandler() + EpoxyController.defaultModelBuildingHandler = EpoxyAsyncUtil.getAsyncBackgroundHandler() + Carousel.setDefaultGlobalSnapHelperFactory(object : Carousel.SnapHelperFactory() { + override fun buildSnapHelper(context: Context?): SnapHelper { + return GravitySnapHelper(Gravity.START) + } + }) + } + private fun enableStrictModeIfNeeded() { if (Config.ENABLE_STRICT_MODE_LOGS) { StrictMode.setThreadPolicy( diff --git a/vector/src/main/java/im/vector/app/features/home/room/list/home/HomeRoomListFragment.kt b/vector/src/main/java/im/vector/app/features/home/room/list/home/HomeRoomListFragment.kt index d0ef6fc0c2..b29e151350 100644 --- a/vector/src/main/java/im/vector/app/features/home/room/list/home/HomeRoomListFragment.kt +++ b/vector/src/main/java/im/vector/app/features/home/room/list/home/HomeRoomListFragment.kt @@ -31,6 +31,7 @@ import com.airbnb.mvrx.withState import com.google.android.material.dialog.MaterialAlertDialogBuilder import im.vector.app.R import im.vector.app.core.epoxy.LayoutManagerStateRestorer +import im.vector.app.core.extensions.cleanup import im.vector.app.core.platform.StateView import im.vector.app.core.platform.VectorBaseFragment import im.vector.app.core.resources.UserPreferencesProvider @@ -47,6 +48,7 @@ import im.vector.app.features.home.room.list.actions.RoomListQuickActionsSharedA import im.vector.app.features.home.room.list.actions.RoomListSharedAction import im.vector.app.features.home.room.list.actions.RoomListSharedActionViewModel import im.vector.app.features.spaces.SpaceListBottomSheet +import im.vector.app.features.home.room.list.home.recent.RecentRoomCarouselController import kotlinx.coroutines.flow.launchIn import kotlinx.coroutines.flow.onEach import org.matrix.android.sdk.api.session.room.model.RoomSummary @@ -57,7 +59,8 @@ import javax.inject.Inject class HomeRoomListFragment @Inject constructor( private val roomSummaryItemFactory: RoomSummaryItemFactory, - private val userPreferencesProvider: UserPreferencesProvider + private val userPreferencesProvider: UserPreferencesProvider, + private val recentRoomCarouselController: RecentRoomCarouselController ) : VectorBaseFragment(), RoomListListener { @@ -237,6 +240,12 @@ class HomeRoomListFragment @Inject constructor( } }.adapter } + is HomeRoomSection.RecentRoomsData -> recentRoomCarouselController.also { controller -> + controller.listener = this + data.list.observe(viewLifecycleOwner) { list -> + controller.submitList(list) + } + }.adapter } } @@ -249,6 +258,12 @@ class HomeRoomListFragment @Inject constructor( ) } + override fun onDestroyView() { + views.roomListView.cleanup() + recentRoomCarouselController.listener = null + super.onDestroyView() + } + // region RoomListListener override fun onRoomClicked(room: RoomSummary) { diff --git a/vector/src/main/java/im/vector/app/features/home/room/list/home/HomeRoomListViewModel.kt b/vector/src/main/java/im/vector/app/features/home/room/list/home/HomeRoomListViewModel.kt index b95ec50ab0..479e22497f 100644 --- a/vector/src/main/java/im/vector/app/features/home/room/list/home/HomeRoomListViewModel.kt +++ b/vector/src/main/java/im/vector/app/features/home/room/list/home/HomeRoomListViewModel.kt @@ -37,6 +37,7 @@ import kotlinx.coroutines.flow.onEach import kotlinx.coroutines.flow.onStart import kotlinx.coroutines.launch import org.matrix.android.sdk.api.extensions.orFalse +import org.matrix.android.sdk.api.query.QueryStringValue import org.matrix.android.sdk.api.query.SpaceFilter import org.matrix.android.sdk.api.query.toActiveSpaceOrNoFilter import org.matrix.android.sdk.api.query.toActiveSpaceOrOrphanRooms @@ -45,6 +46,7 @@ import org.matrix.android.sdk.api.session.getRoom import org.matrix.android.sdk.api.session.room.RoomSummaryQueryParams import org.matrix.android.sdk.api.session.room.model.Membership import org.matrix.android.sdk.api.session.room.model.tag.RoomTag +import org.matrix.android.sdk.api.session.room.roomSummaryQueryParams import org.matrix.android.sdk.api.session.room.state.isPublic class HomeRoomListViewModel @AssistedInject constructor( @@ -78,6 +80,7 @@ class HomeRoomListViewModel @AssistedInject constructor( private fun configureSections() { val newSections = mutableSetOf() + newSections.add(getRecentRoomsSection()) newSections.add(getAllRoomsSection()) viewModelScope.launch { @@ -89,6 +92,18 @@ class HomeRoomListViewModel @AssistedInject constructor( } } + private fun getRecentRoomsSection(): HomeRoomSection { + val liveList = session.roomService() + .getBreadcrumbsLive(roomSummaryQueryParams { + displayName = QueryStringValue.NoCondition + memberships = listOf(Membership.JOIN) + }) + + return HomeRoomSection.RecentRoomsData( + list = liveList + ) + } + private fun getAllRoomsSection(): HomeRoomSection.RoomSummaryData { val builder = RoomSummaryQueryParams.Builder().also { it.memberships = listOf(Membership.JOIN) diff --git a/vector/src/main/java/im/vector/app/features/home/room/list/home/HomeRoomSection.kt b/vector/src/main/java/im/vector/app/features/home/room/list/home/HomeRoomSection.kt index 7bfd0a769e..14c76b08bf 100644 --- a/vector/src/main/java/im/vector/app/features/home/room/list/home/HomeRoomSection.kt +++ b/vector/src/main/java/im/vector/app/features/home/room/list/home/HomeRoomSection.kt @@ -24,4 +24,8 @@ sealed class HomeRoomSection { data class RoomSummaryData( val list: LiveData> ) : HomeRoomSection() + + data class RecentRoomsData( + val list: LiveData> + ) : HomeRoomSection() } diff --git a/vector/src/main/java/im/vector/app/features/home/room/list/home/recent/RecentRoomCarouselController.kt b/vector/src/main/java/im/vector/app/features/home/room/list/home/recent/RecentRoomCarouselController.kt new file mode 100644 index 0000000000..53832bbc74 --- /dev/null +++ b/vector/src/main/java/im/vector/app/features/home/room/list/home/recent/RecentRoomCarouselController.kt @@ -0,0 +1,86 @@ +/* + * Copyright (c) 2022 New Vector Ltd + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package im.vector.app.features.home.room.list.home.recent + +import android.content.res.Resources +import android.util.TypedValue +import com.airbnb.epoxy.Carousel +import com.airbnb.epoxy.CarouselModelBuilder +import com.airbnb.epoxy.EpoxyController +import com.airbnb.epoxy.EpoxyModel +import com.airbnb.epoxy.carousel +import im.vector.app.features.home.AvatarRenderer +import im.vector.app.features.home.room.list.RoomListListener +import org.matrix.android.sdk.api.session.room.model.RoomSummary +import org.matrix.android.sdk.api.util.toMatrixItem +import javax.inject.Inject + +class RecentRoomCarouselController @Inject constructor( + private val avatarRenderer: AvatarRenderer, + private val resources: Resources, +) : EpoxyController() { + + private var data: List? = null + var listener: RoomListListener? = null + + private val hPadding = TypedValue.applyDimension( + TypedValue.COMPLEX_UNIT_DIP, + 16f, + resources.displayMetrics + ).toInt() + + private val itemSpacing = TypedValue.applyDimension( + TypedValue.COMPLEX_UNIT_DIP, + 24f, + resources.displayMetrics + ).toInt() + + fun submitList(recentList: List) { + this.data = recentList + requestModelBuild() + } + + override fun buildModels() { + val host = this + data?.let { data -> + carousel { + id("recents_carousel") + padding(Carousel.Padding(host.hPadding, host.itemSpacing)) + withModelsFrom(data) { roomSummary -> + val onClick = host.listener?.let { it::onRoomClicked } + val onLongClick = host.listener?.let { it::onRoomLongClicked } + + RecentRoomItem_() + .id(roomSummary.roomId) + .avatarRenderer(host.avatarRenderer) + .matrixItem(roomSummary.toMatrixItem()) + .unreadNotificationCount(roomSummary.notificationCount) + .showHighlighted(roomSummary.highlightCount > 0) + .itemLongClickListener { _ -> onLongClick?.invoke(roomSummary) ?: false } + .itemClickListener { onClick?.invoke(roomSummary) } + } + } + } + } +} + +private inline fun CarouselModelBuilder.withModelsFrom( + items: List, + modelBuilder: (T) -> EpoxyModel<*> +) { + models(items.map { modelBuilder(it) }) +} diff --git a/vector/src/main/java/im/vector/app/features/home/room/list/home/recent/RecentRoomItem.kt b/vector/src/main/java/im/vector/app/features/home/room/list/home/recent/RecentRoomItem.kt new file mode 100644 index 0000000000..6a575a2b6a --- /dev/null +++ b/vector/src/main/java/im/vector/app/features/home/room/list/home/recent/RecentRoomItem.kt @@ -0,0 +1,78 @@ +/* + * Copyright (c) 2022 New Vector Ltd + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package im.vector.app.features.home.room.list.home.recent + +import android.view.HapticFeedbackConstants +import android.view.View +import android.view.ViewGroup +import android.widget.ImageView +import android.widget.TextView +import com.airbnb.epoxy.EpoxyAttribute +import com.airbnb.epoxy.EpoxyModelClass +import im.vector.app.R +import im.vector.app.core.epoxy.ClickListener +import im.vector.app.core.epoxy.VectorEpoxyHolder +import im.vector.app.core.epoxy.VectorEpoxyModel +import im.vector.app.core.epoxy.onClick +import im.vector.app.features.displayname.getBestName +import im.vector.app.features.home.AvatarRenderer +import im.vector.app.features.home.room.list.UnreadCounterBadgeView +import org.matrix.android.sdk.api.util.MatrixItem + +@EpoxyModelClass +abstract class RecentRoomItem : VectorEpoxyModel(R.layout.item_recent_room) { + + @EpoxyAttribute lateinit var avatarRenderer: AvatarRenderer + @EpoxyAttribute lateinit var matrixItem: MatrixItem + @EpoxyAttribute var unreadNotificationCount: Int = 0 + @EpoxyAttribute var showHighlighted: Boolean = false + + @EpoxyAttribute(EpoxyAttribute.Option.DoNotHash) + var itemLongClickListener: View.OnLongClickListener? = null + + @EpoxyAttribute(EpoxyAttribute.Option.DoNotHash) + var itemClickListener: ClickListener? = null + + override fun bind(holder: Holder) { + super.bind(holder) + + holder.rootView.onClick(itemClickListener) + holder.rootView.setOnLongClickListener { + it.performHapticFeedback(HapticFeedbackConstants.LONG_PRESS) + itemLongClickListener?.onLongClick(it) ?: false + } + + avatarRenderer.render(matrixItem, holder.avatarImageView) + holder.avatarImageView.contentDescription = matrixItem.getBestName() + holder.unreadCounterBadgeView.render(UnreadCounterBadgeView.State(unreadNotificationCount, showHighlighted)) + holder.title.text = matrixItem.getBestName() + } + + override fun unbind(holder: Holder) { + holder.rootView.setOnClickListener(null) + holder.rootView.setOnLongClickListener(null) + avatarRenderer.clear(holder.avatarImageView) + super.unbind(holder) + } + + class Holder : VectorEpoxyHolder() { + val unreadCounterBadgeView by bind(R.id.recentUnreadCounterBadgeView) + val avatarImageView by bind(R.id.recentImageView) + val title by bind(R.id.recentTitle) + val rootView by bind(R.id.recentRoot) + } +} diff --git a/vector/src/main/java/im/vector/app/features/onboarding/OnboardingViewModel.kt b/vector/src/main/java/im/vector/app/features/onboarding/OnboardingViewModel.kt index 8136dc379b..73288bd6d5 100644 --- a/vector/src/main/java/im/vector/app/features/onboarding/OnboardingViewModel.kt +++ b/vector/src/main/java/im/vector/app/features/onboarding/OnboardingViewModel.kt @@ -193,25 +193,32 @@ class OnboardingViewModel @AssistedInject constructor( } private suspend fun checkUserNameAvailability(userName: String) { - when (val result = registrationWizard.registrationAvailable(userName)) { - RegistrationAvailability.Available -> { - setState { - copy( - registrationState = RegistrationState( - isUserNameAvailable = true, - selectedMatrixId = when { - userName.isMatrixId() -> userName - else -> "@$userName:${selectedHomeserver.userFacingUrl.toReducedUrl()}" - }, - ) - ) - } - } + runCatching { registrationWizard.registrationAvailable(userName) }.fold( + onSuccess = { result -> + when (result) { + RegistrationAvailability.Available -> { + setState { + copy( + registrationState = RegistrationState( + isUserNameAvailable = true, + selectedMatrixId = when { + userName.isMatrixId() -> userName + else -> "@$userName:${selectedHomeserver.userFacingUrl.toReducedUrl()}" + }, + ) + ) + } + } - is RegistrationAvailability.NotAvailable -> { - _viewEvents.post(OnboardingViewEvents.Failure(result.failure)) - } - } + is RegistrationAvailability.NotAvailable -> { + _viewEvents.post(OnboardingViewEvents.Failure(result.failure)) + } + } + }, + onFailure = { + _viewEvents.post(OnboardingViewEvents.Failure(it)) + } + ) } private fun withAction(action: OnboardingAction, block: (OnboardingAction) -> Unit) { diff --git a/vector/src/main/java/im/vector/app/features/pin/PinFragment.kt b/vector/src/main/java/im/vector/app/features/pin/PinFragment.kt index 69548f24f0..1688452167 100644 --- a/vector/src/main/java/im/vector/app/features/pin/PinFragment.kt +++ b/vector/src/main/java/im/vector/app/features/pin/PinFragment.kt @@ -24,6 +24,7 @@ import android.view.View import android.view.ViewGroup import android.widget.Toast import com.airbnb.mvrx.args +import com.airbnb.mvrx.asMavericksArgs import com.google.android.material.dialog.MaterialAlertDialogBuilder import im.vector.app.R import im.vector.app.core.extensions.replaceFragment @@ -33,7 +34,7 @@ import im.vector.app.databinding.FragmentPinBinding import im.vector.app.features.MainActivity import im.vector.app.features.MainActivityArgs import im.vector.app.features.pin.lockscreen.biometrics.BiometricAuthError -import im.vector.app.features.pin.lockscreen.configuration.LockScreenConfiguratorProvider +import im.vector.app.features.pin.lockscreen.configuration.LockScreenConfiguration import im.vector.app.features.pin.lockscreen.configuration.LockScreenMode import im.vector.app.features.pin.lockscreen.ui.AuthMethod import im.vector.app.features.pin.lockscreen.ui.LockScreenFragment @@ -51,7 +52,7 @@ data class PinArgs( class PinFragment @Inject constructor( private val pinCodeStore: PinCodeStore, private val vectorPreferences: VectorPreferences, - private val configuratorProvider: LockScreenConfiguratorProvider, + private val defaultConfiguration: LockScreenConfiguration, ) : VectorBaseFragment() { private val fragmentArgs: PinArgs by args() @@ -81,21 +82,17 @@ class PinFragment @Inject constructor( vectorBaseActivity.finish() } } - - configuratorProvider.updateDefaultConfiguration { - copy( - mode = LockScreenMode.CREATE, - title = getString(R.string.create_pin_title), - needsNewCodeValidation = true, - newCodeConfirmationTitle = getString(R.string.create_pin_confirm_title), - ) - } + createFragment.arguments = defaultConfiguration.copy( + mode = LockScreenMode.CREATE, + title = getString(R.string.create_pin_title), + needsNewCodeValidation = true, + newCodeConfirmationTitle = getString(R.string.create_pin_confirm_title), + ).asMavericksArgs() replaceFragment(R.id.pinFragmentContainer, createFragment) } private fun showAuthFragment() { val authFragment = LockScreenFragment() - val canUseBiometrics = vectorPreferences.useBiometricsToUnlock() authFragment.onLeftButtonClickedListener = View.OnClickListener { displayForgotPinWarningDialog() } authFragment.lockScreenListener = object : LockScreenListener { override fun onAuthenticationFailure(authMethod: AuthMethod) { @@ -133,18 +130,12 @@ class PinFragment @Inject constructor( .show() } } - configuratorProvider.updateDefaultConfiguration { - copy( - mode = LockScreenMode.VERIFY, - title = getString(R.string.auth_pin_title), - isStrongBiometricsEnabled = isStrongBiometricsEnabled && canUseBiometrics, - isWeakBiometricsEnabled = isWeakBiometricsEnabled && canUseBiometrics, - isDeviceCredentialUnlockEnabled = isDeviceCredentialUnlockEnabled && canUseBiometrics, - autoStartBiometric = canUseBiometrics, - leftButtonTitle = getString(R.string.auth_pin_forgot), - clearCodeOnError = true, - ) - } + authFragment.arguments = defaultConfiguration.copy( + mode = LockScreenMode.VERIFY, + title = getString(R.string.auth_pin_title), + leftButtonTitle = getString(R.string.auth_pin_forgot), + clearCodeOnError = true, + ).asMavericksArgs() replaceFragment(R.id.pinFragmentContainer, authFragment) } diff --git a/vector/src/main/java/im/vector/app/features/pin/lockscreen/biometrics/BiometricHelper.kt b/vector/src/main/java/im/vector/app/features/pin/lockscreen/biometrics/BiometricHelper.kt index ae4fa637b4..9bcf6e4264 100644 --- a/vector/src/main/java/im/vector/app/features/pin/lockscreen/biometrics/BiometricHelper.kt +++ b/vector/src/main/java/im/vector/app/features/pin/lockscreen/biometrics/BiometricHelper.kt @@ -31,10 +31,12 @@ import androidx.biometric.BiometricPrompt import androidx.core.content.ContextCompat import androidx.fragment.app.Fragment import androidx.fragment.app.FragmentActivity +import dagger.assisted.Assisted +import dagger.assisted.AssistedFactory +import dagger.assisted.AssistedInject import dagger.hilt.android.qualifiers.ApplicationContext import im.vector.app.R import im.vector.app.features.pin.lockscreen.configuration.LockScreenConfiguration -import im.vector.app.features.pin.lockscreen.configuration.LockScreenConfiguratorProvider import im.vector.app.features.pin.lockscreen.crypto.LockScreenKeyRepository import im.vector.app.features.pin.lockscreen.ui.fallbackprompt.FallbackBiometricDialogFragment import im.vector.app.features.pin.lockscreen.utils.DevicePromptCheck @@ -54,22 +56,24 @@ import kotlinx.coroutines.launch import org.matrix.android.sdk.api.util.BuildVersionSdkIntProvider import java.security.KeyStore import javax.crypto.Cipher -import javax.inject.Inject import kotlin.coroutines.CoroutineContext /** * This is a helper to manage system authentication (biometric and other types) and the system key. */ -class BiometricHelper @Inject constructor( +class BiometricHelper @AssistedInject constructor( + @Assisted private val configuration: LockScreenConfiguration, @ApplicationContext private val context: Context, private val lockScreenKeyRepository: LockScreenKeyRepository, - private val configurationProvider: LockScreenConfiguratorProvider, private val biometricManager: BiometricManager, private val buildVersionSdkIntProvider: BuildVersionSdkIntProvider, ) { private var prompt: BiometricPrompt? = null - private val configuration: LockScreenConfiguration get() = configurationProvider.currentConfiguration + @AssistedFactory + interface BiometricHelperFactory { + fun create(configuration: LockScreenConfiguration): BiometricHelper + } /** * Returns true if a weak biometric method (i.e.: some face or iris unlock implementations) can be used. @@ -174,16 +178,18 @@ class BiometricHelper @Inject constructor( when (val exception = result.exceptionOrNull()) { null -> result.getOrNull()?.let { emit(it) } else -> { - // Exception found, stop collecting, throw it and remove the prompt reference + // Exception found: + // 1. Stop collecting. + // 2. Remove the system key if we were creating it. + // 3. Throw the exception and remove the prompt reference + if (!checkSystemKeyExists) { + lockScreenKeyRepository.deleteSystemKey() + } prompt = null throw exception } } } - // Generates the system key on successful authentication - if (buildVersionSdkIntProvider.get() >= Build.VERSION_CODES.M) { - lockScreenKeyRepository.ensureSystemKey() - } // Channel is closed, remove prompt reference prompt = null } @@ -213,11 +219,11 @@ class BiometricHelper @Inject constructor( .setAllowedAuthenticators(authenticators) .build() - return BiometricPrompt(activity, executor, callback).also { + return BiometricPrompt(activity, executor, callback).also { prompt -> showFallbackFragmentIfNeeded(activity, channel.receiveAsFlow(), executor.asCoroutineDispatcher()) { // For some reason this seems to be needed unless we want to receive a fragment transaction exception delay(1L) - it.authenticate(promptInfo, cryptoObject) + prompt.authenticate(promptInfo, cryptoObject) } } } @@ -253,11 +259,9 @@ class BiometricHelper @Inject constructor( ): BiometricPrompt.AuthenticationCallback = object : BiometricPrompt.AuthenticationCallback() { private val scope = CoroutineScope(coroutineContext) override fun onAuthenticationError(errorCode: Int, errString: CharSequence) { - scope.launch { - // Error is a terminal event, should close both the Channel and the CoroutineScope to free resources. - channel.close(BiometricAuthError(errorCode, errString.toString())) - scope.cancel() - } + // Error is a terminal event, should close both the Channel and the CoroutineScope to free resources. + channel.close(BiometricAuthError(errorCode, errString.toString())) + scope.cancel() } override fun onAuthenticationFailed() { @@ -274,10 +278,8 @@ class BiometricHelper @Inject constructor( scope.cancel() } } else { - scope.launch { - channel.close(IllegalStateException("System key was not valid after authentication.")) - scope.cancel() - } + channel.close(IllegalStateException("System key was not valid after authentication.")) + scope.cancel() } } diff --git a/vector/src/main/java/im/vector/app/features/pin/lockscreen/configuration/LockScreenConfiguration.kt b/vector/src/main/java/im/vector/app/features/pin/lockscreen/configuration/LockScreenConfiguration.kt index 8f3e67dfe5..12846c254c 100644 --- a/vector/src/main/java/im/vector/app/features/pin/lockscreen/configuration/LockScreenConfiguration.kt +++ b/vector/src/main/java/im/vector/app/features/pin/lockscreen/configuration/LockScreenConfiguration.kt @@ -16,9 +16,13 @@ package im.vector.app.features.pin.lockscreen.configuration +import android.os.Parcelable +import kotlinx.parcelize.Parcelize + /** * Configuration to be used by the lockscreen feature. */ +@Parcelize data class LockScreenConfiguration( /** Which mode should the UI display, [LockScreenMode.VERIFY] or [LockScreenMode.CREATE]. */ val mode: LockScreenMode, @@ -56,4 +60,4 @@ data class LockScreenConfiguration( val biometricSubtitle: String? = null, /** Text for the cancel button of the Biometric prompt dialog. Optional. */ val biometricCancelButtonTitle: String? = null, -) +) : Parcelable diff --git a/vector/src/main/java/im/vector/app/features/pin/lockscreen/configuration/LockScreenConfiguratorProvider.kt b/vector/src/main/java/im/vector/app/features/pin/lockscreen/configuration/LockScreenConfiguratorProvider.kt deleted file mode 100644 index 338ac66125..0000000000 --- a/vector/src/main/java/im/vector/app/features/pin/lockscreen/configuration/LockScreenConfiguratorProvider.kt +++ /dev/null @@ -1,58 +0,0 @@ -/* - * Copyright (c) 2022 New Vector Ltd - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -package im.vector.app.features.pin.lockscreen.configuration - -import kotlinx.coroutines.flow.Flow -import kotlinx.coroutines.flow.MutableStateFlow -import javax.inject.Inject -import javax.inject.Singleton - -/** - * Class used to hold both the [defaultConfiguration] and an updated version in [currentConfiguration]. - */ -@Singleton -class LockScreenConfiguratorProvider @Inject constructor( - /** Default [LockScreenConfiguration], any derived configuration created using [updateDefaultConfiguration] will use this as a base. */ - val defaultConfiguration: LockScreenConfiguration, -) { - - private val mutableConfigurationFlow = MutableStateFlow(defaultConfiguration) - - /** - * A [Flow] that emits any changes in configuration. - */ - val configurationFlow: Flow = mutableConfigurationFlow - - /** - * The current configuration to be read and used. - */ - val currentConfiguration get() = mutableConfigurationFlow.value - - /** - * Applies the changes in [block] to the [defaultConfiguration] to generate a new [currentConfiguration]. - */ - fun updateDefaultConfiguration(block: LockScreenConfiguration.() -> LockScreenConfiguration) { - mutableConfigurationFlow.value = defaultConfiguration.block() - } - - /** - * Resets the [currentConfiguration] to the [defaultConfiguration]. - */ - fun reset() { - mutableConfigurationFlow.value = defaultConfiguration - } -} diff --git a/vector/src/main/java/im/vector/app/features/pin/lockscreen/crypto/KeyStoreCrypto.kt b/vector/src/main/java/im/vector/app/features/pin/lockscreen/crypto/KeyStoreCrypto.kt index d37c11ed69..a42ce3a9b7 100644 --- a/vector/src/main/java/im/vector/app/features/pin/lockscreen/crypto/KeyStoreCrypto.kt +++ b/vector/src/main/java/im/vector/app/features/pin/lockscreen/crypto/KeyStoreCrypto.kt @@ -20,13 +20,13 @@ import android.annotation.SuppressLint import android.content.Context import android.os.Build import android.security.keystore.KeyPermanentlyInvalidatedException -import android.security.keystore.UserNotAuthenticatedException import android.util.Base64 import androidx.annotation.VisibleForTesting import androidx.biometric.BiometricPrompt import dagger.assisted.Assisted import dagger.assisted.AssistedFactory import dagger.assisted.AssistedInject +import org.matrix.android.sdk.api.extensions.tryOrNull import org.matrix.android.sdk.api.securestorage.SecretStoringUtils import org.matrix.android.sdk.api.util.BuildVersionSdkIntProvider import java.security.Key @@ -113,14 +113,8 @@ class KeyStoreCrypto @AssistedInject constructor( fun hasValidKey(): Boolean { val keyExists = hasKey() return if (buildVersionSdkIntProvider.get() >= Build.VERSION_CODES.M && keyExists) { - try { - ensureKey() - true - } catch (e: KeyPermanentlyInvalidatedException) { - false - } catch (e: UserNotAuthenticatedException) { - false - } + val initializedKey = tryOrNull("Error validating lockscreen system key.") { ensureKey() } + initializedKey != null } else { keyExists } diff --git a/vector/src/main/java/im/vector/app/features/pin/lockscreen/crypto/LockScreenKeysMigrator.kt b/vector/src/main/java/im/vector/app/features/pin/lockscreen/crypto/LockScreenKeysMigrator.kt index 68acfcebf3..bb55ceb1b7 100644 --- a/vector/src/main/java/im/vector/app/features/pin/lockscreen/crypto/LockScreenKeysMigrator.kt +++ b/vector/src/main/java/im/vector/app/features/pin/lockscreen/crypto/LockScreenKeysMigrator.kt @@ -40,7 +40,7 @@ class LockScreenKeysMigrator @Inject constructor( suspend fun migrateIfNeeded() { if (legacyPinCodeMigrator.isMigrationNeeded()) { legacyPinCodeMigrator.migrate() - missingSystemKeyMigrator.migrate() + missingSystemKeyMigrator.migrateIfNeeded() } if (systemKeyV1Migrator.isMigrationNeeded() && versionProvider.get() >= Build.VERSION_CODES.M) { diff --git a/vector/src/main/java/im/vector/app/features/pin/lockscreen/crypto/migrations/MissingSystemKeyMigrator.kt b/vector/src/main/java/im/vector/app/features/pin/lockscreen/crypto/migrations/MissingSystemKeyMigrator.kt index 75a68c66b7..4c33c14954 100644 --- a/vector/src/main/java/im/vector/app/features/pin/lockscreen/crypto/migrations/MissingSystemKeyMigrator.kt +++ b/vector/src/main/java/im/vector/app/features/pin/lockscreen/crypto/migrations/MissingSystemKeyMigrator.kt @@ -18,8 +18,6 @@ package im.vector.app.features.pin.lockscreen.crypto.migrations import android.annotation.SuppressLint import android.os.Build -import android.security.keystore.KeyPermanentlyInvalidatedException -import android.security.keystore.UserNotAuthenticatedException import im.vector.app.features.pin.lockscreen.crypto.KeyStoreCrypto import im.vector.app.features.pin.lockscreen.di.BiometricKeyAlias import im.vector.app.features.settings.VectorPreferences @@ -41,14 +39,15 @@ class MissingSystemKeyMigrator @Inject constructor( * If user had biometric auth enabled, ensure system key exists, creating one if needed. */ @SuppressLint("NewApi") - fun migrate() { + fun migrateIfNeeded() { if (buildVersionSdkIntProvider.get() >= Build.VERSION_CODES.M && vectorPreferences.useBiometricsToUnlock()) { - try { - keystoreCryptoFactory.provide(systemKeyAlias, true).ensureKey() - } catch (e: KeyPermanentlyInvalidatedException) { - Timber.e("Could not automatically create biometric key because it was invalidated.", e) - } catch (e: UserNotAuthenticatedException) { - Timber.e("Could not automatically create biometric key because there are no enrolled biometric authenticators.", e) + val systemKeyStoreCrypto = keystoreCryptoFactory.provide(systemKeyAlias, true) + runCatching { + systemKeyStoreCrypto.ensureKey() + }.onFailure { e -> + Timber.e(e, "Could not automatically create biometric key. Biometric authentication will be disabled.") + systemKeyStoreCrypto.deleteKey() + vectorPreferences.setUseBiometricToUnlock(false) } } } diff --git a/vector/src/main/java/im/vector/app/features/pin/lockscreen/crypto/migrations/SystemKeyV1Migrator.kt b/vector/src/main/java/im/vector/app/features/pin/lockscreen/crypto/migrations/SystemKeyV1Migrator.kt index 10c7505f27..748001af8b 100644 --- a/vector/src/main/java/im/vector/app/features/pin/lockscreen/crypto/migrations/SystemKeyV1Migrator.kt +++ b/vector/src/main/java/im/vector/app/features/pin/lockscreen/crypto/migrations/SystemKeyV1Migrator.kt @@ -17,10 +17,10 @@ package im.vector.app.features.pin.lockscreen.crypto.migrations import android.os.Build -import android.security.keystore.UserNotAuthenticatedException import androidx.annotation.RequiresApi import im.vector.app.features.pin.lockscreen.crypto.KeyStoreCrypto import im.vector.app.features.pin.lockscreen.di.BiometricKeyAlias +import im.vector.app.features.settings.VectorPreferences import timber.log.Timber import java.security.KeyStore import javax.inject.Inject @@ -32,6 +32,7 @@ class SystemKeyV1Migrator @Inject constructor( @BiometricKeyAlias private val systemKeyAlias: String, private val keyStore: KeyStore, private val keystoreCryptoFactory: KeyStoreCrypto.Factory, + private val vectorPreferences: VectorPreferences, ) { /** @@ -41,10 +42,12 @@ class SystemKeyV1Migrator @Inject constructor( fun migrate() { keyStore.deleteEntry(SYSTEM_KEY_ALIAS_V1) val systemKeyStoreCrypto = keystoreCryptoFactory.provide(systemKeyAlias, keyNeedsUserAuthentication = true) - try { + runCatching { systemKeyStoreCrypto.ensureKey() - } catch (e: UserNotAuthenticatedException) { - Timber.e("Could not migrate v1 biometric key because there are no enrolled biometric authenticators.", e) + }.onFailure { e -> + Timber.e(e, "Could not migrate v1 biometric key. Biometric authentication will be disabled.") + systemKeyStoreCrypto.deleteKey() + vectorPreferences.setUseBiometricToUnlock(false) } } diff --git a/vector/src/main/java/im/vector/app/features/pin/lockscreen/di/LockScreenModule.kt b/vector/src/main/java/im/vector/app/features/pin/lockscreen/di/LockScreenModule.kt index fb333b96bb..811a66f3af 100644 --- a/vector/src/main/java/im/vector/app/features/pin/lockscreen/di/LockScreenModule.kt +++ b/vector/src/main/java/im/vector/app/features/pin/lockscreen/di/LockScreenModule.kt @@ -16,8 +16,10 @@ package im.vector.app.features.pin.lockscreen.di +import android.app.KeyguardManager import android.content.Context import androidx.biometric.BiometricManager +import androidx.core.content.getSystemService import dagger.Binds import dagger.Module import dagger.Provides @@ -83,6 +85,9 @@ object LockScreenModule { SecretStoringUtils(context, keyStore, buildVersionSdkIntProvider), buildVersionSdkIntProvider, ) + + @Provides + fun provideKeyguardManager(context: Context): KeyguardManager = context.getSystemService()!! } @Module diff --git a/vector/src/main/java/im/vector/app/features/pin/lockscreen/ui/LockScreenAction.kt b/vector/src/main/java/im/vector/app/features/pin/lockscreen/ui/LockScreenAction.kt index 7f8d08dddd..729cf24aa5 100644 --- a/vector/src/main/java/im/vector/app/features/pin/lockscreen/ui/LockScreenAction.kt +++ b/vector/src/main/java/im/vector/app/features/pin/lockscreen/ui/LockScreenAction.kt @@ -22,4 +22,5 @@ import im.vector.app.core.platform.VectorViewModelAction sealed class LockScreenAction : VectorViewModelAction { data class PinCodeEntered(val value: String) : LockScreenAction() data class ShowBiometricPrompt(val callingActivity: FragmentActivity) : LockScreenAction() + object OnUIReady : LockScreenAction() } diff --git a/vector/src/main/java/im/vector/app/features/pin/lockscreen/ui/LockScreenFragment.kt b/vector/src/main/java/im/vector/app/features/pin/lockscreen/ui/LockScreenFragment.kt index 9eea61ac82..a7a228a105 100644 --- a/vector/src/main/java/im/vector/app/features/pin/lockscreen/ui/LockScreenFragment.kt +++ b/vector/src/main/java/im/vector/app/features/pin/lockscreen/ui/LockScreenFragment.kt @@ -23,7 +23,6 @@ import android.view.ViewGroup import android.view.animation.AnimationUtils import android.widget.TextView import androidx.core.view.isVisible -import androidx.lifecycle.lifecycleScope import com.airbnb.mvrx.fragmentViewModel import com.airbnb.mvrx.withState import dagger.hilt.android.AndroidEntryPoint @@ -55,22 +54,7 @@ class LockScreenFragment : VectorBaseFragment() { handleEvent(it) } - withState(viewModel) { state -> - if (state.lockScreenConfiguration.mode == LockScreenMode.CREATE) return@withState - - viewLifecycleOwner.lifecycleScope.launchWhenResumed { - if (state.canUseBiometricAuth && state.isBiometricKeyInvalidated) { - lockScreenListener?.onBiometricKeyInvalidated() - } else if (state.showBiometricPromptAutomatically) { - showBiometricPrompt() - } - } - } - } - - override fun onDestroy() { - super.onDestroy() - viewModel.reset() + viewModel.handle(LockScreenAction.OnUIReady) } override fun invalidate() = withState(viewModel) { state -> @@ -83,6 +67,7 @@ class LockScreenFragment : VectorBaseFragment() { setupTitleView(views.titleTextView, false, state.lockScreenConfiguration) } } + renderDeleteOrFingerprintButtons(views, views.codeView.enteredDigits) } @@ -123,6 +108,8 @@ class LockScreenFragment : VectorBaseFragment() { is LockScreenViewEvent.AuthSuccessful -> lockScreenListener?.onAuthenticationSuccess(viewEvent.method) is LockScreenViewEvent.AuthFailure -> onAuthFailure(viewEvent.method) is LockScreenViewEvent.AuthError -> onAuthError(viewEvent.method, viewEvent.throwable) + is LockScreenViewEvent.ShowBiometricKeyInvalidatedMessage -> lockScreenListener?.onBiometricKeyInvalidated() + is LockScreenViewEvent.ShowBiometricPromptAutomatically -> showBiometricPrompt() } } diff --git a/vector/src/main/java/im/vector/app/features/pin/lockscreen/ui/LockScreenViewEvent.kt b/vector/src/main/java/im/vector/app/features/pin/lockscreen/ui/LockScreenViewEvent.kt index cbde16876c..543ed58ffa 100644 --- a/vector/src/main/java/im/vector/app/features/pin/lockscreen/ui/LockScreenViewEvent.kt +++ b/vector/src/main/java/im/vector/app/features/pin/lockscreen/ui/LockScreenViewEvent.kt @@ -24,4 +24,6 @@ sealed class LockScreenViewEvent : VectorViewEvents { data class AuthSuccessful(val method: AuthMethod) : LockScreenViewEvent() data class AuthFailure(val method: AuthMethod) : LockScreenViewEvent() data class AuthError(val method: AuthMethod, val throwable: Throwable) : LockScreenViewEvent() + object ShowBiometricKeyInvalidatedMessage : LockScreenViewEvent() + object ShowBiometricPromptAutomatically : LockScreenViewEvent() } diff --git a/vector/src/main/java/im/vector/app/features/pin/lockscreen/ui/LockScreenViewModel.kt b/vector/src/main/java/im/vector/app/features/pin/lockscreen/ui/LockScreenViewModel.kt index 79c1967670..d40f67ea35 100644 --- a/vector/src/main/java/im/vector/app/features/pin/lockscreen/ui/LockScreenViewModel.kt +++ b/vector/src/main/java/im/vector/app/features/pin/lockscreen/ui/LockScreenViewModel.kt @@ -17,12 +17,11 @@ package im.vector.app.features.pin.lockscreen.ui import android.annotation.SuppressLint +import android.app.KeyguardManager import android.os.Build import android.security.keystore.KeyPermanentlyInvalidatedException import androidx.fragment.app.FragmentActivity import com.airbnb.mvrx.MavericksViewModelFactory -import com.airbnb.mvrx.ViewModelContext -import com.airbnb.mvrx.withState import dagger.assisted.Assisted import dagger.assisted.AssistedFactory import dagger.assisted.AssistedInject @@ -31,26 +30,29 @@ import im.vector.app.core.di.hiltMavericksViewModelFactory import im.vector.app.core.platform.VectorViewModel import im.vector.app.features.pin.lockscreen.biometrics.BiometricAuthError import im.vector.app.features.pin.lockscreen.biometrics.BiometricHelper -import im.vector.app.features.pin.lockscreen.configuration.LockScreenConfiguration -import im.vector.app.features.pin.lockscreen.configuration.LockScreenConfiguratorProvider import im.vector.app.features.pin.lockscreen.configuration.LockScreenMode import im.vector.app.features.pin.lockscreen.crypto.LockScreenKeysMigrator import im.vector.app.features.pin.lockscreen.pincode.PinCodeHelper +import kotlinx.coroutines.delay import kotlinx.coroutines.flow.catch import kotlinx.coroutines.flow.emitAll +import kotlinx.coroutines.flow.firstOrNull import kotlinx.coroutines.flow.flow import kotlinx.coroutines.flow.launchIn import kotlinx.coroutines.flow.onEach -import kotlinx.coroutines.runBlocking +import kotlinx.coroutines.launch +import kotlinx.coroutines.withTimeoutOrNull import org.matrix.android.sdk.api.util.BuildVersionSdkIntProvider +import kotlin.time.Duration.Companion.milliseconds +import kotlin.time.Duration.Companion.seconds class LockScreenViewModel @AssistedInject constructor( @Assisted val initialState: LockScreenViewState, private val pinCodeHelper: PinCodeHelper, - private val biometricHelper: BiometricHelper, + biometricHelperFactory: BiometricHelper.BiometricHelperFactory, private val lockScreenKeysMigrator: LockScreenKeysMigrator, - private val configuratorProvider: LockScreenConfiguratorProvider, - private val buildVersionSdkIntProvider: BuildVersionSdkIntProvider, + private val versionProvider: BuildVersionSdkIntProvider, + private val keyguardManager: KeyguardManager, ) : VectorViewModel(initialState) { @AssistedFactory @@ -58,27 +60,9 @@ class LockScreenViewModel @AssistedInject constructor( override fun create(initialState: LockScreenViewState): LockScreenViewModel } - companion object : MavericksViewModelFactory by hiltMavericksViewModelFactory() { + companion object : MavericksViewModelFactory by hiltMavericksViewModelFactory() - override fun initialState(viewModelContext: ViewModelContext): LockScreenViewState { - return LockScreenViewState( - lockScreenConfiguration = DUMMY_CONFIGURATION, - canUseBiometricAuth = false, - showBiometricPromptAutomatically = false, - pinCodeState = PinCodeState.Idle, - isBiometricKeyInvalidated = false, - ) - } - - private val DUMMY_CONFIGURATION = LockScreenConfiguration( - mode = LockScreenMode.VERIFY, - pinCodeLength = 4, - isStrongBiometricsEnabled = false, - isDeviceCredentialUnlockEnabled = false, - isWeakBiometricsEnabled = false, - needsNewCodeValidation = false, - ) - } + private val biometricHelper = biometricHelperFactory.create(initialState.lockScreenConfiguration) private var firstEnteredCode: String? = null @@ -86,18 +70,37 @@ class LockScreenViewModel @AssistedInject constructor( private var isSystemAuthTemporarilyDisabledByBiometricPrompt = false init { - // We need this to run synchronously before we start reading the configurations - runBlocking { lockScreenKeysMigrator.migrateIfNeeded() } + viewModelScope.launch { + // Wait until the keyguard is unlocked before performing migrations, it might cause crashes otherwise on Android 12 and 12L + waitUntilKeyguardIsUnlocked() + // Migrate pin code / system keys if needed + lockScreenKeysMigrator.migrateIfNeeded() + // Update initial state with biometric info + updateStateWithBiometricInfo() + } + } - configuratorProvider.configurationFlow - .onEach { updateConfiguration(it) } - .launchIn(viewModelScope) + private fun observeStateChanges() { + // The first time the state allows it, show the biometric prompt + viewModelScope.launch { + if (stateFlow.firstOrNull { it.showBiometricPromptAutomatically } != null) { + _viewEvents.post(LockScreenViewEvent.ShowBiometricPromptAutomatically) + } + } + + // The first time the state allows it, react to biometric key being invalidated + viewModelScope.launch { + if (stateFlow.firstOrNull { it.isBiometricKeyInvalidated } != null) { + onBiometricKeyInvalidated() + } + } } override fun handle(action: LockScreenAction) { when (action) { is LockScreenAction.PinCodeEntered -> onPinCodeEntered(action.value) is LockScreenAction.ShowBiometricPrompt -> showBiometricPrompt(action.callingActivity) + is LockScreenAction.OnUIReady -> observeStateChanges() } } @@ -141,13 +144,18 @@ class LockScreenViewModel @AssistedInject constructor( private fun showBiometricPrompt(activity: FragmentActivity) = flow { emitAll(biometricHelper.authenticate(activity)) }.catch { error -> - if (buildVersionSdkIntProvider.get() >= Build.VERSION_CODES.M && error is KeyPermanentlyInvalidatedException) { - removeBiometricAuthentication() - } else if (error is BiometricAuthError && error.isAuthDisabledError) { - isSystemAuthTemporarilyDisabledByBiometricPrompt = true - updateStateWithBiometricInfo() + when { + versionProvider.get() >= Build.VERSION_CODES.M && error is KeyPermanentlyInvalidatedException -> { + onBiometricKeyInvalidated() + } + else -> { + if (error is BiometricAuthError && error.isAuthDisabledError) { + isSystemAuthTemporarilyDisabledByBiometricPrompt = true + updateStateWithBiometricInfo() + } + _viewEvents.post(LockScreenViewEvent.AuthError(AuthMethod.BIOMETRICS, error)) + } } - _viewEvents.post(LockScreenViewEvent.AuthError(AuthMethod.BIOMETRICS, error)) }.onEach { success -> _viewEvents.post( if (success) LockScreenViewEvent.AuthSuccessful(AuthMethod.BIOMETRICS) @@ -155,24 +163,22 @@ class LockScreenViewModel @AssistedInject constructor( ) }.launchIn(viewModelScope) - fun reset() { - configuratorProvider.reset() - } - - private fun removeBiometricAuthentication() { + private suspend fun onBiometricKeyInvalidated() { biometricHelper.disableAuthentication() updateStateWithBiometricInfo() + _viewEvents.post(LockScreenViewEvent.ShowBiometricKeyInvalidatedMessage) } - private fun updateStateWithBiometricInfo() { - val configuration = withState(this) { it.lockScreenConfiguration } - val canUseBiometricAuth = configuration.mode == LockScreenMode.VERIFY && + @SuppressLint("NewApi") + private suspend fun updateStateWithBiometricInfo() { + // This is a terrible hack, but I found no other way to ensure this would be called only after the device is considered unlocked on Android 12+ + waitUntilKeyguardIsUnlocked() + setState { + val isBiometricKeyInvalidated = biometricHelper.hasSystemKey && !biometricHelper.isSystemKeyValid + val canUseBiometricAuth = lockScreenConfiguration.mode == LockScreenMode.VERIFY && !isSystemAuthTemporarilyDisabledByBiometricPrompt && biometricHelper.isSystemAuthEnabledAndValid - val isBiometricKeyInvalidated = biometricHelper.hasSystemKey && !biometricHelper.isSystemKeyValid - val showBiometricPromptAutomatically = canUseBiometricAuth && - configuration.autoStartBiometric - setState { + val showBiometricPromptAutomatically = canUseBiometricAuth && lockScreenConfiguration.autoStartBiometric copy( canUseBiometricAuth = canUseBiometricAuth, showBiometricPromptAutomatically = showBiometricPromptAutomatically, @@ -181,8 +187,18 @@ class LockScreenViewModel @AssistedInject constructor( } } - private fun updateConfiguration(configuration: LockScreenConfiguration) { - setState { copy(lockScreenConfiguration = configuration) } - updateStateWithBiometricInfo() + /** + * Wait until the device is unlocked. There seems to be a behavior change on Android 12 that makes [KeyguardManager.isDeviceLocked] return `false` even + * after an Activity's `onResume` method. If we mix that with the system keys needing the device to be unlocked before they're used, we get crashes. + * See issue [#6768](https://github.com/vector-im/element-android/issues/6768). + */ + @SuppressLint("NewApi") + private suspend fun waitUntilKeyguardIsUnlocked() { + if (versionProvider.get() < Build.VERSION_CODES.S) return + withTimeoutOrNull(5.seconds) { + while (keyguardManager.isDeviceLocked) { + delay(50.milliseconds) + } + } } } diff --git a/vector/src/main/java/im/vector/app/features/pin/lockscreen/ui/LockScreenViewState.kt b/vector/src/main/java/im/vector/app/features/pin/lockscreen/ui/LockScreenViewState.kt index 8d2037953b..f689e1faf1 100644 --- a/vector/src/main/java/im/vector/app/features/pin/lockscreen/ui/LockScreenViewState.kt +++ b/vector/src/main/java/im/vector/app/features/pin/lockscreen/ui/LockScreenViewState.kt @@ -25,7 +25,11 @@ data class LockScreenViewState( val showBiometricPromptAutomatically: Boolean, val pinCodeState: PinCodeState, val isBiometricKeyInvalidated: Boolean, -) : MavericksState +) : MavericksState { + constructor(lockScreenConfiguration: LockScreenConfiguration) : this( + lockScreenConfiguration, false, false, PinCodeState.Idle, false + ) +} sealed class PinCodeState { object Idle : PinCodeState() diff --git a/vector/src/main/java/im/vector/app/features/settings/VectorSettingsPinFragment.kt b/vector/src/main/java/im/vector/app/features/settings/VectorSettingsPinFragment.kt index 4d95fc362b..db402758f1 100644 --- a/vector/src/main/java/im/vector/app/features/settings/VectorSettingsPinFragment.kt +++ b/vector/src/main/java/im/vector/app/features/settings/VectorSettingsPinFragment.kt @@ -28,6 +28,8 @@ import im.vector.app.features.notifications.NotificationDrawerManager import im.vector.app.features.pin.PinCodeStore import im.vector.app.features.pin.PinMode import im.vector.app.features.pin.lockscreen.biometrics.BiometricHelper +import im.vector.app.features.pin.lockscreen.configuration.LockScreenConfiguration +import im.vector.app.features.pin.lockscreen.configuration.LockScreenMode import kotlinx.coroutines.flow.collect import kotlinx.coroutines.launch import org.matrix.android.sdk.api.extensions.orFalse @@ -38,12 +40,15 @@ class VectorSettingsPinFragment @Inject constructor( private val pinCodeStore: PinCodeStore, private val navigator: Navigator, private val notificationDrawerManager: NotificationDrawerManager, - private val biometricHelper: BiometricHelper, + biometricHelperFactory: BiometricHelper.BiometricHelperFactory, + defaultLockScreenConfiguration: LockScreenConfiguration, ) : VectorSettingsBaseFragment() { override var titleRes = R.string.settings_security_application_protection_screen_title override val preferenceXmlRes = R.xml.vector_settings_pin + private val biometricHelper = biometricHelperFactory.create(defaultLockScreenConfiguration.copy(mode = LockScreenMode.CREATE)) + private val usePinCodePref by lazy { findPreference(VectorPreferences.SETTINGS_SECURITY_USE_PIN_CODE_FLAG)!! } @@ -102,9 +107,10 @@ class VectorSettingsPinFragment @Inject constructor( }.onFailure { showEnableBiometricErrorMessage() } + updateBiometricPrefState(isPinCodeChecked = usePinCodePref.isChecked) } - false + true } else { disableBiometricAuthentication() true diff --git a/vector/src/main/res/layout/item_recent_room.xml b/vector/src/main/res/layout/item_recent_room.xml new file mode 100644 index 0000000000..8e17707ff3 --- /dev/null +++ b/vector/src/main/res/layout/item_recent_room.xml @@ -0,0 +1,61 @@ + + + + + + + + + + + diff --git a/vector/src/main/res/values-ar/strings.xml b/vector/src/main/res/values-ar/strings.xml index aa28d9f481..1df5563686 100644 --- a/vector/src/main/res/values-ar/strings.xml +++ b/vector/src/main/res/values-ar/strings.xml @@ -97,14 +97,14 @@ • الخوادِم المُطابقة لـ %s أُزيلت مِن قائمة الحظر. • الخوادِم المُطابقة لـ %s محظورة الآن. • خوادِم مُطابقة IP الحرفية مسموحة الآن. - لقد قمت بتغيير قائمة الوصول لهذه الغُرفة. - قام %s بتغيير قائمة الوصول (ACL) لهذه الغُرفة. + لقد غَيَّرت قائمة الوصول لهذه الغُرفة. + غَيَّرَ %s قائمة التحكم بالوصول (ACL) لهذه الغُرفة. • الخوادِم المتطابقة من حيث بروتوكول الإنترنت (IP) المستخدم محظورة. • الخوادِم المُطابقة لـِ %s مسموحة. • الخوادِم المُطابقة لـ %s محظورة. الخوادِم المتطابقة من حيث بروتوكول الإنترنت (IP) المستخدم مسموحة. - لقد قمت بتعيين قائمة التحكم بالوصول لهذه الغُرفة. - %s قام بتعيين قائمة التحكم بالوصول لهذه الغرفة. + لقد عيَّنت قائمة التحكم بالوصول لهذه الغُرفة. + عيَّنَ %s قائمة التحكم بالوصول لهذه الغرفة. رقيتَ هُنا. رقّى %s هُنا. جعلتَ الرسائل المُستقبلية مرئية لـ %1$s @@ -895,11 +895,11 @@ %d ثانية العنوان - امسح التأريخ + امسح التاريخ لِج سجّل لج عبر %1$s - اتص بخادم مخصص + اتصل بخادم مخصص اتصل بخدمات مايتركس لـ Element اتصل بـ %1$s تابع @@ -1168,4 +1168,9 @@ طَلَبُ مُزامَنة أوَّلِيّ غُرفة عامة تسجيل مرئيّ + هذا البريد الإلكتروني غير مربوط بأي حساب + تابع + البريد الإلكتروني + كلمة السر الجديدة + التالي \ No newline at end of file diff --git a/vector/src/main/res/values-ca/strings.xml b/vector/src/main/res/values-ca/strings.xml index f224c80aab..f7366b7dd3 100644 --- a/vector/src/main/res/values-ca/strings.xml +++ b/vector/src/main/res/values-ca/strings.xml @@ -1277,7 +1277,7 @@ Consulta els termes de %s Accepta els termes per a continuar Estàs compartint les adreces de correu electrònic o números de telèfon amb el servidor d\'identitat %1$s. Per parar de compartir-les t\'has de tornar a connectar a %2$s. - Visualitza Edita Històric + Visualitza l\'històric d\'edició Indica els missatges eliminats Clau de recuperació de la còpia de seguretat de claus utilitzar la clau de recuperació de còpia de seguretat de claus @@ -1777,7 +1777,7 @@ Verifica el nou inici de sessió que està accedint al teu compte: %1$s Utilitza aquesta sessió per a verificar-ne una de nova i poder-li donar accés als missatges xifrats. Fins que aquest usuari no confiï en aquesta sessió, els missatges enviats i rebuts es marcaran amb una alerta. Com a alternativa, pots verificar-lo manualment. - Verifica aquest inici de sessió + Verifica aquest dispositiu Verifica aquesta sessió per fer-la de confiança i permetre que accedeixi als missatges xifrats. Si no has estat tu el que ha iniciat sessió aquí, pot ser que el teu compte estigui compromès: Verifica aquesta sessió Alguna de les següents pot haver estat compromesa: @@ -2228,7 +2228,7 @@ El fitxer és massa gran per carregar-lo. Ubicació Xifrat d\'extrem a extrem i sense haver de donar cap número de telèfon. Sense anuncis ni extracció de dades. - Tria on es desen les teves converses, donant-te control i independència. Connectat a través de Matrix. + Tria on es desen les teves converses, et dona control i independència. Connectat a través de Matrix. Comunicació segura i independent que t\'ofereix el mateix nivell de privadesa que una conversa cara a cara a casa teva. Missatgeria pel teu equip. Missatgeria segura. @@ -2535,7 +2535,7 @@ Envia un primer missatge per convidar %s a parlar Els missatges d\'aquest xat seran xifrats d\'extrem a extrem. Crea - Confirma el teu correu electrònic, prem el botó del correu que t\'hem enviat a %s + Segueix les instruccions enviades a %s Si us plau, llegeix les condicions i polítiques de %s Contacta Et podran trobar com a %s @@ -2547,7 +2547,7 @@ Usuari / Correu / Telèfon No es pot obrir l\'enllaç: les comunitats han estat substituïdes pels espais No tens permís per compartir la ubicació en directe - Comprova el teu correu per verificar. + Verifica el teu correu Torna a enviar el codi S\'ha enviat un codi al %s Confirma el teu número de telèfon @@ -2588,4 +2588,19 @@ Restabliment de contrasenya Contrasenya oblidada Element Matrix Services (EMS) és un servei robust i fiable d\'allotjament (servidors) per comunicacions ràpides, segures i en temps real. Descobreix-ho a <a href=\"${ftue_ems_url}\">element.io/ems</a> + No podràs accedir a l\'històric de missatges xifrats. Restableix la còpia de seguretat de missatges i les claus de verificació per començar de nou. + Aprova automàticament els ginys d\'Element Call i permet l\'accés a la càmera i el micròfon + Activa els permisos d\'accés directe d\'Element Call + Ubicació en directe + Aquest codi QR sembla incorrecte. Prova de fer la verificació amb un altre mètode. + No s\'ha pogut verificar aquest dispositiu + Quina és l\'adreça del teu servidor\? + On viuen les teves converses + Actualitzant dades… + + %1$s i %2$d altre + %1$s i %2$d altres + + %1$s i %2$s + Correu no verificat, mira la teva safata d\'entrada \ No newline at end of file diff --git a/vector/src/main/res/values-cs/strings.xml b/vector/src/main/res/values-cs/strings.xml index a9f177128f..5733fde468 100644 --- a/vector/src/main/res/values-cs/strings.xml +++ b/vector/src/main/res/values-cs/strings.xml @@ -1321,7 +1321,7 @@ %d aktivní relace %d aktivních relací - Ověřit tuto relaci + Ověřit toto zařízení Pro ověření této relace použijte existující relaci, a tím ji udělíte přístup k zašifrovaným zprávám. Ověřit Ověřeno @@ -2599,8 +2599,8 @@ Zapomenuté heslo Znovu poslat e-mail Nepřišel vám e-mail\? - Pro potvrzení e-mailu klepněte na tlačítko ve zprávě, kterou jsme právě odeslali na adresu %s - Pro ověření si zkontrolujte svůj e-mail. + Postupujte podle pokynů zaslaných na adresu %s + Ověřte svůj e-mail Znovu odeslat kód Kód byl odeslán na %s Potvrďte své telefonní číslo @@ -2637,4 +2637,20 @@ Zvolit ručně Nastavit automaticky Volba velikosti písma + Automaticky schvalovat widgety Element hovorů a udělit přístup ke kameře/mikrofonu + Zapnout zkratky pro povolení Element hovorů + Poloha živě + Tento kód QR vypadá chybně. Zkuste provést ověření jinou metodou. + Nebudete mít přístup k historii zašifrovaných zpráv. Obnovte zálohování zabezpečených zpráv a ověřovací klíče a začněte znovu. + Nepodařilo se ověřit toto zařízení + Jaká je adresa vašeho serveru\? + Kde žijí vaše konverzace + Aktualizace vašich dat… + + %1$s a %2$d další + %1$s a %2$d další + %1$s a %2$d dalších + + %1$s a %2$s + E-mail nebyl ověřen, zkontrolujte si schránku \ No newline at end of file diff --git a/vector/src/main/res/values-de/strings.xml b/vector/src/main/res/values-de/strings.xml index fcb82889f5..fa42cc44a1 100644 --- a/vector/src/main/res/values-de/strings.xml +++ b/vector/src/main/res/values-de/strings.xml @@ -1092,7 +1092,7 @@ E-Mail Neues Passwort Achtung! - Eine Änderung deines Passworts wird alle Ende-zu-Ende-Schlüssek zurücksetzen. Dein verschlüsselter Chatverlauf wird dadurch unlesbar. Richte die Schlüsselsicherung ein oder exportiere deine Raumschlüssel aus einer anderen Sitzung, bevor du dein Passwort zurücksetzt. + Eine Änderung deines Passworts wird alle Ende-zu-Ende-Schlüssel zurücksetzen. Dein verschlüsselter Chatverlauf wird dadurch unlesbar. Richte die Schlüsselsicherung ein oder exportiere deine Raumschlüssel aus einer anderen Sitzung, bevor du dein Passwort zurücksetzt. Fortfahren Diese E-Mail-Adresse ist mit keinem Benutzerkonto verknüpft Prüfe deinen Posteingang @@ -1297,7 +1297,7 @@ Eine aktive Sitzung %d aktive Sitzungen - Verifiziere diese Sitzung + Verifiziere dieses Gerät Nutze eine vorhandene Sitzung um diese Sitzung zu verifizieren und ihr Zugriff auf verschlüsselte Nachrichten zu gewähren. Verifizieren Verifiziert @@ -1520,7 +1520,7 @@ Backup Absicherung gegen den Verlust verschlüsselter Nachrichten Richte Backup ein - Nachricht gelöscht + Nachricht entfernt Gelöschte Nachrichten zeigen Zeigt einen Platzhalter für gelöschte Nachrichten an Dedizierten Tab für ungelesene Nachrichten zur Hauptansicht hinzufügen @@ -2367,7 +2367,7 @@ Möchtest du einem existierenden Server beitreten\? Communities Teams - Wir helfen dir, in Verbindung zu kommen. + Wir helfen dir, in Verbindung zu kommen Mit wem wirst du am meisten chatten\? Link zu Thread kopieren Threads anzeigen @@ -2375,8 +2375,8 @@ Laden der Karte fehlgeschlagen Karte Hinweis: App wird neugestartet - diese Frage überspringen - Noch nicht sicher\? Du kannst %s + Diese Frage überspringen + Noch nicht sicher\? %s Freunde und Familie In Thread antworten Aus einem Thread @@ -2471,7 +2471,7 @@ %1$s, %2$s, %3$s Die neuesten Profilinformationen (Avatar und Anzeigename) für alle Nachrichten anzeigen. Aktuelle Benutzerinformationen anzeigen - Du kannst loslegen! + Sieht gut aus! einen Anzeigenamen wählen Zurück zum Home-Screen Threads Beta-Feedback @@ -2516,17 +2516,67 @@ teilten ihren Live-Standort Schritt überspringen Speichern und fortfahren - Deine Einstellungen wurden gespeichert. + Öffne die Einstellungen jederzeit um dein Profil zu aktualisieren Los geht\'s - Du kannst das jederzeit ändern. + Zeit, dem Namen ein Gesicht zu geben Profilbild hinzufügen Du kannst dies später ändern Anzeigename Dies wird angezeigt, wenn Du Nachrichten sendest. - Dein Konto %s wurde erstellt. + Dein Konto %s wurde erstellt Herzlichen Glückwunsch! Profil personalisieren ${app_name} ist auch für den Arbeitsplatz geeignet. Die sichersten Organisationen der Welt vertrauen darauf. Threads sind noch in Arbeit, und es stehen neue, aufregende Funktionen an, wie z. B. verbesserte Benachrichtigungen. Wir würden uns sehr über Dein Feedback freuen! - Nachrichten in diesem Chat werden End-zu-End-Verschlüsselt + Nachrichten in diesem Chat werden Ende-zu-Ende-verschlüsselt. + Bist du ein Mensch\? + Bitte lies dir %ss Bedingungen und Richtlinien durch + Server-Richtlinien + Folge den Anweisungen, die an %s gesendet wurden + E-Mail bestätigen + Ergebnisse sind nach Beenden der Abstimmung sichtbar + Prüfe deine E-Mails. + Passwort zurücksetzen + Gib mindestens 8 Zeichen ein. + E-Mail-Adresse + Telefonnummer + Erneut senden + %s wird dir einen Bestätigungslink schicken + Deine E-Mail-Adresse + Neues Passwort + Wähle ein neues Passwort + Alle Geräte abmelden + Bestätige deine Telefonnummer + Keine E-Mail erhalten\? + Folge den Anweisungen, die an %s gesendet wurden + Kann Link nicht öffnen: Communities wurden durch Spaces ersetzt + MSC3061: Raumschlüssel für vorherige Nachrichten teilen + Beim Einladen in einen Raum mit sichtbarem Verlauf wird der verschlüsselte Verlauf sichtbar sein. + Live-Standort + + %d Nachricht gelöscht + %d Nachrichten gelöscht + + Keine Element Call-Berechtigungsabfragen + Bestätige automatisch Element Call-Widgets und erlaube Kamera- und Mikrofonzugriff + Los + ändern + oder + Das Zuhause deiner Gespräche + Das zukünftige Zuhause für deine Gespräche + Systemstandard nutzen + Automatisch festlegen + Schriftgröße wählen + Manuell wählen + %1$s und %2$s + E-Mail nicht bestätigt, prüfe deinen Posteingang + Willkommen zurück! + Passwort vergessen + Benutzername / E-Mail / Telefon + Erstelle dein Konto + Serveradresse + Wie lautet die Adresse deines Servers\? Das wird eine Art Zuhause für deine Daten + Wie lautet die Adresse deines Servers\? + Muss 8 oder mehr Zeichen umfassen + Wähle deinen Server \ No newline at end of file diff --git a/vector/src/main/res/values-et/strings.xml b/vector/src/main/res/values-et/strings.xml index 45d8a1f96e..124ca1eae7 100644 --- a/vector/src/main/res/values-et/strings.xml +++ b/vector/src/main/res/values-et/strings.xml @@ -720,7 +720,7 @@ %d aktiivne sessioon %d aktiivset sessiooni - Verifitseeri see sisselogimissessioon + Verifitseeri see seade Kasuta olemasolevat sessiooni selle sessiooni verifitseerimiseks, andes sellega ligipääsu krüptitud sõnumitele. Verifitseeri Verifitseeritud @@ -2541,8 +2541,8 @@ Kas unustasid oma salasõna Saada e-kiri uuesti Sa ei saanud e-kirja kätte\? - Oma e-posti aadressi kinnitamiseks klõpsi selles kirjas leiduvat nuppu, mille just saatsime %s aadressile - Kinnitamiseks vaata oma e-kirju. + Palun järgi juhtnööre, mille just saatsime %s aadressile + Verifitseeri oma e-posti aadress Saada kinnituskood uuesti Kinnituskoodi saatsime telefoninumbrile %s Kinnita oma telefoninumber @@ -2579,4 +2579,19 @@ Vali ise Määra automaatselt Vali kirjatüübi suurus + Automaatsel luba kasutada Element\'i põhiste kõnede vidinaid ning luba ligipääs kaamerale ja mikrofonile + Võta kasutsele Element\'i põhiste kõnede õiguste kiirnupud + Asukoht reaalajas + See QR-kood tundub olema vigane. Palun proovi verifitseerimist mõne muu meetodiga. + Sa ei saa lugeda varasemaid krüptitud sõnumeid. Uuesti alustamiseks seadista varundusvõtmed ja verifitseerimisvõtmed. + Selle seadme verifitseerimine ei õnnestunud + Mis on sinu koduserveri aadress\? + Kuidas sinu vestlusi hallatakse + Sinu andmed on uuendamisel… + + %1$s ja veel %2$d + %1$s ja veel %2$d muud + + %1$s ja %2$s + E-posti aadress on kinnitamata, palun vaata oma saabunud e-kirju \ No newline at end of file diff --git a/vector/src/main/res/values-fa/strings.xml b/vector/src/main/res/values-fa/strings.xml index 26a006f498..591ae21198 100644 --- a/vector/src/main/res/values-fa/strings.xml +++ b/vector/src/main/res/values-fa/strings.xml @@ -982,7 +982,7 @@ ورود چندگانه به کار نیفتاده مدیر کارسازتان رمزنگاری سرتاسری پیش‌گزیده را در اتاق‌های خصوصی و پیام‌های مستقیم از کار انداخته است. خروج از این نشست - تأیید این ورود + تأیید این افزاره استفاده از نشستی موجود برای تأییدش که به پیام‌های رمزشده دسترسی می‌دهد. تأیید تأیید‌شده @@ -2550,8 +2550,8 @@ فراموشی گذرواژه فرستادن دوبارهٔ رایانامه رایانامه‌ای نگرفتید؟ - برای تأیید رایانامه‌تان، روی دکمه در رایانامه‌ای‌که اکنون برایتان به %s فرستادیم بزنید - برای تأیید، رایانامه‌تان را بررسی کنید. + دستورالعمل‌های فرستاده شده به %s را دنبال کنید + رایانامه‌تان را تأیید کنید فرستادن دوبارهٔ رمز رمزی به %s فرستاده خواهد شد شمارهٔ تلفنتان را تأیید کنید @@ -2588,4 +2588,19 @@ گزینش دستی تنظیم خودکار گزینش اندازهٔ قلم + تأیید خودکار ابزارک‌های تماس و اعطای دسترسی دوربین و میکروفون + به کار انداخت میان‌برهای اجازهٔ تماس المنت + مکان زنده + این رمز QR بدریخت به نظر می‌رسد. لطفاً تأیید با روشی دیگر را بیازمایید. + نخواهید توانست به تاریخچهٔ پیام رمزشده دست یابید. برای آغاز تازه، کلیدهای تأیید و پشتیبان پیام امنتان را بازنشانی کنید. + ناتوان در تأیید این افزاره + نشانی کارسازتان چیست؟ + زیستگاه گفت‌وگوهایتان + به‌روز رساندن داده‌هایتان… + + %1$s و ۱ والد دیگر + %1$s و %2$d والد دیگر + + %1$s و %2$s + رایانامه تأیید نشده. صندوق ورودیتان را بررسی کنید \ No newline at end of file diff --git a/vector/src/main/res/values-fr/strings.xml b/vector/src/main/res/values-fr/strings.xml index 1039a66f32..07849141b6 100644 --- a/vector/src/main/res/values-fr/strings.xml +++ b/vector/src/main/res/values-fr/strings.xml @@ -1273,7 +1273,7 @@ %d session active %d sessions actives - Vérifier cette session + Vérifier cet appareil Utilisez une session existante pour vérifier celle-ci, ce qui lui permettra d’avoir accès aux messages chiffrés. Vérifier Vérifié @@ -2550,8 +2550,8 @@ Mot de passe oublié Renvoyer le courriel Pas reçu de courriel \? - Pour confirmer votre courriel, appuyez sur le bouton dans le courriel que nous venons d’envoyer à %s - Relevez vos courriels pour la vérification. + Suivez les instructions envoyées à %s + Vérifiez votre courriel Renvoyer le code Un code a été envoyé au %s Confirmer votre numéro de téléphone @@ -2588,4 +2588,19 @@ Choisir manuellement Automatique Choisir la taille de la police + Approuve automatiquement les widgets de Element Call et leur donner l’accès au micro et à la caméra + Activer les raccourcis de permission de Element Call + Position en direct + Ce QR code semble incorrect. Veuillez réessayer la vérification avec une autre méthode. + Vous ne pourrez plus accéder à l’historique des messages chiffrés. Réinitialiser votre sauvegarde sécurisée des messages et vos clés de récupération pour un nouveau départ. + Impossible de vérifier cet appareil + Quelle est l’adresse de votre serveur \? + Où seront vos conversations + Mise-à-jour de vos données… + + %1$s et %2$d autre + %1$s et %2$d autres + + %1$s et %2$s + Courriel non vérifié, relevez votre boîte de réception \ No newline at end of file diff --git a/vector/src/main/res/values-hu/strings.xml b/vector/src/main/res/values-hu/strings.xml index dc9e17fc75..33c0044843 100644 --- a/vector/src/main/res/values-hu/strings.xml +++ b/vector/src/main/res/values-hu/strings.xml @@ -1064,9 +1064,9 @@ A Visszaállítási Kulcsot tartsd biztonságos helyen, mint pl. egy jelszókeze Az alkalmazás nem tud bejelentkezni a Matrix szerverbe. A Matrix szerver ezeket a bejelentkezési módokat támogatja: %1$s. \n \nWeb klienssel szeretnél bejelentkezni\? - Az alkalmazás nem tud fiókot készíteni ezen a Matrix szerveren. + Ez az alkalmazás nem tudott fiókot készíteni ezen a Matrix szerveren. \n -\nWeb klienssel szeretnél bejelentkezni\? +\nSzeretnél a webes kliens segítségével létrehozni egy fiókot\? Koppints a linkre az új jelszó megerősítéséhez. Miután követted a linket, kattints alább. Állíts be egy e-mail címet a fiókod visszaállításához. Később esetleg engedélyezheted, hogy ismerősök e-mail címmel megtalálhassanak. Nemzetközi telefonszámnak „+” jellel kell kezdődnie @@ -1077,7 +1077,7 @@ A Visszaállítási Kulcsot tartsd biztonságos helyen, mint pl. egy jelszókeze Következő A felhasználónév már használatban van Figyelmeztetés - A felhasználói fiókod még nincs kész. Megállítód a regisztrációt\? + A felhasználói fiókod még nincs kész. Félbehagyod a regisztrációt\? matrix.org kiválasztása Element Matrix Services kiválasztása Egyedi matrix szerver kiválasztása @@ -2318,7 +2318,7 @@ A Visszaállítási Kulcsot tartsd biztonságos helyen, mint pl. egy jelszókeze A változások életbelépéséhez indítsd újra az alkalmazást. LaTeX matematikai szintaxis engedélyezése Nem léphetsz be ebbe a szobába - Birtokold a beszélgetéseid. + Vedd birtokba a beszélgetéseid. Titkosítás visszafejtési hiba esemény alkalmával a rendszer automatikusan elküldi a logokat Titkosítás visszafejtési hibák automatikus jelentése. Szavazás létrehozása @@ -2330,7 +2330,7 @@ A Visszaállítási Kulcsot tartsd biztonságos helyen, mint pl. egy jelszókeze Megjelenítendő név színének megváltoztatása Már van fiókom Biztonságos üzenetküldés. - Te irányítasz. + Tiéd az irányítás. Tartózkodási hely megosztása Megnyitás ezzel Az ${app_name} nem fér hozzá a tartózkodási helyedhez. Próbáld újra később. @@ -2351,9 +2351,9 @@ A Visszaállítási Kulcsot tartsd biztonságos helyen, mint pl. egy jelszókeze A földrajzi helyzetüket megosztották Fiók létrehozása Üzenetküldés a csoportodnak. - Telefonszám nélkül végpontok között titkosított. Reklámok és adatbányászat nélkül. - Válaszd meg hol legyenek a beszélgetéseid tárolva, visszaadja az irányítást és függetlenné tesz. Csatlakozva a Matrixhoz. - Biztonságos és független kommunikáció ami olyan biztonságos mintha valakivel négyszemközt beszélgetnél a házadban. + Végponti tikosítással és telefonszám nélküli regisztrációval. Reklámok és adatbányászat nélkül. + Te választhatod ki, hogy hol legyenek a beszélgetéseid tárolva, ezáltal visszadva az irányítást és a függetlenséget neked. A Matrix hálózathoz csatlakozva. + Biztonságos és független kommunikáció, ami olyan biztonságos mint ha valakivel négyszemközt beszélgetnél a házadban. Földrajzi helyzet A titkosítás beállítása hibás így nem lehet üzenetet küldeni. Kattints a beállításokért. A titkosítás beállítása hibás így nem lehet üzenetet küldeni. Kérjük vedd fel a kapcsolatot az adminisztrátorral a titkosítás helyreállításához. @@ -2367,9 +2367,9 @@ A Visszaállítási Kulcsot tartsd biztonságos helyen, mint pl. egy jelszókeze Kihagyhatod ezt a kérdést. Még nem tudod\? %s Közösségek - Csoportok + Munkahelyi csoportok Barátok és család - Segítünk a kapcsolatteremtésben. + Segítünk a kapcsolatteremtésben Kikkel fogsz legtöbbet beszélgetni\? Jelenleg ezt az üzenetszálat olvasod! Megjelenítés szobában @@ -2424,17 +2424,17 @@ A Visszaállítási Kulcsot tartsd biztonságos helyen, mint pl. egy jelszókeze ${app_name} Folyamatos helymeghatározás A matrix szerver nem fogad el olyan felhasználói nevet ami csak számokból áll. Lépés kihagyása - Mentés és tovább - A beállítások elmentve. - Minden kész! + Mentés és folytatás + A beállításokban bármikor megváltoztathatod a profilod adatait + Jónak tűnik! Gyerünk - Bármikor megváltoztatható. + Itt az ideje egy profilképet adni a fiókhoz Profilkép hozzáadása Ezt később meg lehet változtatni Megjelenítendő név Ez fog megjelenni amikor üzenetet küldesz. - Válassz egy megjelenítési nevet - A fiókod elkészült: %s. + Válassz egy megjelenítendő nevet + A fiókod elkészült. A Matrix címed: %s Gratulálunk! A kezdőlapra Profil személyre szabása @@ -2499,35 +2499,35 @@ A Visszaállítási Kulcsot tartsd biztonságos helyen, mint pl. egy jelszókeze mperc perc ó - Földrajzi hely megosztás engedélyezése - Figyelem: ez a labor lehetőség egy átmeneti megvalósítás. Ez azt jelenti, hogy a szobába már elküldött helyadatok az élő hely megosztás leállítása után is hozzáférhetők maradnak a szobában. - Élő földrajzi hely megosztása + Földrajzi hely megosztásának engedélyezése + Figyelem: ez a labs lehetőség egy átmeneti megvalósítás. Ez azt jelenti, hogy nem lehet utólag törölni a megosztott helyadatokat. A technikailag hozzáértő szoba tagok a helyzetmegosztás leállítása után is meg tudják nézni az útvonalat. + Folyamatos helyzetmegosztás Jelenlegi átjáró: %s Átjáró (gateway) Nem található végpont. Jelenlegi végpont: %s Végpont Jelenleg használatban: %s. - Metódus + Mód - %d beállítás található. - %d beállítás található. + %d mód található. + %d mód található. - A háttér szinkronizációs szolgáltatástól eltérő beállítási lehetőség nem érhető el. - A Google Play szolgáltatástól eltérő beállítási lehetőség nem érhető el. - Elérhető beállítások - Értesítési beállítások + A háttér szinkronizációs szolgáltatáson kívül nem található más mód. + A Google Play szolgáltatásokon kívül nem található más mód. + Elérhető módok + Értesítési módok Szinkronizálás a háttérben Google szolgáltatások Válaszd ki, hogyan szeretnél értesítéseket kapni - Az eredmény a szavazás végeztével válik láthatóvá - Ha olyan titkosított szobába hívsz meg valakit ahol a régi üzenetek megosztása engedélyezett a régi titkosított üzenetek is láthatóak lesznek a meghívott számára. + Az eredmény a szavazás lezárása után válik láthatóvá + Ha olyan titkosított szobába hívsz meg valakit ahol a régi üzenetek megosztása be van kapcsolva, akkor a meghívott felhasználó el fogja tudni olvasni a meghívása előtt küldött üzeneteket is. MSC3061: Szoba kulcsok megosztása a régi üzenetekhez - A biometrikus azonosítást nem lehet engedélyezni. - A biometrikus azonosítás kikapcsolásra került mivel egy új biometrikus azonosítási metódus került hozzáadásra. Újra engedélyezheted a Beállításokban. - Az első üzeneteddel hívd meg ide őt: %s + Nem sikerült bekapcsolni a biometrikus azonosítást. + A biometrikus azonosítás kikapcsolásra került mivel egy új biometrikus azonosítási mód került hozzáadásra. Újra engedélyezheted a Beállításokban. + A meghívó elküldéséhez küldj %s felhasználónak egy üzenetet Az üzenetek ebben a beszélgetésben végpontok közötti titkosítással lesznek védve. - Értesítési metódus visszaállítása + Értesítési mód visszaállítása Profil címke: Menj Végpont token regisztrációja sikertelen a Matrix-kiszolgálón: @@ -2535,4 +2535,44 @@ A Visszaállítási Kulcsot tartsd biztonságos helyen, mint pl. egy jelszókeze Végpont sikeresen regisztrálva lett a matrix szerveren. Végpont regisztráció Következő + Felhasználónév / e-mail / telefonszám + Valódi személy vagy\? + Kövesd az útmutatást, amit ide küldtünk: %s + Jelszó visszaállítás + Elfelejtett jelszó + E-mail újraküldése + Nem kaptad meg az e-mailt\? + Kövesd az útmutatást, amit ide küldtünk: %s + E-mail ellenőrzése + Kód újraküldése + Egy kódot küldtünk ide: %s + Telefonszám megerősítése + Összes eszközöm kijelentkeztetése + Jelszó visszaállítása + Bizonyosodj meg róla, hogy minimum 8 karakter hosszú. + Megerősítő kód + Telefonszám + A %s szervernek ellenőriznie kell a fiókod + Add meg a telefonszámod + E-mail + A %s szervernek ellenőriznie kell a fiókod + Add meg az e-mail címed + Kérünk, olvasd el a %s szerver felhasználási feltételeit + Szerver szabályok + Kapcsolatfelvétel + Az Element Matrix Services (EMS) egy robosztus és megbízható szerver üzemeltetési szolgáltatás a gyors és biztonságos kommunikáció céljaira. Tudj meg többet itt: element.io/ems + Szeretnél egy saját szervert\? + Szerver URL + Mi a szervered címe\? + Válassz szervert + Üdv újra! + Szerkesztés + Ahol a beszélgetéseid tárolva vannak + vagy + Minimum 8 karakterből kell álljon + Mások ezen a címen tudnak írni neked: %s + Fiók létrehozása + Itt lesznek tárolva a beszélgetéseid + Mi a szervered címe\? Itt lesz tárolva az összes üzeneted + Az email cím nem lett ellenőrizve, kérlek nézd meg a beérkező email-jeidet \ No newline at end of file diff --git a/vector/src/main/res/values-it/strings.xml b/vector/src/main/res/values-it/strings.xml index 6dbdffd27e..46bb5453a8 100644 --- a/vector/src/main/res/values-it/strings.xml +++ b/vector/src/main/res/values-it/strings.xml @@ -1289,7 +1289,7 @@ %d sessione attiva %d sessioni attive - Verifica questo accesso + Verifica questo dispositivo Usa una sessione esistente per verificare questa, dandole l\'accesso ai messaggi criptati. Verifica Verificato @@ -2541,8 +2541,8 @@ Password dimenticata Reinvia email Non hai ricevuto un\'email\? - Per confermare l\'email, tocca il pulsante nell\'email che abbiamo appena inviato a %s - Controlla l\'email per verificare. + Segui le istruzioni inviate a %s + Verifica l\'email Reinvia codice È stato inviato un codice a %s Conferma il tuo numero di telefono @@ -2579,4 +2579,19 @@ Scegli manualmente Imposta automaticamente Scegli dimensione caratteri + Auto-approva i widget di Element Call e consenti accesso a fotocamera / microfono + Attiva scorciatoie di autorizzazione di Element Call + Posizione in tempo reale + Questo codice QR sembra sbagliato. Prova la verifica con un altro metodo. + Non potrai accedere alla cronologia dei messaggi cifrati. Reimposta il backup dei messaggi sicuri e le chiavi di verifica per ricominciare. + Impossibile verificare questo dispositivo + Qual è l\'indirizzo del tuo server\? + Dove vivono le tue conversazioni + Aggiornamento dei tuoi dati… + + %1$s e %2$d altro + %1$s e altri %2$d + + %1$s e %2$s + Email non verificata, controlla la posta in arrivo \ No newline at end of file diff --git a/vector/src/main/res/values-nl/strings.xml b/vector/src/main/res/values-nl/strings.xml index 2c66e51960..a79f5e7aee 100644 --- a/vector/src/main/res/values-nl/strings.xml +++ b/vector/src/main/res/values-nl/strings.xml @@ -2000,7 +2000,7 @@ Deze sessie wordt vertrouwd voor veilig berichtenverkeer omdat %1$s (%2$s) deze heeft geverifieerd: Kan geen sessies ophalen Gebruik een bestaande sessie om deze te verifiëren en deze toegang te verlenen tot versleutelde berichten. - Verifieer deze login + Verifieer dit apparaat %d actieve sessie %d actieve sessies @@ -2550,8 +2550,8 @@ Wachtwoord vergeten Email opnieuw verzenden Geen e-mail ontvangen\? - Om uw e-mailadres te bevestigen, tikt u op de knop in de e-mail die we zojuist naar %s hebben gestuurd - Controleer uw e-mail om te verifiëren. + Volg de instructies die naar %s zijn gestuurd + Verifieer uw e-mailadres Code nogmaals versturen Er is een code verzonden naar %s Bevestig uw telefoonnummer @@ -2588,4 +2588,19 @@ Handmatig kiezen Automatisch instellen Kies lettergrootte + Keur automatisch Element Oproep widgets goed en verleen camera-/microfoontoegang + Snelkoppelingen voor Element Oproep machtigingen inschakelen + Live locatie + Deze QR-code lijkt misvormd. Probeer te verifiëren met een andere methode. + U hebt geen toegang tot de gecodeerde berichtgeschiedenis. Reset uw Veilige Berichten Back-up en verificatiesleutels om opnieuw te beginnen. + Kan dit apparaat niet verifiëren + Wat is het adres van uw server\? + Waar uw conversaties leven + Uw gegevens bijwerken… + + %1$s en %2$d andere + %1$s en %2$d andere + + %1$s en %2$s + E-mailadres niet geverifieerd, controleer je inbox \ No newline at end of file diff --git a/vector/src/main/res/values-pl/strings.xml b/vector/src/main/res/values-pl/strings.xml index 421de65bce..71669740db 100644 --- a/vector/src/main/res/values-pl/strings.xml +++ b/vector/src/main/res/values-pl/strings.xml @@ -457,7 +457,7 @@ Markdown został włączony. Markdown został wyłączony. %1$s: %2$s - %d+ + +%d Użyj domyślnego dzwonka ${app_name} dla przychodzących połączeń Połączenia Wyrzuć @@ -1132,7 +1132,7 @@ %d aktywnych sesji %d aktywnych sesji - Zweryfikuj tą sesję + Zweryfikuj te urządzenie Otwórz obecną sesję i użyj jej do zweryfikowania obecnej, przyznając jej dostęp do zaszyfrowanych wiadomości. Zweryfkuj Zweryfikowano @@ -1303,7 +1303,7 @@ %s stworzył(a) i skonfigurował(a) ten pokój. Szyfrowanie wykorzystywane przez ten pokój nie jest obsługiwane Szyfrowanie wyłączone - Wiadomości w tym pokoju są szyfrowane w trybie punkt-punkt (e2e). + Wiadomości w tym czacie są szyfrowane end-to-end. Wiadomości w tym pokoju są szyfrowane punkt-punkt (e2e). Możesz dowiedzieć się więcej i zweryfikować użytkowników w ich profilach. Szyfrowanie włączone Jeżeli teraz przerwiesz, możesz utracić zaszyfrowane wiadomości oraz dane jeżeli utracisz dostęp do zalogowanych sesji. @@ -2377,28 +2377,28 @@ Utwórz konto Pomiń ten krok Zapisz i kontynuuj - Twoje ustawienia zostały zapisane. - Wszystko ustawiłeś! + Zawsze możesz zmienić swój profil w ustawieniach + Dobrze wyglądasz! Chodźmy - Możesz go zmienić w każdym momencie. + Pora nadać tej nazwie jakąś twarz Dodaj obraz profilu Możesz zmienić ją później Powiadomienie pokoju Wyświetlana nazwa Będzie ona widoczna podczas wysyłania wiadomości. Wybierz wyświetlaną nazwę - Twoje konto %s zostało utworzone. + Twoje konto %s zostało utworzone Gratulacje! Zabierz mnie do domu Spersonalizuj profil Połącz się z serwerem Chcesz dołączyć do istniejącego serwera\? - pominąć to pytanie - Nie wiesz jeszcze\? Możesz %s + Pomiń to pytanie + Nie wiesz jeszcze\? %s Społeczności Zespoły Przyjaciele i rodzina - Pomożemy Ci się połączyć. + Pomożemy Ci się połączyć Z kim będziesz najczęściej rozmawiać\? Szyfrowane od-końca-do-końca i nie wymaga numeru telefonu. Brak reklam i dataminingu. Wybierz, gdzie prowadzone są Twoje rozmowy, dając Ci kontrolę i niezależność. Połączenie przez sieć Matrix. @@ -2636,8 +2636,8 @@ Zapomniane hasło Wyślij wiadomość ponownie Email nie dotarł\? - Aby potwierdzić swój email, dotknij przycisk we wiadomości wysłanej na %s - Sprawdź swoją skrzynkę email aby dokończyć weryfikację. + Wykonaj instrukcje podane w wiadomości wysłanej na %s + Potwierdź swój email Wyślij kod ponownie Kod został wysłany do %s Potwierdź swój numer telefonu @@ -2672,4 +2672,25 @@ Ustaw ręcznie Ustaw automatycznie Wybierz rozmiar czcionki + Automatycznie akceptuj widżety Element Call i przyznaj dostęp do kamery i mikrofonu + Włącz skróty uprawnień dla Element Call + Uwaga: to eksperymentalna funkcja wykorzystująca tymczasową implementację. Oznacza to, że nie będzie możliwości usunięcia historii lokalizacji, a zaawansowani użytkownicy będą mogli ją zobaczyć nawet gdy przestaniesz dzielić się lokalizacją na żywo z tym pokojem. + Lokalizacja na żywo + Zapraszając kogoś do zaszyfrowanego pokoju który współdzieli historię, zaszyfrowana historia będzie dla tej osoby widoczna. + MSC3061: Współdzielenie kluczy pokoju dla wcześniejszych wiadomości + Ten kod QR wygląda na niepoprawny. Spróbuj zweryfikować przy użyciu innej metody. + Dostęp do wcześniejszych zaszyfrowanych wiadomości nie będzie możliwy. Zresetuj bezpieczną kopię zapasową wiadomości oraz klucze weryfikacyjne by zacząć od nowa. + Nie udało się zweryfikować tego urządzenia + Zapoznaj się z warunkami i zasadami serwera %s + Jaki jest adres twojego serwera\? + Miejsce na twoje konwersacje + Aktualizowanie twoich danych… + + %1$s i %2$d inny + %1$s i %2$d inne + %1$s i %2$d innych + %1$s i %2$d innych + + %1$s i %2$s + Email nie został zweryfikowany, sprawdź swoją skrzynkę \ No newline at end of file diff --git a/vector/src/main/res/values-pt-rBR/strings.xml b/vector/src/main/res/values-pt-rBR/strings.xml index 26218a509a..8e4e1942da 100644 --- a/vector/src/main/res/values-pt-rBR/strings.xml +++ b/vector/src/main/res/values-pt-rBR/strings.xml @@ -1390,7 +1390,7 @@ %d sessão ativa %d sessões ativas - Verificar este login + Verificar este dispositivo Use uma sessão existente para verificar esta aqui, concedendo-lhe acesso a mensagens encriptadas. Verificar Verificada(o) @@ -2549,8 +2549,8 @@ Esqueceu senha Reenviar email Não recebeu um email\? - Para confirmar seu email, toque no botão no email que nós acabamos de enviar para %s - Checar seu email para verificar. + Siga as instruções enviadas para %s + Verifique seu email Reenviar código Um código foi enviado para %s Confirmar seu número de telefone @@ -2588,4 +2588,19 @@ Escolher manualmente Definir automaticamente Encolher tamanho de fonte + Auto-aprovar widgets de Element Call e conceder acesso de câmera / mic + Habilitar atalhos de permissão de Element Call + Localização ao vivo + Este QR code parece malformado. Por favor tente verificar com um outro método. + Você não vai ser capaz de acessar histórico de mensagem encriptada. Resette seu Backup de Mensagem Seguro e chaves de verificação para começar do zero. + Incapaz de verificar este dispositivo + Qual é o endereço de seu servidor\? + Onde suas conversas vivem + Atualizando seus dados… + + %1$s e %2$d outro + %1$s e %2$d outros + + %1$s e %2$s + Email não verificado, cheque sua inbox \ No newline at end of file diff --git a/vector/src/main/res/values-ru/strings.xml b/vector/src/main/res/values-ru/strings.xml index 55c5ab0127..1fd522c7d5 100644 --- a/vector/src/main/res/values-ru/strings.xml +++ b/vector/src/main/res/values-ru/strings.xml @@ -194,7 +194,7 @@ %1$s, %2$s и %3$s 🎉 Всем серверам запрещено участвовать! Эта комната больше не может быть использована. Без изменений. - Пустая комната (была %s) + Пустая комната (был(а) %s) • Соответствующие серверы %s заблокированы. • Серверы, соответствующие буквальным IP-адресам, теперь запрещены. • Серверы, соответствующие буквальным IP-адресам, теперь разрешены. @@ -302,7 +302,7 @@ Камера Вход Подать - Неправильный логин и/или пароль + Неправильное имя пользователя и/или пароль Это не похоже на действительный адрес электронной почты Этот адрес электронной почты уже используется. Забыли пароль? @@ -962,7 +962,7 @@ Отфильтровать беседы… Не можете найти то, что ищете\? Создать новую комнату - Отправить новое сообщение в диалог + Отправить новое личное сообщение Просмотр каталога комнат Имя или ID (#example:matrix.org) Включить жест смахивания для ответа в ленте сообщений @@ -1455,7 +1455,7 @@ %d сессии активны %d сессий активно - Подтвердите эту сессию + Подтвердите это устройство Используйте существующую сессию для подтверждения этой, предоставив ей доступ к зашифрованным сообщениям. Инструменты для разработчиков Данные учётной записи @@ -1749,7 +1749,7 @@ Забыли или потеряли все варианты восстановления\? Сбросить все Вы вошли. %s вошёл(ла). - Сообщения в этой комнате зашифрованы сквозным шифрованием. + Сообщения в этой переписке защищены сквозным шифрованием. Покинуть Настройки Сообщения здесь защищены сквозным шифрованием. @@ -1840,7 +1840,7 @@ Отправить историю запросов на обмен ключами Начать беседу %s чтобы люди знали, о чём эта комната. - Это начало вашей истории диалога с %s. + Это начало вашей переписки с %s. Экспорт аудита Личное сообщение Отправить электронную почту и номера телефонов @@ -2035,7 +2035,7 @@ Ищете кого-то не в %s\? %s приглашает вас Вы приглашены - Пространства - это новый способ группировки комнат и людей. + Пространства — это новый способ организации комнат и людей. Добавить существующие комнаты и пространство Вы единственный администратор этого пространства. Если вы его покинете, то никто не сможет управлять им. Вы сможете присоединиться только после повторного приглашения. @@ -2387,7 +2387,7 @@ Восстановить шифрование Обратитесь к администратору, чтобы восстановить шифрование до рабочего состояния. Шифрование настроено неправильно. - Поделились своим местоположением + Поделился(лась) местоположением У меня уже есть учётная запись Создать учётную запись Обмен сообщениями для вашей команды. @@ -2443,12 +2443,12 @@ Включить обсуждения сообщений Подключиться к серверу Хотите присоединиться к существующему серверу\? - пропустить вопрос - Пока не уверенны\? Вы можете %s + Пропустить вопрос + Пока не уверены\? %s Сообщества Команды Друзья и семья - Мы поможем вам подключится. + Мы поможем вам подключиться С кем вы будете общаться больше всего\? Вы уже просматриваете это обсуждение! Просмотр в Комнате @@ -2489,7 +2489,7 @@ Отображаемое имя Показывается, когда вы отправляете сообщения. Выберите отображаемое имя - Ваш аккаунт «%s» создан. + Ваша учётная запись %s создана Поздравляем! Домой Персонализировать @@ -2523,7 +2523,7 @@ Загрузка трансляции местоположения… Поделиться трансляцией местоположения на Трансляцией местоположения - Поделились трансляцией местоположения + Поделился(лась) трансляцией местоположения Трансляцией местоположения Некоторые результаты могут быть скрыты, поскольку они являются приватными и для их просмотра необходимо приглашение. мин @@ -2550,7 +2550,7 @@ Пропустить этот шаг Сохранить и продолжить Ваши предпочтения были сохранены. - Всё готово! + Выглядит хорошо! ${app_name} также отлично подходит для работы. Ему доверяют самые надёжные организации в мире. Резервная копия имеет действительную подпись для данного пользователя. Воспроизводить анимированные изображения, как только они попадают в поле зрения @@ -2609,4 +2609,58 @@ Текущая конечная точка: %s Конечная точка Вещи в этом пространстве - + Поделиться местоположением + MSC3061: Предоставление ключей от прошлых сообщений + Вперёд + Убедитесь, что он состоит из 8 или более символов. + Выбрать вручную + Выбор размера шрифта + + Удалено %d сообщение + Удалено %d сообщения + Удалено %d сообщений + Удалено %d сообщений + + Выйти из учётной записи на всех устройствах + Трансляция местоположения + Сообщения в этой переписке будут защищены сквозным шифрованием. + Обновление данных… + Повторно отправить письмо + Следуйте инструкциям, отправленным на %s + Подтвердите свою электронную почту + Проверьте электронную почту. + Пожалуйста, ознакомьтесь с условиями и правилами %s + Хотите разместить собственный сервер\? + Какой адрес у вашего сервера\? + Какой адрес у вашего сервера\? Это что-то вроде дома для всех ваших данных + Выберите свой сервер + Установить автоматически + Почта не подтверждена, проверьте почтовый ящик + Изменить + Где хранятся ваши переписки + Где будут храниться ваши переписки + Должно быть 8 или более символов + Не удалось подтвердить это устройство + Невозможно открыть эту ссылку: сообщества были заменены пространствами + Имя пользователя / Почта / Телефон + Следуйте инструкциям, отправленным на %s + Забыли пароль + Не получили письмо\? + Вы человек\? + Повторно отправить код + Код отправлен на %s + Подтвердите номер телефона + Сбросить пароль + Новый пароль + Код подтверждения + Номер телефона + Введите номер телефона + Электронная почта + Введите адрес электронной почты + Правила сервера + URL-адрес сервера + С возвращением! + Или + Создать учётную запись + %1$s и %2$s + \ No newline at end of file diff --git a/vector/src/main/res/values-sk/strings.xml b/vector/src/main/res/values-sk/strings.xml index f0cf54953e..cac6187d8a 100644 --- a/vector/src/main/res/values-sk/strings.xml +++ b/vector/src/main/res/values-sk/strings.xml @@ -1239,7 +1239,7 @@ Šifrovanie nie je zapnuté Udržujte ho v bezpečí Toto som nebol ja - Overte toto prihlásenie + Overiť toto zariadenie %d aktívna relácia %d aktívne relácie @@ -2600,8 +2600,8 @@ Zabudnuté heslo Opätovne odoslať e-mail Nedostali ste e-mail\? - Ak chcete potvrdiť svoj e-mail, ťuknite na tlačidlo v e-maile, ktorý sme práve poslali na adresu %s - Pre overenie skontrolujte svoj e-mail. + Postupujte podľa pokynov zaslaných na adresu %s + Overte svoj e-mail Znovu odoslať kód Kód bol odoslaný na %s Potvrďte svoje telefónne číslo @@ -2637,4 +2637,20 @@ Vybrať manuálne Nastaviť automaticky Vyberte veľkosť písma + Automaticky schvaľovať miniaplikácie Element Call a udeliť prístup ku kamere/mikrofónu + Zapnúť skratky na povolenie Element hovorov + Poloha v reálnom čase + Tento QR kód vyzerá chybne. Skúste vykonať overenie inou metódou. + K zašifrovanej histórii správ nebudete mať prístup. Obnovte zálohu zabezpečených správ a overovacie kľúče a začnite odznova. + Nie je možné overiť toto zariadenie + Aká je adresa vášho servera\? + Kde vaše rozhovory žijú + Aktualizácia vašich údajov… + + %1$s a %2$d ďalší + %1$s a %2$d ďalší + %1$s a %2$d ďalších + + %1$s a %2$s + E-mail nie je overený, skontrolujte si schránku \ No newline at end of file diff --git a/vector/src/main/res/values-uk/strings.xml b/vector/src/main/res/values-uk/strings.xml index 45a057dd5f..8300199e8a 100644 --- a/vector/src/main/res/values-uk/strings.xml +++ b/vector/src/main/res/values-uk/strings.xml @@ -1207,7 +1207,7 @@ Використайте чинний сеанс, щоб звірити цей сеанс, таким чином надавши йому доступ до зашифрованих повідомлень. Підтвердьте вашу тотожність, звіривши цей вхід та надавши йому доступ до зашифрованих повідомлень. Звірте новий вхід, що доступається до вашого облікового запису: %1$s - Звірте цей вхід + Звірте цей пристрій Очікування… Новий вхід. Це були ви\? Оновити @@ -1227,7 +1227,7 @@ Ви прийняли запрошення для %1$s. Причина: %2$s %1$s приймає запрошення для %2$s. Причина: %3$s Ви заблокували %1$s. Причина: %2$s - %1$s заблоковано %2$s. Причина: %3$s + %1$s блокує %2$s. Причина: %3$s Ви розблокували %1$s. Причина: %2$s %1$s розблоковує %2$s. Причина: %3$s Ви вилучили %1$s. Причина: %2$s @@ -1436,11 +1436,11 @@ Резервну копію відновлено %s! Не вдалося розшифрувати резервну копію за допомогою цього ключа відновлення: переконайтеся, що ви ввели правильний ключ відновлення. - • Сервери з відповідними літералам IP тепер заблоковано. + • Сервери з відповідними літералам IP тепер заблоковані. • Відповідні сервери %s було вилучено зі списку блокування. - • Відповідні сервери %s тепер заблоковано. - • Сервери, що з відповідними літералам IP заблоковано. - • Відповідні сервери %s заблоковано. + • Відповідні сервери %s відтепер заблоковані. + • Сервери з відповідними літералам IP заблоковані. + • Відповідні сервери %s заблоковані. Неможливо відкрити кімнату, у якій вас заблоковано. Переглянути каталог кімнат Створення кімнати… @@ -2648,8 +2648,8 @@ Забули пароль Не отримали лист\? Повторно надіслати електронний лист - Щоб підтвердити свою електронну адресу, торкніться кнопки в електронному листі, який ми щойно надіслали на адресу %s - Перейдіть до своєї електронної пошти для підтвердження. + Виконайте вказівки надіслані на %s + Підтвердьте свою електронну адресу Повторно надіслати код Код було надіслано на %s Підтвердьте свій номер телефону @@ -2686,4 +2686,21 @@ Вибрати вручну Установити автоматично Виберіть розмір шрифту + Віджети автосхвалення викликів Element і надання доступу до камери/мікрофона + Увімкнути ярлики дозволів на виклик елемента + Місце перебування наживо + Цей QR-код має неправильний вигляд. Спробуйте звірити іншим методом. + Ви не зможете отримати доступ до історії зашифрованих повідомлень. Скиньте захищену резервну копію повідомлень і ключі підтвердження, щоб почати заново. + Не вдалося звірити цей пристрій + Яка адреса вашого сервера\? + Де відбуватимуться ваші розмови + Оновлення ваших даних… + + %1$s і %2$d інший + %1$s і %2$d інші + %1$s і %2$d інших + %1$s і %2$d інших + + %1$s і %2$s + Електронна пошта не підтверджена, перевірте свою поштову скриньку \ No newline at end of file diff --git a/vector/src/main/res/values-vi/strings.xml b/vector/src/main/res/values-vi/strings.xml index dca5645f1d..61b8728ca0 100644 --- a/vector/src/main/res/values-vi/strings.xml +++ b/vector/src/main/res/values-vi/strings.xml @@ -975,7 +975,7 @@ \nVui lòng kiểm tra thiết lập. Thông báo được bật trong thiết lập hệ thống. Thiết lập hệ thống. - Xử lý lỗi thông báo + Thông báo khắc phục lỗi Từ khóa không được chứa \'%s\' Từ khóa không được bắt đầu với \'.\' Thêm mới từ khóa @@ -2274,4 +2274,7 @@ Chế độ ngoại tuyến Sự hiện diện Hiển thị bong bóng tin nhắn + Chọn cách nhận thông báo + Phương thức thông báo + Đặt lại phương thức thông báo \ No newline at end of file diff --git a/vector/src/main/res/values-zh-rCN/strings.xml b/vector/src/main/res/values-zh-rCN/strings.xml index 0fae38bf6e..0549bd84a9 100644 --- a/vector/src/main/res/values-zh-rCN/strings.xml +++ b/vector/src/main/res/values-zh-rCN/strings.xml @@ -90,7 +90,7 @@ 你加入了房间 你离开了房间 你拒绝了邀请 - 你移除了 %1$s + 你踢了 %1$s 你解封了 %1$s 你封禁了 %1$s 你撤回了对 %1$s 的邀请 @@ -108,7 +108,7 @@ 你发送了数据以建立通话。 你接听了通话。 你结束了通话。 - 你已让未来的房间记录对%1$s可见 + 你已让未来的房间历史对%1$s可见 你升级了此房间。 你移除了房间名称 你移除了房间主题 @@ -163,7 +163,7 @@ 你邀请了 %1$s %1$s 邀请了 %2$s 你在此处升级。 - %s 在此处升级。 + %s 是升级后的房间。 你使未来的消息对 %1$s 可见 %1$s 使未来的消息对 %2$s 可见 你离开了房间 @@ -193,7 +193,7 @@ 登出 搜索 发送文件 - 通话结束 + 通话已结束 继续 加入 拒绝 @@ -420,12 +420,12 @@ 黑色主题 通知声音 使用12小时制显示时间戳 - 你确定要删除这个挂件吗? + 确定要从此房间删除此挂件吗? 无法创建挂件。 发送请求失败。 权力级别必须是正整数。 你不在这个房间。 - 你没有在当前房间中执行此操作的权限。 + 你没权限在当前房间执行此操作。 请求中缺失 room_id。 请求中缺失 user_id。 房间 %s 不可见。 @@ -470,14 +470,14 @@ %d个成员 - %d 条未读消息 + %d条未读的已通知消息 成员 %d 个房间 - 已启用 %d 个挂件 + %d个启用的挂件 主页 房间 @@ -504,7 +504,7 @@ 你目前没有启用任何贴纸包。 \n \n要添加一些吗? - 缺少所需的参数。 + 缺少一个必需参数。 要想继续使用主服务器 %1$s 你必须阅读并同意其服务条款。 现在阅读 下载 @@ -587,7 +587,7 @@ FCM令牌已成功注册至主服务器。 未能将FCM令牌注册到主服务器: \n %1$s - 调用系统相机应用而非使用 ${app_name} 内置的相机界面。 + 启动系统相机而非自定义的相机屏幕。 开机时启动 启用开机时启动 检查后台限制 @@ -760,12 +760,12 @@ 播放快门声 标记为已读 - %1$s: %2$d 条消息 + %1$s:%2$d条消息 %d 条通知 - 新活动 + 新事件 房间 新消息 新邀请 @@ -812,11 +812,11 @@ 无法预览此房间 房间 创建 - 房间名称 + 名称 Matrix SDK 版本 通用 - 选项 - 隐私安全 + 偏好 + 安全与隐私 推送规则 尚未定义任何推送规则 没有已注册的推送通道 @@ -893,7 +893,7 @@ 重置安全备份 在此设备上设置 通过在你的服务器上备份加密密钥,防止失去对加密信息和数据的访问。 - 为你已有的备份生成新的安全密钥或设置新的安全口令。 + 为你已有的备份生成新的安全密钥或设置新的安全短语。 这将替换你的当前密钥或短语。 发现 管理你的发现设置。 @@ -905,16 +905,16 @@ %d 个封禁用户 成功导出密钥 - %1$s: %2$s - %1$s: %2$s %3$s + %1$s:%2$s + %1$s:%2$s %3$s 查看 活动挂件 挂件 载入挂件 此挂件添加者: - 使用它会设置 cookie 并与 %s 分享数据: - 使用它会与 %s 分享数据: - 无法载入挂件。 + 使用它可能会设置cookie并与%s分享数据: + 使用它可能会与%s分享数据: + 载入挂件失败。 \n%s 重载挂件 在浏览器中打开 @@ -956,7 +956,7 @@ 任何人都可以加入此房间 获取信任信息时发生错误 获取密钥备份数据时发生错误 - 从文件 \"%1$s\" 导入端对端密钥。 + 从文件“%1$s”导入端到端密钥。 其他第三方通知 你已经在查看此房间! 注册令牌 @@ -1319,7 +1319,7 @@ %d 个活动会话 - 验证此登录 + 验证此设备 使用现有会话来验证此会话,并授予其访问加密消息的权限。 验证 已验证 @@ -1335,7 +1335,7 @@ 重置密钥 二维码 快要完成了!%s 显示对勾了吗? - 使 + 到服务器的连接已丢失 飞行模式已打开 @@ -1350,7 +1350,7 @@ 发送原始尺寸图片 确认移除 - 你确实想要移除(删除)此事件吗?注意如果你删除房间名或话题更改,可以撤销更改。 + 你确定要移除(删除)此事件吗?注意,如果删除房间名称或话题的更改,更改会被撤销。 附加理由 编辑理由 事件被用户删除,理由:%1$s @@ -1379,7 +1379,7 @@ 消息密钥 输入你的 %s 以继续。 不要使用你的账户密码。 - 输入只有你知道的安全口令,用于保护服务器上的秘密。 + 输入只有你知道的安全短语,用于保护服务器上的秘密。 这可能会花费数秒,请耐心等待。 设置恢复。 完成了! @@ -1517,20 +1517,20 @@ 设置 使用安全密钥 生成安全密钥存储在安全的地方如密码管理器或保险箱。 - 使用安全口令 - 输入仅有你知道的安全口令,生成备份用的密钥。 + 使用安全短语 + 输入仅有你知道的秘密短语,生成备份用的密钥。 保存你的安全密钥 将你的安全密钥存储在安全的地方,例如密码管理器或保险箱。 - 设置安全口令 - 输入只有你知道的安全口令,用于保护你的服务器上的秘密。 - 安全口令 - 再次输入你的安全口令以确认。 + 设置安全短语 + 输入只有你知道的安全短语,用于保护你的服务器上的秘密。 + 安全短语 + 再次输入你的安全短语以确认。 房间名称 主题 你已成功更改房间设置 你无法访问此消息 正在等待此消息,可能会花费一些时间 - 由于端对端加密,你可能需要等待某人的消息到达因为加密密钥未正确发送给你。 + 由于端到端加密,你可能需要等待某人的消息到达,因为加密密钥未正确发送给你。 你无法访问此消息因为你已屏蔽此发送者 你无法访问此消息,因为你的会话不被发送者信任 你无法访问此消息因为发送者有意不发送密钥 @@ -1540,7 +1540,7 @@ 明白了 了解更多 将恢复密钥保存到 - 正在获取你的联系人… + 正在获取你的联系人…… 你的通讯录是空的 通讯录 撤销邀请 @@ -1548,7 +1548,7 @@ 被 %1$s 封禁 解封用户失败 推送通知已禁用 - 查看你的设置以启用推送通知 + 检查你的设置以启用推送通知 选择 PIN 以确保安全 确认 PIN 验证 PIN 失败,请输入新的。 @@ -1583,7 +1583,7 @@ 错误代码,剩余 %d 次尝试 - 注意!登出前最后一次尝试! + 警告!登出前最后一次尝试! 错误次数过多,你已被登出 此电话号码已定义。 你的账户尚未添加电话号码 @@ -1613,8 +1613,8 @@ 每次打开 ${app_name} 都要求 PIN 码。 在 2 分钟未使用 ${app_name} 后要求 PIN 码。 2 分钟后要求 PIN - 仅在一个简单通知中显示未读消息内容数量。 - 显示细节如房间名称和消息内容。 + 仅在一个简单的通知中显示未读消息的数量。 + 显示详情,如房间名称和消息内容。 在通知中显示内容 PIN 码是解锁 ${app_name} 的唯一方式。 启用设备特定的生物特征识别,如指纹和面部识别。 @@ -1775,7 +1775,7 @@ • 匹配 %s 的服务器现已被屏蔽。 • 已封禁匹配 IP 地址的服务器。 • 已允许匹配 IP 地址的服务器。 - • 匹配 %s 的服务器被屏蔽。 + • 匹配 %s 的服务器已被屏蔽。 • 已允许匹配 %s 的服务器。 已勾选 已选中 @@ -1901,7 +1901,7 @@ 邀请至 %s 分享链接 通过电子邮件进行邀请 - 此刻仅有你自己。%s 与他人一道会更好。 + 此刻只有你。%s与他人一道会更好。 邀请至 %s 邀请人们 邀请人们加入你的空间 @@ -1925,7 +1925,7 @@ 我和伙伴 用于整理你房间的私有空间 仅我 - 确保对的人可以访问 %s。 + 确保正确的人可以访问%s。 你与谁一同工作? 要加入现有空间,你需要获得邀请。 你可以稍后更改 @@ -1974,7 +1974,7 @@ 你正在使用空间的测试版。你的反馈将有助于改善下一版本。我们将会记录你的平台和用户名以帮助我们尽我们所能多发挥你的反馈的作用。 反馈 空间反馈 - 离开当前的回忆并切换至其他会议? + 离开当前会议并切换至另一个? 抱歉,在尝试加入会议时发生了错误 所有在此空间中的人都可以找到并加入它。仅有此房间的管理员可以将其添加到空间中。 仅空间成员 @@ -2127,7 +2127,7 @@ 视频通话被拒绝 语音通话被拒绝 视频通话已结束 • %1$s - 语音通话结束了 • %1$s + 语音通话已结束 • %1$s 视频来电 语音来电 你拒绝了此通话 @@ -2143,7 +2143,7 @@ 添加新关键词 你的关键词 - 仅提及 & 关键词 + 仅提及和关键词 正结束通话… 无应答 你呼叫的用户正忙。 @@ -2224,7 +2224,7 @@ 投票 向 %s 发送电子邮件和电话号码 您的联系人是私密的。 要从您的联系人中发现用户,我们需要您的许可才能将联系信息发送到您的身份服务器。 - 已退出此会话! + 已登出此会话! 已离开此房间! 你同意发送此信息吗? 要发现现有的联系人,您需要将联系人信息(电子邮件和电话号码)发送到您的身份服务器。出乎隐私考量,我们会在发送前对您的数据进行散列处理。 @@ -2232,7 +2232,7 @@ 您确定要删除此投票吗?一旦移除,就无法恢复。 删除投票 投票已结束 - 已投票 + 投票 结束投票 这将使人们无法再投票,并将显示投票的最终结果。 结束此投票? @@ -2274,7 +2274,7 @@ 修改服务器 %d 的 ACLs - Thread帮助你的对话不离题且易于跟踪。 + 消息列帮助你的对话不离题且易于跟踪。 显示当前房间的所有子区 所有子区 筛选器 @@ -2310,7 +2310,7 @@ 缩放到当前位置 地图上选定位置的图钉 无投票 - 检查你的电子邮件以验证。 + 验证你的电子邮件 %d条消息已移除 @@ -2374,9 +2374,9 @@ 位置 分享位置 结果仅在你结束投票后展示 - 已关闭的投票 + 封闭式投票 投票者一投票就能看到结果 - 开启投票 + 开放式投票 投票类型 编辑投票 结果将在投票结束时可见 @@ -2386,7 +2386,7 @@ 添加用户资料图片 重发电子邮件 没有收到电子邮件? - 要确认你的电子邮件地址,点击我们刚刚寄到%s的电子邮件里的按钮 + 跟随发送到%s的说明 重新发送验证码 代码已发送到%s 确认你的电话号码 @@ -2430,7 +2430,7 @@ 安全传送消息。 向主服务器注册端点token失败: \n%1$s - Threads帮助保持你的对话不离题且易于跟踪。%s启用thread会刷新应用。这对一些账户可能需要更长时间。 + Threads帮助保持你的对话不离题且易于跟踪。%s启用消息列会刷新应用。这对一些账户可能需要更长时间。 重启应用以使更改生效。 启用LaTeX数学 (%1$s) @@ -2442,7 +2442,7 @@ %1$s、%2$s、%3$s 显示最新用户信息 注意:应用将重启 - 启用thread消息 + 启用消息列消息 当无法解密的错误出现时,你的系统会自动发送日志 自动报告解密错误。 一些结果可能被隐藏,因为它们是私有的,你需要它们的邀请。 @@ -2495,11 +2495,11 @@ BETA 重设通知方式 资料标签: - 你已经在查看这个thread了! + 你已经在查看这个消息列了! 出发 - 在thread中回复 + 在消息列中回复 备份具有来自该用户的有效签名。 - 命令“%s”可被识别但在threads中不被支持。 + 命令“%s”可被识别但在消息列中不被支持。 使用系统默认值 手动选择 自动设置 @@ -2516,13 +2516,13 @@ 自动播放动画图片 端点成功注册到主服务器。 端点注册 - 你的主服务器当前不支持threads,所以此功能可能不可靠。Some threaded messages may not be reliably available. %s你仍要启用threads吗? + 你的主服务器当前不支持消息列,所以此功能可能不可靠。Some threaded messages may not be reliably available. %s你仍要启用消息列吗? Threads接近Beta了 🎉 - 来自thread + 来自消息列 实用提示:长按消息并使用“%s”。 - 使用threads来保持讨论的条理性 - 显示你参与的所有threads - 我的threads + 使用消息列来保持讨论的条理性 + 显示你参与的所有消息列 + 我的消息列s 加密被错误地配置了,所以你无法发送消息。点击以打开设置。 加密被错误地配置了,所以你无法发送消息。请联系管理员将加密还原到有效的状态。 %1$s、%2$s与其他人 @@ -2539,4 +2539,18 @@ 小时 - 一些用户已被取消忽略 初始同步请求 + + %1$s与其他%2$d人 + + 正在更新你的数据…… + 自动批准Element通话组件,授予相机/麦克风权限 + 启用Element通话权限捷径 + 实时位置 + 这个QR码看起来不正常。请尝试用另一个方法验证。 + 你无法访问加密消息历史。重置你的安全消息备份和验证密钥以重新开始。 + 无法验证此设备 + 你的服务器地址是什么? + 你的对话发生的地方 + %1$s和%2$s + 电子邮件未确认,检查你的收件箱 \ No newline at end of file diff --git a/vector/src/main/res/values-zh-rTW/strings.xml b/vector/src/main/res/values-zh-rTW/strings.xml index ee96ee6063..effb6e7410 100644 --- a/vector/src/main/res/values-zh-rTW/strings.xml +++ b/vector/src/main/res/values-zh-rTW/strings.xml @@ -1273,7 +1273,7 @@ %d 活躍的工作階段 - 驗證此登入 + 驗證此裝置 使用既有的工作階段來驗證這個,讓它可以存取已加密的訊息。 驗證 已驗證 @@ -1304,7 +1304,7 @@ 使用原始大小傳送圖片 確認移除 - 您確定您想要移除(刪除)此活動嗎?注意,如果您刪除聊天室名稱或變更主題,則可能會撤銷變更。 + 您確定您想要移除(刪除)此活動嗎?注意,如果您刪除聊天室名稱或主題的變更,變更會被撤銷。 包含理由 修改原因 被使用者刪除的活動,理由:%1$s @@ -2293,9 +2293,9 @@ 位置 分享位置 結果僅在您結束投票後顯示 - 已關閉投票 + 封闭式投票 投票者在投票後可以立刻看到投票結果 - 開啟投票 + 開放式投票 投票類型 編輯投票 沒有投票 @@ -2486,7 +2486,7 @@ MSC3061:為過去的訊息分享聊天室金鑰 傳送您的第一則訊息以邀請 %s 來聊天 此聊天中的訊息將會是端到端加密。 - + 出發 已移除 %d 則訊息 @@ -2501,8 +2501,8 @@ 忘記密碼 重新傳送電子郵件 沒有收到電子郵件? - 要確認您的電子郵件,請點擊我們剛剛寄給 %s 的電子郵件中的按鈕 - 檢查您的電子郵件以驗證。 + 請遵循傳送至 %s 中的步驟 + 驗證您的電子郵件 重新傳送驗證碼 驗證碼已傳送至 %s 確認您的電話號碼 @@ -2539,4 +2539,18 @@ 手動選擇 自動設定 選擇字型大小 + 自動批准 Element Call 小工具並授予相機/麥克風存取權限 + 啟用 Element Call 權限捷徑 + 即時位置 + 這個 QR code 的格式似乎不正確。請嘗試使用其他方法進行驗證。 + 您將無法存取已加密的訊息歷史紀錄。重設您的安全訊息備份與驗證金鑰以重新開始。 + 無法驗證此裝置 + 您的伺服器地址是? + 您的對話所在位置 + 正在更新您的資料…… + + %1$s 與 %2$d 個其他人 + + %1$s 與 %2$s + 電子郵件未驗證,請檢查您的收件匣 \ No newline at end of file diff --git a/vector/src/test/java/im/vector/app/features/onboarding/OnboardingViewModelTest.kt b/vector/src/test/java/im/vector/app/features/onboarding/OnboardingViewModelTest.kt index b8a0be9529..b505d05944 100644 --- a/vector/src/test/java/im/vector/app/features/onboarding/OnboardingViewModelTest.kt +++ b/vector/src/test/java/im/vector/app/features/onboarding/OnboardingViewModelTest.kt @@ -478,6 +478,20 @@ class OnboardingViewModelTest { .finish() } + @Test + fun `given available username throws, when a register username is entered, then emits error`() = runTest { + viewModelWith(initialRegistrationState(A_HOMESERVER_URL)) + fakeAuthenticationService.givenRegistrationWizard(FakeRegistrationWizard().also { it.givenUserNameIsAvailableThrows(A_USERNAME, AN_ERROR) }) + val test = viewModel.test() + + viewModel.handle(OnboardingAction.UserNameEnteredAction.Registration(A_USERNAME)) + + test + .assertStates(initialState) + .assertEvents(OnboardingViewEvents.Failure(AN_ERROR)) + .finish() + } + @Test fun `given available username, when a register username is entered, then emits available registration state`() = runTest { viewModelWith(initialRegistrationState(A_HOMESERVER_URL)) diff --git a/vector/src/test/java/im/vector/app/features/pin/lockscreen/crypto/migrations/LockScreenTestMigratorTests.kt b/vector/src/test/java/im/vector/app/features/pin/lockscreen/crypto/migrations/LockScreenKeysMigratorTests.kt similarity index 94% rename from vector/src/test/java/im/vector/app/features/pin/lockscreen/crypto/migrations/LockScreenTestMigratorTests.kt rename to vector/src/test/java/im/vector/app/features/pin/lockscreen/crypto/migrations/LockScreenKeysMigratorTests.kt index 73f71dbf2b..588fb12382 100644 --- a/vector/src/test/java/im/vector/app/features/pin/lockscreen/crypto/migrations/LockScreenTestMigratorTests.kt +++ b/vector/src/test/java/im/vector/app/features/pin/lockscreen/crypto/migrations/LockScreenKeysMigratorTests.kt @@ -26,7 +26,7 @@ import io.mockk.verify import kotlinx.coroutines.runBlocking import org.junit.Test -class LockScreenTestMigratorTests { +class LockScreenKeysMigratorTests { private val legacyPinCodeMigrator = mockk(relaxed = true) private val missingSystemKeyMigrator = mockk(relaxed = true) @@ -42,7 +42,7 @@ class LockScreenTestMigratorTests { runBlocking { migrator.migrateIfNeeded() } coVerify(exactly = 0) { legacyPinCodeMigrator.migrate() } - verify(exactly = 0) { missingSystemKeyMigrator.migrate() } + verify(exactly = 0) { missingSystemKeyMigrator.migrateIfNeeded() } // When migration is needed every { legacyPinCodeMigrator.isMigrationNeeded() } returns true @@ -50,7 +50,7 @@ class LockScreenTestMigratorTests { runBlocking { migrator.migrateIfNeeded() } coVerify { legacyPinCodeMigrator.migrate() } - verify { missingSystemKeyMigrator.migrate() } + verify { missingSystemKeyMigrator.migrateIfNeeded() } } @Test diff --git a/vector/src/test/java/im/vector/app/features/pin/lockscreen/crypto/migrations/MissingSystemKeyMigratorTests.kt b/vector/src/test/java/im/vector/app/features/pin/lockscreen/crypto/migrations/MissingSystemKeyMigratorTests.kt index 3098187962..e211fc8a0e 100644 --- a/vector/src/test/java/im/vector/app/features/pin/lockscreen/crypto/migrations/MissingSystemKeyMigratorTests.kt +++ b/vector/src/test/java/im/vector/app/features/pin/lockscreen/crypto/migrations/MissingSystemKeyMigratorTests.kt @@ -44,7 +44,7 @@ class MissingSystemKeyMigratorTests { every { keyStoreCryptoFactory.provide(any(), any()) } returns keyStoreCryptoMock every { vectorPreferences.useBiometricsToUnlock() } returns true - missingSystemKeyMigrator.migrate() + missingSystemKeyMigrator.migrateIfNeeded() verify { keyStoreCryptoMock.ensureKey() } } @@ -57,7 +57,7 @@ class MissingSystemKeyMigratorTests { every { keyStoreCryptoFactory.provide(any(), any()) } returns keyStoreCryptoMock every { vectorPreferences.useBiometricsToUnlock() } returns true - invoking { missingSystemKeyMigrator.migrate() } shouldNotThrow KeyPermanentlyInvalidatedException::class + invoking { missingSystemKeyMigrator.migrateIfNeeded() } shouldNotThrow KeyPermanentlyInvalidatedException::class } @Test @@ -68,7 +68,7 @@ class MissingSystemKeyMigratorTests { every { keyStoreCryptoFactory.provide(any(), any()) } returns keyStoreCryptoMock every { vectorPreferences.useBiometricsToUnlock() } returns true - invoking { missingSystemKeyMigrator.migrate() } shouldNotThrow UserNotAuthenticatedException::class + invoking { missingSystemKeyMigrator.migrateIfNeeded() } shouldNotThrow UserNotAuthenticatedException::class } @Test @@ -79,7 +79,7 @@ class MissingSystemKeyMigratorTests { every { keyStoreCryptoFactory.provide(any(), any()) } returns keyStoreCryptoMock every { vectorPreferences.useBiometricsToUnlock() } returns false - missingSystemKeyMigrator.migrate() + missingSystemKeyMigrator.migrateIfNeeded() verify(exactly = 0) { keyStoreCryptoMock.ensureKey() } } @@ -93,7 +93,7 @@ class MissingSystemKeyMigratorTests { every { keyStoreCryptoFactory.provide(any(), any()) } returns keyStoreCryptoMock every { vectorPreferences.useBiometricsToUnlock() } returns false - missingSystemKeyMigrator.migrate() + missingSystemKeyMigrator.migrateIfNeeded() verify(exactly = 0) { keyStoreCryptoMock.ensureKey() } } diff --git a/vector/src/test/java/im/vector/app/features/pin/lockscreen/crypto/migrations/SystemKeyV1MigratorTests.kt b/vector/src/test/java/im/vector/app/features/pin/lockscreen/crypto/migrations/SystemKeyV1MigratorTests.kt index 5cbb828f71..825b251f3e 100644 --- a/vector/src/test/java/im/vector/app/features/pin/lockscreen/crypto/migrations/SystemKeyV1MigratorTests.kt +++ b/vector/src/test/java/im/vector/app/features/pin/lockscreen/crypto/migrations/SystemKeyV1MigratorTests.kt @@ -18,6 +18,7 @@ package im.vector.app.features.pin.lockscreen.crypto.migrations import android.security.keystore.UserNotAuthenticatedException import im.vector.app.features.pin.lockscreen.crypto.KeyStoreCrypto +import im.vector.app.features.settings.VectorPreferences import io.mockk.every import io.mockk.mockk import io.mockk.verify @@ -31,7 +32,8 @@ class SystemKeyV1MigratorTests { private val keyStoreCryptoFactory = mockk() private val keyStore = mockk(relaxed = true) - private val systemKeyV1Migrator = SystemKeyV1Migrator("vector.system_new", keyStore, keyStoreCryptoFactory) + private val vectorPreferences = mockk(relaxed = true) + private val systemKeyV1Migrator = SystemKeyV1Migrator("vector.system_new", keyStore, keyStoreCryptoFactory, vectorPreferences) @Test fun isMigrationNeededReturnsTrueIfV1KeyExists() { diff --git a/vector/src/test/java/im/vector/app/features/pin/lockscreen/fragment/LockScreenViewModelTests.kt b/vector/src/test/java/im/vector/app/features/pin/lockscreen/fragment/LockScreenViewModelTests.kt index 9e80bb490b..18dfdf9145 100644 --- a/vector/src/test/java/im/vector/app/features/pin/lockscreen/fragment/LockScreenViewModelTests.kt +++ b/vector/src/test/java/im/vector/app/features/pin/lockscreen/fragment/LockScreenViewModelTests.kt @@ -16,6 +16,7 @@ package im.vector.app.features.pin.lockscreen.fragment +import android.app.KeyguardManager import android.os.Build import android.security.keystore.KeyPermanentlyInvalidatedException import androidx.fragment.app.FragmentActivity @@ -23,7 +24,6 @@ import com.airbnb.mvrx.test.MvRxTestRule import com.airbnb.mvrx.withState import im.vector.app.features.pin.lockscreen.biometrics.BiometricHelper import im.vector.app.features.pin.lockscreen.configuration.LockScreenConfiguration -import im.vector.app.features.pin.lockscreen.configuration.LockScreenConfiguratorProvider import im.vector.app.features.pin.lockscreen.configuration.LockScreenMode import im.vector.app.features.pin.lockscreen.crypto.LockScreenKeysMigrator import im.vector.app.features.pin.lockscreen.pincode.PinCodeHelper @@ -42,6 +42,7 @@ import io.mockk.every import io.mockk.mockk import io.mockk.verify import kotlinx.coroutines.flow.flowOf +import kotlinx.coroutines.test.advanceUntilIdle import kotlinx.coroutines.test.runTest import org.amshove.kluent.shouldBeEqualTo import org.amshove.kluent.shouldBeFalse @@ -57,7 +58,15 @@ class LockScreenViewModelTests { private val pinCodeHelper = mockk(relaxed = true) private val biometricHelper = mockk(relaxed = true) + private val biometricHelperFactory = object : BiometricHelper.BiometricHelperFactory { + override fun create(configuration: LockScreenConfiguration): BiometricHelper { + return biometricHelper + } + } private val keysMigrator = mockk(relaxed = true) + private val keyguardManager = mockk(relaxed = true) { + every { isDeviceLocked } returns false + } private val versionProvider = TestBuildVersionSdkIntProvider() @Before @@ -68,19 +77,36 @@ class LockScreenViewModelTests { @Test fun `init migrates old keys to new ones if needed`() { val initialState = createViewState() - val configProvider = LockScreenConfiguratorProvider(createDefaultConfiguration()) - LockScreenViewModel(initialState, pinCodeHelper, biometricHelper, keysMigrator, configProvider, versionProvider) + LockScreenViewModel(initialState, pinCodeHelper, biometricHelperFactory, keysMigrator, versionProvider, keyguardManager) coVerify { keysMigrator.migrateIfNeeded() } } + @Test + fun `init updates the initial state with biometric info`() = runTest { + every { biometricHelper.isSystemAuthEnabledAndValid } returns true + val initialState = createViewState() + val viewModel = LockScreenViewModel(initialState, pinCodeHelper, biometricHelperFactory, keysMigrator, versionProvider, keyguardManager) + advanceUntilIdle() + val newState = viewModel.awaitState() + newState shouldNotBeEqualTo initialState + } + + @Test + fun `Updating the initial state with biometric info waits until device is unlocked on Android 12+`() = runTest { + val initialState = createViewState() + versionProvider.value = Build.VERSION_CODES.S + LockScreenViewModel(initialState, pinCodeHelper, biometricHelperFactory, keysMigrator, versionProvider, keyguardManager) + advanceUntilIdle() + verify { keyguardManager.isDeviceLocked } + } + @Test fun `when ViewModel is instantiated initialState is updated with biometric info`() { val initialState = createViewState() - val configProvider = LockScreenConfiguratorProvider(createDefaultConfiguration()) // This should set canUseBiometricAuth to true every { biometricHelper.isSystemAuthEnabledAndValid } returns true - val viewModel = LockScreenViewModel(initialState, pinCodeHelper, biometricHelper, keysMigrator, configProvider, versionProvider) + val viewModel = LockScreenViewModel(initialState, pinCodeHelper, biometricHelperFactory, keysMigrator, versionProvider, keyguardManager) val newState = withState(viewModel) { it } initialState shouldNotBeEqualTo newState } @@ -88,8 +114,7 @@ class LockScreenViewModelTests { @Test fun `when onPinCodeEntered is called in VERIFY mode, the code is verified and the result is emitted as a ViewEvent`() = runTest { val initialState = createViewState() - val configProvider = LockScreenConfiguratorProvider(createDefaultConfiguration()) - val viewModel = LockScreenViewModel(initialState, pinCodeHelper, biometricHelper, keysMigrator, configProvider, versionProvider) + val viewModel = LockScreenViewModel(initialState, pinCodeHelper, biometricHelperFactory, keysMigrator, versionProvider, keyguardManager) coEvery { pinCodeHelper.verifyPinCode(any()) } returns true val events = viewModel.test().viewEvents @@ -113,8 +138,7 @@ class LockScreenViewModelTests { fun `when onPinCodeEntered is called in CREATE mode with no confirmation needed it creates the pin code`() = runTest { val configuration = createDefaultConfiguration(mode = LockScreenMode.CREATE, needsNewCodeValidation = false) val initialState = createViewState(lockScreenConfiguration = configuration) - val configProvider = LockScreenConfiguratorProvider(configuration) - val viewModel = LockScreenViewModel(initialState, pinCodeHelper, biometricHelper, keysMigrator, configProvider, versionProvider) + val viewModel = LockScreenViewModel(initialState, pinCodeHelper, biometricHelperFactory, keysMigrator, versionProvider, keyguardManager) val events = viewModel.test().viewEvents events.assertNoValues() @@ -128,9 +152,8 @@ class LockScreenViewModelTests { @Test fun `when onPinCodeEntered is called twice in CREATE mode with confirmation needed it verifies and creates the pin code`() = runTest { val configuration = createDefaultConfiguration(mode = LockScreenMode.CREATE, needsNewCodeValidation = true) - val configProvider = LockScreenConfiguratorProvider(configuration) val initialState = createViewState(lockScreenConfiguration = configuration) - val viewModel = LockScreenViewModel(initialState, pinCodeHelper, biometricHelper, keysMigrator, configProvider, versionProvider) + val viewModel = LockScreenViewModel(initialState, pinCodeHelper, biometricHelperFactory, keysMigrator, versionProvider, keyguardManager) val events = viewModel.test().viewEvents events.assertNoValues() @@ -149,8 +172,7 @@ class LockScreenViewModelTests { fun `when onPinCodeEntered is called in CREATE mode with incorrect confirmation it clears the pin code`() = runTest { val configuration = createDefaultConfiguration(mode = LockScreenMode.CREATE, needsNewCodeValidation = true) val initialState = createViewState(lockScreenConfiguration = configuration) - val configProvider = LockScreenConfiguratorProvider(configuration) - val viewModel = LockScreenViewModel(initialState, pinCodeHelper, biometricHelper, keysMigrator, configProvider, versionProvider) + val viewModel = LockScreenViewModel(initialState, pinCodeHelper, biometricHelperFactory, keysMigrator, versionProvider, keyguardManager) val events = viewModel.test().viewEvents events.assertNoValues() @@ -170,8 +192,7 @@ class LockScreenViewModelTests { @Test fun `onPinCodeEntered handles exceptions`() = runTest { val initialState = createViewState() - val configProvider = LockScreenConfiguratorProvider(createDefaultConfiguration()) - val viewModel = LockScreenViewModel(initialState, pinCodeHelper, biometricHelper, keysMigrator, configProvider, versionProvider) + val viewModel = LockScreenViewModel(initialState, pinCodeHelper, biometricHelperFactory, keysMigrator, versionProvider, keyguardManager) val exception = IllegalStateException("Something went wrong") coEvery { pinCodeHelper.verifyPinCode(any()) } throws exception @@ -187,39 +208,34 @@ class LockScreenViewModelTests { fun `when showBiometricPrompt catches a KeyPermanentlyInvalidatedException it disables biometric authentication`() = runTest { versionProvider.value = Build.VERSION_CODES.M - every { biometricHelper.isSystemAuthEnabledAndValid } returns true - every { biometricHelper.isSystemKeyValid } returns true + every { biometricHelper.isSystemKeyValid } returns false val exception = KeyPermanentlyInvalidatedException() coEvery { biometricHelper.authenticate(any()) } throws exception - coEvery { biometricHelper.disableAuthentication() } coAnswers { - every { biometricHelper.isSystemAuthEnabledAndValid } returns false - } val configuration = createDefaultConfiguration(mode = LockScreenMode.VERIFY, needsNewCodeValidation = true, isBiometricsEnabled = true) - val configProvider = LockScreenConfiguratorProvider(configuration) val initialState = createViewState( canUseBiometricAuth = true, isBiometricKeyInvalidated = false, lockScreenConfiguration = configuration ) - val viewModel = LockScreenViewModel(initialState, pinCodeHelper, biometricHelper, keysMigrator, configProvider, versionProvider) + val viewModel = LockScreenViewModel(initialState, pinCodeHelper, biometricHelperFactory, keysMigrator, versionProvider, keyguardManager) val events = viewModel.test().viewEvents events.assertNoValues() viewModel.handle(LockScreenAction.ShowBiometricPrompt(mockk())) - events.assertValues(LockScreenViewEvent.AuthError(AuthMethod.BIOMETRICS, exception)) + events.assertValues(LockScreenViewEvent.ShowBiometricKeyInvalidatedMessage) verify { biometricHelper.disableAuthentication() } // System key was deleted, biometric auth should be disabled + every { biometricHelper.isSystemAuthEnabledAndValid } returns false val newState = viewModel.awaitState() newState.canUseBiometricAuth.shouldBeFalse() } @Test fun `when showBiometricPrompt receives an event it propagates it as a ViewEvent`() = runTest { - val configProvider = LockScreenConfiguratorProvider(createDefaultConfiguration()) - val viewModel = LockScreenViewModel(createViewState(), pinCodeHelper, biometricHelper, keysMigrator, configProvider, versionProvider) + val viewModel = LockScreenViewModel(createViewState(), pinCodeHelper, biometricHelperFactory, keysMigrator, versionProvider, keyguardManager) coEvery { biometricHelper.authenticate(any()) } returns flowOf(false, true) val events = viewModel.test().viewEvents @@ -232,8 +248,7 @@ class LockScreenViewModelTests { @Test fun `showBiometricPrompt handles exceptions`() = runTest { - val configProvider = LockScreenConfiguratorProvider(createDefaultConfiguration()) - val viewModel = LockScreenViewModel(createViewState(), pinCodeHelper, biometricHelper, keysMigrator, configProvider, versionProvider) + val viewModel = LockScreenViewModel(createViewState(), pinCodeHelper, biometricHelperFactory, keysMigrator, versionProvider, keyguardManager) val exception = IllegalStateException("Something went wrong") coEvery { biometricHelper.authenticate(any()) } throws exception diff --git a/vector/src/test/java/im/vector/app/test/fakes/FakeRegistrationWizard.kt b/vector/src/test/java/im/vector/app/test/fakes/FakeRegistrationWizard.kt index 4f0b1fe083..6cacd6c1e5 100644 --- a/vector/src/test/java/im/vector/app/test/fakes/FakeRegistrationWizard.kt +++ b/vector/src/test/java/im/vector/app/test/fakes/FakeRegistrationWizard.kt @@ -57,6 +57,10 @@ class FakeRegistrationWizard : RegistrationWizard by mockk(relaxed = false) { coEvery { registrationAvailable(userName) } returns RegistrationAvailability.Available } + fun givenUserNameIsAvailableThrows(userName: String, cause: Throwable) { + coEvery { registrationAvailable(userName) } throws cause + } + fun givenUserNameIsUnavailable(userName: String, failure: Failure.ServerError) { coEvery { registrationAvailable(userName) } returns RegistrationAvailability.NotAvailable(failure) }