From 91b0465caaaeb1ef0cfaf46f35707fb2a98b0c6b Mon Sep 17 00:00:00 2001 From: Fork Liang Date: Mon, 1 Aug 2022 03:41:02 +0000 Subject: [PATCH 01/32] Translated using Weblate (Chinese (Simplified)) Currently translated at 100.0% (2327 of 2327 strings) Translation: Element Android/Element Android App Translate-URL: https://translate.element.io/projects/element-android/element-app/zh_Hans/ --- vector/src/main/res/values-zh-rCN/strings.xml | 15 +++++++++------ 1 file changed, 9 insertions(+), 6 deletions(-) diff --git a/vector/src/main/res/values-zh-rCN/strings.xml b/vector/src/main/res/values-zh-rCN/strings.xml index 0fae38bf6e..ab88307fc0 100644 --- a/vector/src/main/res/values-zh-rCN/strings.xml +++ b/vector/src/main/res/values-zh-rCN/strings.xml @@ -6,7 +6,7 @@ %1$s 加入了房间 %1$s 离开了房间 %1$s 拒绝了邀请 - %1$s 移除了 %2$s + %1$s 踢了 %2$s %1$s 解封了 %2$s %1$s 封禁了 %2$s %1$s 更换了他们的头像 @@ -33,7 +33,7 @@ 电子邮箱地址 手机号码 %1$s 撤回了对 %2$s 的邀请 - %1$s 让未来的房间历史记录对 %2$s 可见 + %1$s 让未来的房间聊天记录对 %2$s 可见 %1$s 向 %2$s 发送了加入房间的邀请 %1$s 接受了 %2$s 的邀请 空房间 @@ -90,7 +90,7 @@ 你加入了房间 你离开了房间 你拒绝了邀请 - 你移除了 %1$s + 你踢了 %1$s 你解封了 %1$s 你封禁了 %1$s 你撤回了对 %1$s 的邀请 @@ -163,7 +163,7 @@ 你邀请了 %1$s %1$s 邀请了 %2$s 你在此处升级。 - %s 在此处升级。 + %s 是升级后的房间。 你使未来的消息对 %1$s 可见 %1$s 使未来的消息对 %2$s 可见 你离开了房间 @@ -1775,7 +1775,7 @@ • 匹配 %s 的服务器现已被屏蔽。 • 已封禁匹配 IP 地址的服务器。 • 已允许匹配 IP 地址的服务器。 - • 匹配 %s 的服务器被屏蔽。 + • 匹配 %s 的服务器已被屏蔽。 • 已允许匹配 %s 的服务器。 已勾选 已选中 @@ -2232,7 +2232,7 @@ 您确定要删除此投票吗?一旦移除,就无法恢复。 删除投票 投票已结束 - 已投票 + 投票 结束投票 这将使人们无法再投票,并将显示投票的最终结果。 结束此投票? @@ -2539,4 +2539,7 @@ 小时 - 一些用户已被取消忽略 初始同步请求 + + %1$s 与 %2$d 其他人 + \ No newline at end of file From 6671a100adb214ab160edd45139a5b5449599f7a Mon Sep 17 00:00:00 2001 From: phardyle Date: Sun, 31 Jul 2022 18:30:51 +0000 Subject: [PATCH 02/32] Translated using Weblate (Chinese (Simplified)) Currently translated at 100.0% (2327 of 2327 strings) Translation: Element Android/Element Android App Translate-URL: https://translate.element.io/projects/element-android/element-app/zh_Hans/ --- vector/src/main/res/values-zh-rCN/strings.xml | 89 +++++++++++-------- 1 file changed, 50 insertions(+), 39 deletions(-) diff --git a/vector/src/main/res/values-zh-rCN/strings.xml b/vector/src/main/res/values-zh-rCN/strings.xml index ab88307fc0..ef4209a590 100644 --- a/vector/src/main/res/values-zh-rCN/strings.xml +++ b/vector/src/main/res/values-zh-rCN/strings.xml @@ -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 版本 通用 - 选项 - 隐私安全 + 偏好 + 安全与隐私 推送规则 尚未定义任何推送规则 没有已注册的推送通道 @@ -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 显示对勾了吗? - 使 + 到服务器的连接已丢失 飞行模式已打开 @@ -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 视频来电 语音来电 你拒绝了此通话 @@ -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与其他人 @@ -2542,4 +2542,15 @@ %1$s 与 %2$d 其他人 + 正在更新你的数据…… + 自动批准Element通话组件,授予相机/麦克风权限 + 启用Element通话权限捷径 + 实时位置 + 这个QR码看起来不正常。请尝试用另一个方法验证。 + 你无法访问加密消息历史。重置你的安全消息备份和验证密钥以重新开始。 + 无法验证此设备 + 你的服务器地址是什么? + 你的对话发生的地方 + %1$s和%2$s + 电子邮件未确认,检查你的收件箱 \ No newline at end of file From 825ba77bb2bf6bccd2f1ac0066e180e0bfdac83b Mon Sep 17 00:00:00 2001 From: Adam Brown Date: Thu, 4 Aug 2022 11:58:01 +0100 Subject: [PATCH 03/32] taking into account non ascii characters as invalid username error --- .../java/org/matrix/android/sdk/api/failure/Extensions.kt | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) 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 && From a4ea47e7409ba6e8eff1fe4a276fae8350f39fe6 Mon Sep 17 00:00:00 2001 From: Adam Brown Date: Thu, 4 Aug 2022 11:58:31 +0100 Subject: [PATCH 04/32] catching username availabilty exceptions and handling as user facing error --- .../onboarding/OnboardingViewModel.kt | 43 +++++++++++-------- .../onboarding/OnboardingViewModelTest.kt | 14 ++++++ .../app/test/fakes/FakeRegistrationWizard.kt | 4 ++ 3 files changed, 43 insertions(+), 18 deletions(-) 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/test/java/im/vector/app/features/onboarding/OnboardingViewModelTest.kt b/vector/src/test/java/im/vector/app/features/onboarding/OnboardingViewModelTest.kt index 61d3101b64..f734a62cf8 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 @@ -390,6 +390,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/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) } From 9a97e0bf6143f3249453d40adf1c195e26aa301d Mon Sep 17 00:00:00 2001 From: Adam Brown Date: Thu, 4 Aug 2022 12:04:51 +0100 Subject: [PATCH 05/32] adding changelog entry --- changelog.d/6735.bugfix | 1 + 1 file changed, 1 insertion(+) create mode 100644 changelog.d/6735.bugfix 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 From c2fbb74e4b1d88c74ce706f8d3048296370fc9e5 Mon Sep 17 00:00:00 2001 From: "Mr.Narsus" Date: Fri, 5 Aug 2022 10:20:32 +0000 Subject: [PATCH 06/32] Translated using Weblate (Arabic) Currently translated at 42.0% (979 of 2327 strings) Translation: Element Android/Element Android App Translate-URL: https://translate.element.io/projects/element-android/element-app/ar/ --- vector/src/main/res/values-ar/strings.xml | 17 +++++++++++------ 1 file changed, 11 insertions(+), 6 deletions(-) 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 From f33e2fd656a8a3cee90fcc2aecf0fb8fc72d4733 Mon Sep 17 00:00:00 2001 From: "Auri B. P" Date: Tue, 2 Aug 2022 17:54:39 +0000 Subject: [PATCH 07/32] Translated using Weblate (Catalan) Currently translated at 100.0% (2327 of 2327 strings) Translation: Element Android/Element Android App Translate-URL: https://translate.element.io/projects/element-android/element-app/ca/ --- vector/src/main/res/values-ca/strings.xml | 25 ++++++++++++++++++----- 1 file changed, 20 insertions(+), 5 deletions(-) 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 From a56d8a23f58d91d44242f6a49002ae9c2f1455b7 Mon Sep 17 00:00:00 2001 From: waclaw66 Date: Fri, 29 Jul 2022 04:50:39 +0000 Subject: [PATCH 08/32] Translated using Weblate (Czech) Currently translated at 100.0% (2327 of 2327 strings) Translation: Element Android/Element Android App Translate-URL: https://translate.element.io/projects/element-android/element-app/cs/ --- vector/src/main/res/values-cs/strings.xml | 22 +++++++++++++++++++--- 1 file changed, 19 insertions(+), 3 deletions(-) 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 From 4af4f3f88ca01c7e8fbf8199ed40ef904ac3754b Mon Sep 17 00:00:00 2001 From: Vri Date: Fri, 5 Aug 2022 12:03:45 +0000 Subject: [PATCH 09/32] Translated using Weblate (German) Currently translated at 98.6% (2296 of 2327 strings) Translation: Element Android/Element Android App Translate-URL: https://translate.element.io/projects/element-android/element-app/de/ --- vector/src/main/res/values-de/strings.xml | 72 +++++++++++++++++++---- 1 file changed, 61 insertions(+), 11 deletions(-) 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 From 7b96597d569f7d48e14b194fa22100d4de8cbe45 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Priit=20J=C3=B5er=C3=BC=C3=BCt?= Date: Sat, 30 Jul 2022 19:59:00 +0000 Subject: [PATCH 10/32] Translated using Weblate (Estonian) Currently translated at 99.6% (2319 of 2327 strings) Translation: Element Android/Element Android App Translate-URL: https://translate.element.io/projects/element-android/element-app/et/ --- vector/src/main/res/values-et/strings.xml | 21 ++++++++++++++++++--- 1 file changed, 18 insertions(+), 3 deletions(-) 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 From dbbdc1791a859e5b14d9fb199201ef29fad65292 Mon Sep 17 00:00:00 2001 From: Danial Behzadi Date: Thu, 28 Jul 2022 13:46:05 +0000 Subject: [PATCH 11/32] Translated using Weblate (Persian) Currently translated at 100.0% (2327 of 2327 strings) Translation: Element Android/Element Android App Translate-URL: https://translate.element.io/projects/element-android/element-app/fa/ --- vector/src/main/res/values-fa/strings.xml | 21 ++++++++++++++++++--- 1 file changed, 18 insertions(+), 3 deletions(-) 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 From 4a3b6baaf71a23e5f670739dc4643cc76add06a2 Mon Sep 17 00:00:00 2001 From: Glandos Date: Fri, 29 Jul 2022 10:47:20 +0000 Subject: [PATCH 12/32] Translated using Weblate (French) Currently translated at 100.0% (2327 of 2327 strings) Translation: Element Android/Element Android App Translate-URL: https://translate.element.io/projects/element-android/element-app/fr/ --- vector/src/main/res/values-fr/strings.xml | 21 ++++++++++++++++++--- 1 file changed, 18 insertions(+), 3 deletions(-) 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 From 8cbdd6e13095af3703e8af979dce325a04c6928c Mon Sep 17 00:00:00 2001 From: notramo Date: Wed, 3 Aug 2022 20:28:14 +0000 Subject: [PATCH 13/32] Translated using Weblate (Hungarian) Currently translated at 98.8% (2301 of 2327 strings) Translation: Element Android/Element Android App Translate-URL: https://translate.element.io/projects/element-android/element-app/hu/ --- vector/src/main/res/values-hu/strings.xml | 104 +++++++++++++++------- 1 file changed, 72 insertions(+), 32 deletions(-) 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 From 804880cc9c0fd42c272f406812fe5993173d2184 Mon Sep 17 00:00:00 2001 From: random Date: Mon, 1 Aug 2022 13:05:27 +0000 Subject: [PATCH 14/32] Translated using Weblate (Italian) Currently translated at 100.0% (2327 of 2327 strings) Translation: Element Android/Element Android App Translate-URL: https://translate.element.io/projects/element-android/element-app/it/ --- vector/src/main/res/values-it/strings.xml | 21 ++++++++++++++++++--- 1 file changed, 18 insertions(+), 3 deletions(-) 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 From 721c4c0d9c7bc64ccc2a5f732cc75f671f89ad19 Mon Sep 17 00:00:00 2001 From: Johan Smits Date: Thu, 4 Aug 2022 06:15:58 +0000 Subject: [PATCH 15/32] Translated using Weblate (Dutch) Currently translated at 100.0% (2327 of 2327 strings) Translation: Element Android/Element Android App Translate-URL: https://translate.element.io/projects/element-android/element-app/nl/ --- vector/src/main/res/values-nl/strings.xml | 21 ++++++++++++++++++--- 1 file changed, 18 insertions(+), 3 deletions(-) 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 From a41ad6625e0d7bb93c6b098a311e0aaef63274a3 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Przemys=C5=82aw=20Romanik?= Date: Fri, 29 Jul 2022 12:50:25 +0000 Subject: [PATCH 16/32] Translated using Weblate (Polish) Currently translated at 99.6% (2319 of 2327 strings) Translation: Element Android/Element Android App Translate-URL: https://translate.element.io/projects/element-android/element-app/pl/ --- vector/src/main/res/values-pl/strings.xml | 45 +++++++++++++++++------ 1 file changed, 33 insertions(+), 12 deletions(-) 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 From ee1f29432750163ffe6eaf99a7b33ffc565c95c3 Mon Sep 17 00:00:00 2001 From: lvre <7uu3qrbvm@relay.firefox.com> Date: Thu, 4 Aug 2022 03:30:49 +0000 Subject: [PATCH 17/32] Translated using Weblate (Portuguese (Brazil)) Currently translated at 100.0% (2327 of 2327 strings) Translation: Element Android/Element Android App Translate-URL: https://translate.element.io/projects/element-android/element-app/pt_BR/ --- vector/src/main/res/values-pt-rBR/strings.xml | 21 ++++++++++++++++--- 1 file changed, 18 insertions(+), 3 deletions(-) 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 From 05816d00ae52d643a4f5033f326ca6eea2b3a237 Mon Sep 17 00:00:00 2001 From: Nui Harime Date: Fri, 5 Aug 2022 15:15:48 +0000 Subject: [PATCH 18/32] Translated using Weblate (Russian) Currently translated at 99.0% (2306 of 2327 strings) Translation: Element Android/Element Android App Translate-URL: https://translate.element.io/projects/element-android/element-app/ru/ --- vector/src/main/res/values-ru/strings.xml | 82 +++++++++++++++++++---- 1 file changed, 68 insertions(+), 14 deletions(-) diff --git a/vector/src/main/res/values-ru/strings.xml b/vector/src/main/res/values-ru/strings.xml index 55c5ab0127..281262d02d 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 вошёл(ла). - Сообщения в этой комнате зашифрованы сквозным шифрованием. + Сообщения в этой переписке защищены сквозным шифрованием. Покинуть Настройки Сообщения здесь защищены сквозным шифрованием. @@ -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 From 2aac0c9d649fc20425b00e95c9d62841c86bf485 Mon Sep 17 00:00:00 2001 From: Jozef Gaal Date: Thu, 28 Jul 2022 20:59:51 +0000 Subject: [PATCH 19/32] Translated using Weblate (Slovak) Currently translated at 100.0% (2327 of 2327 strings) Translation: Element Android/Element Android App Translate-URL: https://translate.element.io/projects/element-android/element-app/sk/ --- vector/src/main/res/values-sk/strings.xml | 22 +++++++++++++++++++--- 1 file changed, 19 insertions(+), 3 deletions(-) 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 From fde0d81c1e70a3818ecba7857ca472bba193a447 Mon Sep 17 00:00:00 2001 From: Ihor Hordiichuk Date: Tue, 2 Aug 2022 19:59:30 +0000 Subject: [PATCH 20/32] Translated using Weblate (Ukrainian) Currently translated at 100.0% (2327 of 2327 strings) Translation: Element Android/Element Android App Translate-URL: https://translate.element.io/projects/element-android/element-app/uk/ --- vector/src/main/res/values-uk/strings.xml | 33 +++++++++++++++++------ 1 file changed, 25 insertions(+), 8 deletions(-) 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 From e15e941e49588f328a1c2eecf5e9918defc524e6 Mon Sep 17 00:00:00 2001 From: phardyle Date: Wed, 3 Aug 2022 16:36:30 +0000 Subject: [PATCH 21/32] Translated using Weblate (Chinese (Simplified)) Currently translated at 100.0% (2327 of 2327 strings) Translation: Element Android/Element Android App Translate-URL: https://translate.element.io/projects/element-android/element-app/zh_Hans/ --- vector/src/main/res/values-zh-rCN/strings.xml | 42 +++++++++---------- 1 file changed, 21 insertions(+), 21 deletions(-) diff --git a/vector/src/main/res/values-zh-rCN/strings.xml b/vector/src/main/res/values-zh-rCN/strings.xml index ef4209a590..0549bd84a9 100644 --- a/vector/src/main/res/values-zh-rCN/strings.xml +++ b/vector/src/main/res/values-zh-rCN/strings.xml @@ -6,7 +6,7 @@ %1$s 加入了房间 %1$s 离开了房间 %1$s 拒绝了邀请 - %1$s 踢了 %2$s + %1$s 移除了 %2$s %1$s 解封了 %2$s %1$s 封禁了 %2$s %1$s 更换了他们的头像 @@ -33,7 +33,7 @@ 电子邮箱地址 手机号码 %1$s 撤回了对 %2$s 的邀请 - %1$s 让未来的房间聊天记录对 %2$s 可见 + %1$s 让未来的房间历史记录对 %2$s 可见 %1$s 向 %2$s 发送了加入房间的邀请 %1$s 接受了 %2$s 的邀请 空房间 @@ -108,7 +108,7 @@ 你发送了数据以建立通话。 你接听了通话。 你结束了通话。 - 你已让未来的房间记录对%1$s可见 + 你已让未来的房间历史对%1$s可见 你升级了此房间。 你移除了房间名称 你移除了房间主题 @@ -893,7 +893,7 @@ 重置安全备份 在此设备上设置 通过在你的服务器上备份加密密钥,防止失去对加密信息和数据的访问。 - 为你已有的备份生成新的安全密钥或设置新的安全口令。 + 为你已有的备份生成新的安全密钥或设置新的安全短语。 这将替换你的当前密钥或短语。 发现 管理你的发现设置。 @@ -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} 的唯一方式。 启用设备特定的生物特征识别,如指纹和面部识别。 @@ -2143,7 +2143,7 @@ 添加新关键词 你的关键词 - 仅提及 & 关键词 + 仅提及和关键词 正结束通话… 无应答 你呼叫的用户正忙。 @@ -2224,7 +2224,7 @@ 投票 向 %s 发送电子邮件和电话号码 您的联系人是私密的。 要从您的联系人中发现用户,我们需要您的许可才能将联系信息发送到您的身份服务器。 - 已退出此会话! + 已登出此会话! 已离开此房间! 你同意发送此信息吗? 要发现现有的联系人,您需要将联系人信息(电子邮件和电话号码)发送到您的身份服务器。出乎隐私考量,我们会在发送前对您的数据进行散列处理。 @@ -2540,7 +2540,7 @@ - 一些用户已被取消忽略 初始同步请求 - %1$s 与 %2$d 其他人 + %1$s与其他%2$d人 正在更新你的数据…… 自动批准Element通话组件,授予相机/麦克风权限 From ec9199cb18d4d6ed60c423d39a7a2e76b033bbaa Mon Sep 17 00:00:00 2001 From: phardyle Date: Wed, 3 Aug 2022 16:39:09 +0000 Subject: [PATCH 22/32] Translated using Weblate (Chinese (Traditional)) Currently translated at 100.0% (2327 of 2327 strings) Translation: Element Android/Element Android App Translate-URL: https://translate.element.io/projects/element-android/element-app/zh_Hant/ --- vector/src/main/res/values-zh-rTW/strings.xml | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/vector/src/main/res/values-zh-rTW/strings.xml b/vector/src/main/res/values-zh-rTW/strings.xml index ee96ee6063..c4d5627bce 100644 --- a/vector/src/main/res/values-zh-rTW/strings.xml +++ b/vector/src/main/res/values-zh-rTW/strings.xml @@ -1304,7 +1304,7 @@ 使用原始大小傳送圖片 確認移除 - 您確定您想要移除(刪除)此活動嗎?注意,如果您刪除聊天室名稱或變更主題,則可能會撤銷變更。 + 您確定您想要移除(刪除)此活動嗎?注意,如果您刪除聊天室名稱或主題的變更,變更會被撤銷。 包含理由 修改原因 被使用者刪除的活動,理由:%1$s @@ -2293,9 +2293,9 @@ 位置 分享位置 結果僅在您結束投票後顯示 - 已關閉投票 + 封闭式投票 投票者在投票後可以立刻看到投票結果 - 開啟投票 + 開放式投票 投票類型 編輯投票 沒有投票 @@ -2486,7 +2486,7 @@ MSC3061:為過去的訊息分享聊天室金鑰 傳送您的第一則訊息以邀請 %s 來聊天 此聊天中的訊息將會是端到端加密。 - + 出發 已移除 %d 則訊息 From 77ae291c1576fe378cbb0e6fb7ebc8c7433a1403 Mon Sep 17 00:00:00 2001 From: Jeff Huang Date: Fri, 29 Jul 2022 02:10:33 +0000 Subject: [PATCH 23/32] Translated using Weblate (Chinese (Traditional)) Currently translated at 100.0% (2327 of 2327 strings) Translation: Element Android/Element Android App Translate-URL: https://translate.element.io/projects/element-android/element-app/zh_Hant/ --- vector/src/main/res/values-zh-rTW/strings.xml | 20 ++++++++++++++++--- 1 file changed, 17 insertions(+), 3 deletions(-) diff --git a/vector/src/main/res/values-zh-rTW/strings.xml b/vector/src/main/res/values-zh-rTW/strings.xml index c4d5627bce..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 活躍的工作階段 - 驗證此登入 + 驗證此裝置 使用既有的工作階段來驗證這個,讓它可以存取已加密的訊息。 驗證 已驗證 @@ -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 From ac047afd278b3bf59914e1d73976cdb993fec3d3 Mon Sep 17 00:00:00 2001 From: Dinh Quang Tuyen Date: Thu, 4 Aug 2022 08:17:21 +0000 Subject: [PATCH 24/32] Translated using Weblate (Vietnamese) Currently translated at 88.6% (2062 of 2327 strings) Translation: Element Android/Element Android App Translate-URL: https://translate.element.io/projects/element-android/element-app/vi/ --- vector/src/main/res/values-vi/strings.xml | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) 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 From b2d19152274463f8c552f62966f772fe64864e21 Mon Sep 17 00:00:00 2001 From: Nui Harime Date: Sun, 7 Aug 2022 23:02:55 +0000 Subject: [PATCH 25/32] Translated using Weblate (Russian) Currently translated at 99.0% (2306 of 2327 strings) Translation: Element Android/Element Android App Translate-URL: https://translate.element.io/projects/element-android/element-app/ru/ --- vector/src/main/res/values-ru/strings.xml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/vector/src/main/res/values-ru/strings.xml b/vector/src/main/res/values-ru/strings.xml index 281262d02d..1fd522c7d5 100644 --- a/vector/src/main/res/values-ru/strings.xml +++ b/vector/src/main/res/values-ru/strings.xml @@ -1840,7 +1840,7 @@ Отправить историю запросов на обмен ключами Начать беседу %s чтобы люди знали, о чём эта комната. - Это начало вашей истории диалога с %s. + Это начало вашей переписки с %s. Экспорт аудита Личное сообщение Отправить электронную почту и номера телефонов From 6e1e31bac1e61aaec10e957d3c9340bb757c18e4 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Jorge=20Mart=C3=ADn?= Date: Tue, 9 Aug 2022 09:53:08 +0200 Subject: [PATCH 26/32] Avoid crashes from unknown exceptions on lockscreen key migration. --- changelog.d/6769.bugfix | 1 + .../lockscreen/crypto/LockScreenKeysMigrator.kt | 2 +- .../migrations/MissingSystemKeyMigrator.kt | 17 ++++++++--------- .../crypto/migrations/SystemKeyV1Migrator.kt | 11 +++++++---- ...rTests.kt => LockScreenKeysMigratorTests.kt} | 6 +++--- .../migrations/MissingSystemKeyMigratorTests.kt | 10 +++++----- .../migrations/SystemKeyV1MigratorTests.kt | 4 +++- 7 files changed, 28 insertions(+), 23 deletions(-) create mode 100644 changelog.d/6769.bugfix rename vector/src/test/java/im/vector/app/features/pin/lockscreen/crypto/migrations/{LockScreenTestMigratorTests.kt => LockScreenKeysMigratorTests.kt} (94%) 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/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/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() { From 2f4725cfe96172827c8d62a5b4aaa76e74ae25f3 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Jorge=20Mart=C3=ADn?= Date: Mon, 8 Aug 2022 12:22:05 +0200 Subject: [PATCH 27/32] Prevent crash while validating biometric key. --- changelog.d/6768.bugfix | 1 + .../features/pin/lockscreen/crypto/KeyStoreCrypto.kt | 12 +++--------- 2 files changed, 4 insertions(+), 9 deletions(-) create mode 100644 changelog.d/6768.bugfix 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/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 } From dfc8526b474c979fc8a2d5b128f9fe88551e7d6e Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Jorge=20Mart=C3=ADn?= Date: Tue, 9 Aug 2022 09:54:05 +0200 Subject: [PATCH 28/32] Refactor lockscreen implementation. Try to fix issues and simplify flow. --- .../biometrics/BiometricHelperTests.kt | 18 +-- .../crypto/LockScreenKeyRepositoryTests.kt | 4 - .../im/vector/app/features/pin/PinFragment.kt | 39 +++--- .../lockscreen/biometrics/BiometricHelper.kt | 44 +++---- .../configuration/LockScreenConfiguration.kt | 6 +- .../LockScreenConfiguratorProvider.kt | 58 --------- .../pin/lockscreen/di/LockScreenModule.kt | 5 + .../pin/lockscreen/ui/LockScreenFragment.kt | 24 ++-- .../pin/lockscreen/ui/LockScreenViewEvent.kt | 1 + .../pin/lockscreen/ui/LockScreenViewModel.kt | 114 +++++++++--------- .../pin/lockscreen/ui/LockScreenViewState.kt | 6 +- .../settings/VectorSettingsPinFragment.kt | 10 +- .../fragment/LockScreenViewModelTests.kt | 69 ++++++----- 13 files changed, 183 insertions(+), 215 deletions(-) delete mode 100644 vector/src/main/java/im/vector/app/features/pin/lockscreen/configuration/LockScreenConfiguratorProvider.kt 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..2ec69cf0b1 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 @@ -54,8 +53,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 +240,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() } + every { lockScreenKeyRepository.deleteSystemKey() } returns Unit 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/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/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/LockScreenFragment.kt b/vector/src/main/java/im/vector/app/features/pin/lockscreen/ui/LockScreenFragment.kt index 9eea61ac82..9a618ce939 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 @@ -34,6 +34,10 @@ import im.vector.app.databinding.FragmentLockScreenBinding 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.views.LockScreenCodeView +import kotlinx.coroutines.flow.distinctUntilChangedBy +import kotlinx.coroutines.flow.filter +import kotlinx.coroutines.flow.launchIn +import kotlinx.coroutines.flow.onEach @AndroidEntryPoint class LockScreenFragment : VectorBaseFragment() { @@ -55,22 +59,12 @@ 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) { + viewModel.stateFlow.distinctUntilChangedBy { it.showBiometricPromptAutomatically } + .filter { it.showBiometricPromptAutomatically } + .onEach { showBiometricPrompt() } - } - } - } - - override fun onDestroy() { - super.onDestroy() - viewModel.reset() + .launchIn(viewLifecycleOwner.lifecycleScope) } override fun invalidate() = withState(viewModel) { state -> @@ -83,6 +77,7 @@ class LockScreenFragment : VectorBaseFragment() { setupTitleView(views.titleTextView, false, state.lockScreenConfiguration) } } + renderDeleteOrFingerprintButtons(views, views.codeView.enteredDigits) } @@ -123,6 +118,7 @@ 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() } } 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..e340486bc0 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,5 @@ 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() } 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..417ebdbd93 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,12 @@ 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.annotation.VisibleForTesting 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 +31,28 @@ 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.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,12 +70,20 @@ 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) + val state = awaitState() + // If when initialized we detect a key invalidation, we should show an error message. + if (state.isBiometricKeyInvalidated) { + onBiometricKeyInvalidated() + } + } } override fun handle(action: LockScreenAction) { @@ -141,13 +133,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 +152,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 +176,19 @@ 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") + @VisibleForTesting(otherwise = VisibleForTesting.PRIVATE) + internal 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/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 From cc59b9e6959c569ebd6c92305974d2debbff8cda Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Jorge=20Mart=C3=ADn?= Date: Tue, 9 Aug 2022 12:57:59 +0200 Subject: [PATCH 29/32] Address review comments. --- .../features/pin/lockscreen/biometrics/BiometricHelperTests.kt | 3 ++- .../app/features/pin/lockscreen/ui/LockScreenViewModel.kt | 3 +-- 2 files changed, 3 insertions(+), 3 deletions(-) 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 2ec69cf0b1..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 @@ -39,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 @@ -247,7 +248,7 @@ class BiometricHelperTests { every { createAuthChannel() } returns mockAuthChannel every { authenticateWithPromptInternal(any(), any(), any()) } returns mockk() } - every { lockScreenKeyRepository.deleteSystemKey() } returns Unit + justRun { lockScreenKeyRepository.deleteSystemKey() } val latch = CountDownLatch(1) val intent = Intent(InstrumentationRegistry.getInstrumentation().targetContext, LockScreenTestActivity::class.java) 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 417ebdbd93..2230215047 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 @@ -182,8 +182,7 @@ class LockScreenViewModel @AssistedInject constructor( * See issue [#6768](https://github.com/vector-im/element-android/issues/6768). */ @SuppressLint("NewApi") - @VisibleForTesting(otherwise = VisibleForTesting.PRIVATE) - internal suspend fun waitUntilKeyguardIsUnlocked() { + private suspend fun waitUntilKeyguardIsUnlocked() { if (versionProvider.get() < Build.VERSION_CODES.S) return withTimeoutOrNull(5.seconds) { while (keyguardManager.isDeviceLocked) { From 9888e15f2a4570b2376ec23a66809254b365d222 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Jorge=20Mart=C3=ADn?= Date: Tue, 9 Aug 2022 14:02:26 +0200 Subject: [PATCH 30/32] Improve logic to trigger LockScreenViewEvents --- .../pin/lockscreen/ui/LockScreenAction.kt | 1 + .../pin/lockscreen/ui/LockScreenFragment.kt | 13 ++----------- .../pin/lockscreen/ui/LockScreenViewEvent.kt | 1 + .../pin/lockscreen/ui/LockScreenViewModel.kt | 19 +++++++++++++++---- 4 files changed, 19 insertions(+), 15 deletions(-) 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 9a618ce939..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 @@ -34,10 +33,6 @@ import im.vector.app.databinding.FragmentLockScreenBinding 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.views.LockScreenCodeView -import kotlinx.coroutines.flow.distinctUntilChangedBy -import kotlinx.coroutines.flow.filter -import kotlinx.coroutines.flow.launchIn -import kotlinx.coroutines.flow.onEach @AndroidEntryPoint class LockScreenFragment : VectorBaseFragment() { @@ -59,12 +54,7 @@ class LockScreenFragment : VectorBaseFragment() { handleEvent(it) } - viewModel.stateFlow.distinctUntilChangedBy { it.showBiometricPromptAutomatically } - .filter { it.showBiometricPromptAutomatically } - .onEach { - showBiometricPrompt() - } - .launchIn(viewLifecycleOwner.lifecycleScope) + viewModel.handle(LockScreenAction.OnUIReady) } override fun invalidate() = withState(viewModel) { state -> @@ -119,6 +109,7 @@ class LockScreenFragment : VectorBaseFragment() { 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 e340486bc0..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 @@ -25,4 +25,5 @@ sealed class LockScreenViewEvent : VectorViewEvents { 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 2230215047..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 @@ -20,7 +20,6 @@ import android.annotation.SuppressLint import android.app.KeyguardManager import android.os.Build import android.security.keystore.KeyPermanentlyInvalidatedException -import androidx.annotation.VisibleForTesting import androidx.fragment.app.FragmentActivity import com.airbnb.mvrx.MavericksViewModelFactory import dagger.assisted.Assisted @@ -37,6 +36,7 @@ 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 @@ -77,10 +77,20 @@ class LockScreenViewModel @AssistedInject constructor( lockScreenKeysMigrator.migrateIfNeeded() // Update initial state with biometric info updateStateWithBiometricInfo() + } + } - val state = awaitState() - // If when initialized we detect a key invalidation, we should show an error message. - if (state.isBiometricKeyInvalidated) { + 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() } } @@ -90,6 +100,7 @@ class LockScreenViewModel @AssistedInject constructor( when (action) { is LockScreenAction.PinCodeEntered -> onPinCodeEntered(action.value) is LockScreenAction.ShowBiometricPrompt -> showBiometricPrompt(action.callingActivity) + is LockScreenAction.OnUIReady -> observeStateChanges() } } From 6045eac87a4eb5ae95d6c811482a81f7a09982b0 Mon Sep 17 00:00:00 2001 From: Nikita Fedrunov <66663241+fedrunov@users.noreply.github.com> Date: Tue, 9 Aug 2022 14:31:26 +0200 Subject: [PATCH 31/32] recents carousel for new home screen layout (#6707) --- dependencies_groups.gradle | 1 + vector/build.gradle | 3 + .../java/im/vector/app/VectorApplication.kt | 19 +++- .../room/list/home/HomeRoomListFragment.kt | 17 +++- .../room/list/home/HomeRoomListViewModel.kt | 15 ++++ .../home/room/list/home/HomeRoomSection.kt | 4 + .../recent/RecentRoomCarouselController.kt | 86 +++++++++++++++++++ .../room/list/home/recent/RecentRoomItem.kt | 78 +++++++++++++++++ .../src/main/res/layout/item_recent_room.xml | 61 +++++++++++++ 9 files changed, 281 insertions(+), 3 deletions(-) create mode 100644 vector/src/main/java/im/vector/app/features/home/room/list/home/recent/RecentRoomCarouselController.kt create mode 100644 vector/src/main/java/im/vector/app/features/home/room/list/home/recent/RecentRoomItem.kt create mode 100644 vector/src/main/res/layout/item_recent_room.xml 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/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/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 f0eb027785..02122a5ee1 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 @@ -30,6 +30,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 @@ -43,6 +44,7 @@ import im.vector.app.features.home.room.list.RoomSummaryPagedController import im.vector.app.features.home.room.list.actions.RoomListQuickActionsBottomSheet import im.vector.app.features.home.room.list.actions.RoomListQuickActionsSharedAction import im.vector.app.features.home.room.list.actions.RoomListQuickActionsSharedActionViewModel +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 @@ -53,7 +55,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 { @@ -180,6 +183,12 @@ class HomeRoomListFragment @Inject constructor( } }.adapter } + is HomeRoomSection.RecentRoomsData -> recentRoomCarouselController.also { controller -> + controller.listener = this + data.list.observe(viewLifecycleOwner) { list -> + controller.submitList(list) + } + }.adapter } } @@ -192,6 +201,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/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 @@ + + + + + + + + + + + From a2768ccab7b99c3a44cdd745033d63b52fe91f12 Mon Sep 17 00:00:00 2001 From: Adam Brown Date: Tue, 9 Aug 2022 16:22:21 +0100 Subject: [PATCH 32/32] ignoring the gl locale from the play store upload step as it's unsupported --- tools/release/pushPlayStoreMetaData.sh | 1 + 1 file changed, 1 insertion(+) 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