Page MenuHomePhorge

No OneTemporary

Size
82 KB
Referenced Files
None
Subscribers
None
diff --git a/src/contents/ui/StickerPicker.qml b/src/contents/ui/StickerPicker.qml
index 20fde2e..764a135 100644
--- a/src/contents/ui/StickerPicker.qml
+++ b/src/contents/ui/StickerPicker.qml
@@ -1,102 +1,148 @@
/*
* This file is part of kazv.
* SPDX-FileCopyrightText: 2020-2023 tusooa <tusooa@kazv.moe>
* SPDX-License-Identifier: AGPL-3.0-or-later
*/
import QtQuick
import QtQuick.Layouts
import QtQuick.Controls
import org.kde.kirigami as Kirigami
import moe.kazv.mxc.kazv as MK
import '.' as Kazv
ColumnLayout {
id: stickerPicker
property var stickerPackList
spacing: 0
property var currentIndex: 0
signal sendMessageRequested(var eventJson)
+ property var removeStickerPack
+ property string removeStickerShortCode: ''
+
+ property var removeStickerHandler: Kazv.AsyncHandler {
+ trigger: () => {
+ return matrixSession.updateStickerPack(
+ stickerPicker.removeStickerPack.removeSticker(stickerPicker.removeStickerShortCode)
+ );
+ }
+ onResolved: (success, data) => {
+ if (!success) {
+ showPassiveNotification(l10n.get('sticker-remove-failed-prompt', { errorCode: data.errorCode, errorMsg: data.error }));
+ }
+ }
+ }
+
+ function requestRemoveSticker(pack, shortCode) {
+ stickerPicker.removeStickerPack = pack;
+ stickerPicker.removeStickerShortCode = shortCode;
+ removeStickerConfirmationComp.createObject(Overlay.overlay, {
+ message: l10n.get('sticker-remove-confirmation-message', { shortCode: shortCode })
+ }).open();
+ }
+
+ property var removeStickerConfirmationComp: Component {
+ Kazv.ConfirmationOverlay {
+ objectName: 'removeStickerConfirmation'
+ shouldSelfDestroy: true
+ title: l10n.get('sticker-remove-confirmation-title')
+ message: l10n.get('sticker-remove-confirmation-message')
+ confirmActionText: l10n.get('sticker-remove-confirm-action')
+ cancelActionText: l10n.get('sticker-remove-cancel-action')
+ onAccepted: stickerPicker.removeStickerHandler.call()
+ }
+ }
+
TabBar {
id: packListView
Layout.fillWidth: true
Layout.minimumHeight: Kirigami.Units.gridUnit * 2
Repeater {
model: stickerPackList
delegate: TabButton {
id: packDelegate
objectName: `stickerPack${index}`
property var pack: stickerPackList.at(index)
property var packNameProvider: Kazv.StickerPackNameProvider {
pack: packDelegate.pack
}
icon.name: 'smiley'
text: packNameProvider.name
checked: stickerPicker.currentIndex === index
onClicked: setCurrent()
Accessible.onPressAction: setCurrent()
function setCurrent() {
stickerPicker.currentIndex = index;
}
}
}
}
property var currentPack: stickerPackList.at(currentIndex)
property var stickerSize: Kirigami.Units.iconSizes.enormous
property var stickerMargin: Kirigami.Units.smallSpacing
ColumnLayout {
clip: true
GridView {
id: stickersGridView
Layout.fillWidth: true
Layout.minimumHeight: stickerPicker.stickerSize * 5
Layout.preferredHeight: stickersGridView.height
model: currentPack
cellWidth: stickerPicker.stickerSize + stickerPicker.stickerMargin
cellHeight: stickerPicker.stickerSize + stickerPicker.stickerMargin
focus: true
focusPolicy: Qt.StrongFocus
delegate: AbstractButton {
id: stickerButton
objectName: `sticker${index}`
property var sticker: currentPack.at(index)
height: stickerPicker.stickerSize + stickerPicker.stickerMargin
width: stickerPicker.stickerSize + stickerPicker.stickerMargin
topInset: 0
bottomInset: 0
leftInset: 0
rightInset: 0
padding: stickerPicker.stickerMargin
contentItem: Kazv.ImageAdapter {
fillMode: Image.PreserveAspectFit
height: stickerPicker.stickerSize
width: stickerPicker.stickerSize
mxcUri: sticker.mxcUri
}
hoverEnabled: true
background: Kazv.FocusRingBackground {
target: stickerButton
}
Accessible.role: Accessible.Button
Accessible.name: sticker.body
Accessible.onPressAction: requestSendMessage()
onClicked: requestSendMessage()
function requestSendMessage() {
stickerPicker.sendMessageRequested(sticker.makeEventJson());
}
+
+ ContextMenu.menu: Menu {
+ objectName: 'stickerContextMenu'
+ Kirigami.Action {
+ objectName: 'removeStickerMenuItem'
+ text: l10n.get('sticker-remove-action')
+ icon.name: 'edit-delete'
+ onTriggered: stickerPicker.requestRemoveSticker(currentPack, sticker.shortCode)
+ }
+ }
}
}
}
}
diff --git a/src/l10n/cmn-Hans/100-ui.ftl b/src/l10n/cmn-Hans/100-ui.ftl
index ad45114..63ba3df 100644
--- a/src/l10n/cmn-Hans/100-ui.ftl
+++ b/src/l10n/cmn-Hans/100-ui.ftl
@@ -1,473 +1,480 @@
### This file is part of kazv.
### SPDX-FileCopyrightText: 2020-2023 tusooa <tusooa@kazv.moe>
### SPDX-License-Identifier: AGPL-3.0-or-later
app-title-with-user-id = { -kt-app-name } - { $userId }
app-title-not-logged-in = { -kt-app-name } - 未登录
about-page-title = 关于 { -kt-app-name }
about-copyright = (c) 2020- the Kazv Project
about-display-name = { -kt-app-name } { $version }
about-short-description = 各平台同一的 Matrix 客户端和即时通讯软件
about-license = 以 AGPL 3 或以后版本授权
about-used-libraries = 使用了的库
about-authors = 作者
about-author-email-action = 写电邮
about-author-website-action = 访问网站
about-author-task-maintainer = 维护者
about-author-task-developer = 开发者
user-name-with-id = { $name } ({ $userId })
user-name-overrided = { $overridedName } ({ $globalName })
global-drawer-title = { -kt-app-name }
global-drawer-action-switch-account = 切换账号
global-drawer-action-hard-logout = 登出
global-drawer-action-save-session = 保存当前会话
global-drawer-action-configure-shortcuts = 配置快捷键
global-drawer-action-settings = 设置
global-drawer-action-create-room = 创建房间
global-drawer-action-join-room = 加入房间
global-drawer-action-verifications = 设备验证
global-drawer-action-about = 关于 { -kt-app-name }
action-settings-page-title = 配置快捷键
action-settings-shortcut-prompt = 快捷键:{ $shortcut }
action-settings-shortcut-none = (无)
action-settings-shortcut-edit-action = 编辑
action-settings-shortcut-remove-action = 清除
action-settings-shortcut-conflict-modal-title = 冲突的快捷键
action-settings-shortcut-conflict = 快捷键 { $shortcut } 跟别的指令有冲突。<br>若要继续,别的指令的快捷键会被清除。<br><br>冲突的指令有:<br>{ $conflictingAction }
action-settings-shortcut-conflict-continue = 继续
action-settings-shortcut-conflict-cancel = 取消
empty-room-page-title = 没有选中房间
empty-room-page-description = 当前没有选中的房间。
login-page-title = 登录
login-page-userid-prompt = 用户 id:
login-page-userid-input-placeholder = 例如: @foo:example.org
login-page-password-prompt = 密码:
login-page-login-button = 登录
login-page-close-button = 关闭
login-page-existing-sessions-prompt = 从已有会话中选一个:
login-page-alternative-password-login-prompt = 或者用用户 id 和密码启动新会话:
login-page-restore-session-button = 恢复会话
login-page-request-failed-prompt = 登录失败。错误代码:{ $errorCode }。错误讯息:{ $errorMsg }。
login-page-discover-failed-enter-prompt = 不能检测此用户所在的服务器,或者服务器不可用。错误代码:{ $errorCode }。错误讯息:{ $errorMsg }。请手动输入服务器链接。
login-page-server-url-placeholder = 例如: https://example.org
login-page-server-url-prompt = 服务器链接(可选):
login-page-next-action = 下一步
login-page-server-label = 连接到 { $serverUrl }
login-page-login-flow-choice = 选择一种登录方式:
login-page-login-flow-password = 密码
login-page-login-flow-sso = 单点登录
login-page-login-with-sso-button = 进行单点登录
login-page-login-sso-redirect-url-label = 你应该看到浏览器打开了一个网页。遵从那个页面上的指示。如果没看到,那就手动打开下面的 URL:
login-page-change-server-button = 切换服务器
login-page-no-flow-supported = 这个服务器并不提供任何 { -kt-app-name } 支持的登录流程。
session-load-failure-not-found = 找不到会话 { $sessionName }。
session-load-failure-format-unknown = 会话 { $sessionName } 包含不支持的格式。是由未来版本的 { -kt-app-name } 保存的吗?
session-load-failure-cannot-backup = 无法备份会话 { $sessionName }。
session-load-failure-lock-failed = 会话 { $sessionName } 正在被别的程序使用。
session-load-failure-cannot-open-file = 无法打开会话 { $sessionName } 的存档文件。
session-load-failure-deserialize-failed = 无法打开会话 { $sessionName }。是被损坏了或者是由未来版本的 { -kt-app-name } 保存的吗?
main-page-title = { -kt-app-name } - { $userId }
main-page-recent-tab-title = 最近
main-page-favourites-tab-title = 最爱
main-page-people-tab-title = 人们
main-page-rooms-tab-title = 房间
main-page-room-filter-prompt = 过滤房间...
room-list-view-room-item-title-name = { $name }
room-list-view-room-item-title-heroes = { $hero } { $otherNum ->
[0] { "" }
[1] 和 { $secondHero }
*[other] 和别的 { $otherNum } 个人
}
room-list-view-room-item-title-id = 未命名房间({ $roomId })
room-list-view-room-item-fav-action = 设为最爱
room-list-view-room-item-unread-indicator = 未读
room-list-view-room-item-unread-notification-count = { $count }
room-list-view-room-item-unread-notification-count-text = { $count } 条未读通知
room-tags-fav-action-notification = 把 { $name } 设为了最爱
room-tags-fav-action-notification-failed = 不能把 { $name } 设为最爱。错误代码:{ $errorCode }。错误讯息:{ $errorMsg }。
room-tags-unfav-action-notification = 把 { $name } 从最爱中移除了
room-tags-unfav-action-notification-failed = 不能把 { $name } 从最爱中移除。错误代码:{ $errorCode }。错误讯息:{ $errorMsg }。
room-tags-add-tag-action-notification = 把标签 { $tag } 添加到了 { $name }
room-tags-add-tag-action-notification-failed = 不能把标签 { $tag } 添加到 { $name }。错误代码:{ $errorCode }。错误讯息:{ $errorMsg }。
room-tags-remove-tag-action-notification = 把标签 { $tag } 从 { $name } 移除了
room-tags-remove-tag-action-notification-failed = 不能把标签 { $tag } 从 { $name } 移除。错误代码:{ $errorCode }。错误讯息:{ $errorMsg }。
room-list-view-room-item-more-action = 更多...
room-list-view-room-item-invited = (邀请)
room-list-view-room-item-left = (已离开)
room-list-view-room-item-tombstone = (已废弃)
room-settings-action = 房间设置...
room-settings-page-title = { $room } 的房间设置
room-settings-tags = 房间标签
room-settings-favourited = 设为最爱
room-settings-remove-tag = 移除标签
room-settings-add-tag = 添加标签
room-settings-members-action = 房间成员...
room-settings-banned-members-action = 被封禁的成员...
room-settings-enable-encryption-prompt-dialog-title = 启用加密
room-settings-enable-encryption-prompt-dialog-prompt = 一旦在本房间中启用了加密,就不能再禁用了。确定吗?
room-settings-enable-encryption-action = 启用加密
room-settings-encrypted = 本房间中的消息是端对端加密了的。
room-settings-not-encrypted = 本房间中的消息没有端对端加密。
room-settings-encryption-enabled-notification = 本房间中的加密已经启用。
room-settings-encryption-failed-to-enable-notification = 不能在本房间中启用加密。错误代码:{ $errorCode }。错误讯息:{ $errorMsg }。
room-settings-name-missing = 这个房间没有名字。
room-settings-topic-missing = 这个房间没有话题。
room-settings-edit-name-action = 编辑房间名字
room-settings-edit-topic-action = 编辑话题
room-settings-save-name-action = 保存房间名字
room-settings-save-topic-action = 保存话题
room-settings-discard-name-action = 放弃房间名字
room-settings-discard-topic-action = 放弃话题
room-settings-set-name-failed-prompt = 不能设置房间名字。错误代码:{ $errorCode }。错误讯息:{ $errorMsg }。
room-settings-set-topic-failed-prompt = 不能设置话题。错误代码:{ $errorCode }。错误讯息:{ $errorMsg }。
room-sticker-packs-action = 贴纸包...
room-sticker-packs-page-title = { $room } 中的贴纸包
room-sticker-packs-use-action = 使用贴纸包
room-sticker-packs-use-failed = 设置使用的贴纸包失败。错误代码:{ $errorCode }。错误讯息:{ $errorMsg }。
room-sticker-packs-page-add-action = 添加贴纸包
room-sticker-packs-page-add-popup-title = 添加贴纸包
room-sticker-packs-page-add-success = 已添加贴纸包。
room-sticker-packs-page-add-failed = 无法添加贴纸包。错误代码:{ $errorCode }。错误讯息:{ $errorMsg }。
room-sticker-packs-page-add-popup-name-prompt = 贴纸包名称:
room-sticker-packs-page-add-popup-state-key-prompt = 状态键:
room-member-list-page-title = { $room } 的成员
room-leave-action = 离开房间
room-leave-confirm-popup-title = 离开房间
room-leave-confirm-popup-message = 你确定要离开这个房间吗?
room-leave-confirm-popup-confirm-action = 离开
room-leave-confirm-popup-cancel-action = 留下
room-forget-action = 忘记房间
room-forget-confirm-popup-title = 忘记房间
room-forget-confirm-popup-message = 你确定要忘记这个房间吗?
room-forget-confirm-popup-confirm-action = 忘记
room-forget-confirm-popup-cancel-action = 取消
send-message-box-input-placeholder = 在此输入您的讯息...
send-message-box-send = 发送
send-message-box-send-file = 发送文件
send-message-box-reply-to = 回复给
send-message-box-edit = 编辑
send-message-box-remove-reply-to-action = 移除回复关系
send-message-box-remove-replace-action = 取消编辑
send-message-box-stickers = 发送贴纸...
send-message-box-stickers-popup-title = 发送贴纸
save-draft-confirmation-title = 保存草稿
save-draft-confirmation = 你有一个未发送的草稿,需要保存草稿以便在稍后恢复它吗?
save-draft-confirm-action = 保存
save-draft-discard-action = 丢弃
save-draft-cancel-action = 维持现状
discard-draft-confirmation-title = 丢弃草稿
discard-draft-confirmation = 你有一个未发送的草稿,你的操作将会丢弃它。
discard-draft-confirm-action = 丢弃
discard-draft-keep-action = 保留
discard-draft-cancel-action = 维持现状
sticker-picker-user-stickers = 我的贴纸
sticker-picker-room-sticker-pack-name = {$room} 中的 {$stateKey}
sticker-picker-room-default-sticker-pack-name = {$room} 中的默认包
room-timeline-load-more-action = 加载更多
room-timeline-scroll-to-latest-action = 滚动到最新的消息
room-invite-accept-action = 接受邀请
room-invite-reject-action = 拒绝邀请
room-invite-popup-title = 被邀请了
room-invite-popup-text = 你被邀请到这个房间了。
room-invite-popup-text-with-inviter = 你被 { $inviterName } 邀请到这个房间了。
room-pinned-events-action = { $count } 条置顶消息...
room-pinned-events-page-title = { $room } 的置顶消息
## 状态事件
## 通用参数:
## gender = 发送者的性别(male/female/neutral)
## stateKeyUser = state key 用户的名字
## stateKeyUserGender = state key 用户的性别
member-state-joined-room = 加入了房间。
member-state-changed-name-and-avatar = 修改了名字和头像。
member-state-changed-name = 修改了名字。
member-state-changed-avatar = 修改了头像。
member-state-invited = 把 { $stateKeyUser } 邀请到了本房间。
member-state-left = 离开了房间。
member-state-kicked = 踢出了 { $stateKeyUser }。
member-state-banned = 封禁了 { $stateKeyUser }。
member-state-unbanned = 解封了 { $stateKeyUser }。
state-room-created = 创建了房间。
state-room-name-changed = 把房间名字改成了 { $newName }。
state-room-topic-changed = 把房间话题改成了 { $newTopic }。
state-room-avatar-changed = 修改了房间头像。
state-room-pinned-events-changed = 修改了房间的置顶讯息。
state-room-alias-changed = 修改了房间的别名。
state-room-join-rules-changed = 修改了房间的加入规则。
state-room-power-levels-changed = 修改了房间的权限。
state-room-encryption-activated = 对本房间启用了加密。
event-message-image-sent = 发送了图片「{ $body }」。
event-message-sticker-sent = 发送了贴纸「{ $body }」。
event-summary-image-sent = 发送了图片「{ $body }」。
event-summary-sticker-sent = 发送了贴纸「{ $body }」。
event-message-file-sent = 发送了文件「{ $body }」。
event-message-video-sent = 发送了视频「{ $body }」。
event-summary-video-sent = 发送了视频「{ $body }」。
event-message-audio-sent = 发送了音频「{ $body }」。
event-message-audio-play-audio = 播放音频
event-sending = 发送中...
event-send-failed = 发送失败
event-resend = 重试发送这个事件
event-deleted = (已删除)
event-delete = 删除
event-delete-failed = 删除事件出错。错误码:{ $errorCode }。错误讯息:{ $errorMsg }。
event-view-source = 查看源码...
event-view-history = 查看编辑历史...
event-source-popup-title = 事件源码
event-source-decrypted = 解密的事件源码
event-source-original = 原始的事件源码
event-history-popup-title = 事件编辑历史
event-reply-action = 回复
event-popup-action = 更多关于这个事件...
event-reacted-with = 回应了「{ $key }」
event-react-action = 回应...
event-react-popup-title = 回应一条消息
event-react-accept-action = 回应
event-react-cancel-action = 取消
event-react-with-prompt = 回应以:
event-react-button-description = 回应以 { $key }
event-edit-action = 编辑
event-encrypted = 这条消息已加密
event-msgtype-fallback-text = 未知的消息类型:{ $msgtype }
event-fallback-text = 未知事件:{ $type }
event-cannot-decrypt-text = (加密内容)
event-read-indicator-more = +{ $rest }
event-read-indicator-list-title = { $numUsers } 个用户已读
event-edited-indicator = (编辑过了)
event-pin-action = 在房间置顶
event-unpin-action = 从房间取消置顶
event-pin-confirmation-title = 在房间置顶
event-pin-confirmation = 确定要置顶这条消息吗?
event-pin-confirm-action = 置顶
event-pin-cancel-action = 不置顶
event-pin-success-prompt = 消息在房间置顶了。
event-pin-failed-prompt = 无法置顶消息。错误代码:{ $errorCode }。错误讯息:{ $errorMsg }。
event-unpin-confirmation-title = 从房间取消置顶
event-unpin-confirmation = 确定要取消置顶这条消息吗?
event-unpin-confirm-action = 取消置顶
event-unpin-cancel-action = 不取消置顶
event-unpin-success-prompt = 消息从房间取消置顶了。
event-unpin-failed-prompt = 无法取消置顶消息。错误代码:{ $errorCode }。错误讯息:{ $errorMsg }。
media-file-menu-option-view = 查看
media-file-menu-option-save-as = 保存为
media-file-menu-add-sticker-action = 添加到贴纸...
add-sticker-popup-title = 添加到贴纸
add-sticker-popup-pack-prompt = 添加到:
add-sticker-popup-short-code-prompt = 短代码:
add-sticker-popup-short-code-exists-warning = 贴纸包里已经有这个短代码了。上面的贴纸会覆盖已有的。
add-sticker-popup-add-sticker-button = 添加
add-sticker-popup-cancel-button = 取消
add-sticker-popup-failed-prompt = 无法添加贴纸。错误代码:{ $errorCode }。错误讯息:{ $errorMsg }。
+sticker-remove-action = 移除贴纸...
+sticker-remove-confirmation-title = 移除贴纸
+sticker-remove-confirmation-message = 确定要移除贴纸「{ $shortCode }」吗?
+sticker-remove-confirm-action = 移除
+sticker-remove-cancel-action = 不移除
+sticker-remove-failed-prompt = 无法移除贴纸。错误代码:{ $errorCode }。错误讯息:{ $errorMsg }。
+
kazv-io-download-success-prompt = 下载成功
kazv-io-download-failure-prompt = 下载失败:{ $detail }
kazv-io-failure-detail-user-cancel = 用户已取消
kazv-io-failure-detail-network-error = 网络错误
kazv-io-failure-detail-open-file-error = 打开文件错误
kazv-io-failure-detail-write-file-error = 写入文件错误
kazv-io-failure-detail-hash-error = 哈息值校验失败
kazv-io-failure-detail-response-error = 接收到无效的响应
kazv-io-failure-detail-kazv-error = 未知错误,请将此报告为漏洞
kazv-io-upload-failure-prompt = 上传失败:{ $detail }
kazv-io-downloading-prompt = 正在下载:{ $fileName }
kazv-io-uploading-prompt = 正在上传:{ $fileName }
kazv-io-prompt-close = 好的
kazv-io-pause = 暂停
kazv-io-resume = 继续
kazv-io-cancel = 取消
kazv-io-progress = { $progress }/100
create-room-page-title = 创建房间
create-room-page-type-prompt = 房间类型:
create-room-page-type-public = 公开(每个人都可加入)
create-room-page-type-private = 私有(仅受邀请用户可加入)
create-room-page-type-direct = 私聊(与私有一样,但初始受邀请用户具有管理权限)
create-room-page-name-prompt = 房间名称(可选):
create-room-page-name-placeholder = 无名
create-room-page-alias-prompt = 房间别名(可选):
create-room-page-alias-placeholder = #foo:example.org
create-room-page-topic-prompt = 房间主题(可选):
create-room-page-topic-placeholder = 无题
create-room-page-allow-federate-prompt = 允许别的服务器上的用户加入
create-room-page-action-create-room = 创建房间
create-room-page-success-prompt = 房间已创建。
create-room-page-failed-prompt = 无法创建房间。错误代码:{ $errorCode }。错误讯息:{ $errorMsg }。
create-room-invite-userids-prompt = 受邀请的用户的 Matrix id:
create-room-page-add-invite-prompt = 添加一个新的受邀请用户:
create-room-page-add-invite-placeholder = Matrix id,例如 @foo:example.org
create-room-page-action-add-invite = 添加
create-room-page-action-remove-invite = 移除
create-room-page-encrypted-prompt = 启用端对端加密
join-room-page-title = 加入房间
join-room-page-id-or-alias-prompt = 房间 id 或别名:
join-room-page-id-or-alias-placeholder = #foo:example.org 或 !abcdef:example.org
join-room-page-servers-prompt = 经由服务器(可选,用换行分割):
join-room-page-servers-placeholder =
example.org
example.com
join-room-page-action-join-room = 加入房间
join-room-page-success-prompt = 成功加入房间 { $room }。
join-room-page-failed-prompt = 无法加入房间 { $room }。错误代码:{ $errorCode }。错误讯息:{ $errorMsg }。
leave-room-success-prompt = 成功离开房间 { $room }。
leave-room-failed-prompt = 无法离开房间 { $room }。错误代码:{ $errorCode }。错误讯息:{ $errorMsg }。
forget-room-success-prompt = 成功忘记房间 { $room }。
forget-room-failed-prompt = 无法忘记房间 { $room }。错误代码: { $errorCode }。错误讯息: { $errorMsg }。
user-devices = 设备
device-trust-level-unseen = 未曾见过
device-trust-level-seen = 见过
device-trust-level-verified = 已验证
device-trust-level-blocked = 已屏蔽
device-set-trust-level = 设置信任等级...
device-set-trust-level-dialog-title = 设置信任等级
device-set-trust-level-dialog-name-label = 设备名:{ $name }
device-set-trust-level-dialog-id-label = 设备id:{ $id }
device-set-trust-level-dialog-ed25519-key-label = Ed25519公钥:{ $key }
device-set-trust-level-dialog-curve25519-key-label = Curve25519公钥:{ $key }
device-set-trust-level-dialog-save = 保存
device-set-trust-level-dialog-cancel = 取消
device-verify-action = 验证设备...
device-verification-request-failed = 无法发送验证请求。错误码:{ $errorCode }。错误讯息:{ $errorMsg }。
device-item-title-deleted = (已删除) { $deviceId }
settings-page-title = 设置
settings-save = 保存设置
settings-profile-load-failed-prompt = 无法加载用户资料。错误码:{ $errorCode }。错误讯息:{ $errorMsg }。
settings-profile-change-avatar = 改变头像...
settings-profile-display-name = 显示名:
settings-profile-save-failed-prompt = 无法保存用户资料。错误码:{ $errorCode }。错误讯息:{ $errorMsg }。
settings-cache-directory = 缓存目录:
settings-select-cache-directory = 选择缓存目录...
settings-security-import-keys-action = 从备份文件导入密钥...
settings-security-import-keys-password-input-title = 输入密码
settings-security-import-keys-password-input-label = 当你创建备份时,提示了你要设置一个用来加密备份文件的密码。输入你当时设置的那个密码:
settings-security-import-keys-confirm-action = 导入
settings-security-import-keys-cancel-action = 不导入
settings-security-import-keys-success-notification = 导入了 { $imported } 个密钥。
settings-security-import-keys-failure-notification = 无法加载密钥备份文件。错误码:{ $errorCode }。错误讯息:{ $errorMsg }。
typing-indicator = { $typingUser } { $otherNum ->
[0] 正在输入...
[1] 和 { $secondTypingUser } 正在输入...
*[other] 和另外 { $otherNum } 人正在输入...
}
notification-message = <b>{ $user }:</b> { $message }
notification-message-no-content = <b>{ $user }</b> 给你发了一条讯息。
notification-open = 打开
user-page-power-level = 权限等级:{ $powerLevel }
user-page-edit-power-level-action = 编辑
user-page-save-power-level-action = 保存
user-page-discard-power-level-action = 丢弃
user-page-set-power-level-failed-prompt = 无法设置权限等级。错误码:{ $errorCode }。错误讯息:{ $errorMsg }。
user-page-kick-user-action = 踢出
user-page-kick-user-confirm-dialog-title = 踢出用户
user-page-kick-user-confirm-dialog-content = 确定要把 { $name }({ $userId })踢出 { $roomName } 吗?
user-page-kick-user-reason-prompt = 原因(可选):
user-page-kick-user-failed-prompt = 无法踢出用户。错误码:{ $errorCode }。错误讯息:{ $errorMsg }。
user-page-ban-user-action = 封禁
user-page-ban-user-confirm-dialog-title = 封禁用户
user-page-ban-user-confirm-dialog-content = 确定要在 { $roomName } 中封禁 { $name }({ $userId })吗?
user-page-ban-user-reason-prompt = 原因(可选):
user-page-ban-user-failed-prompt = 无法封禁用户。错误码:{ $error }。错误讯息:{ $errorMsg }。
user-page-unban-user-action = 解禁
user-page-unban-user-failed-prompt = 无法解禁用户。错误码:{ $error }。错误讯息:{ $errorMsg }。
user-page-overrided-name-placeholder = 自定义显示名...
user-page-save-name-override-action = 保存
user-page-update-name-override-failed-prompt = 无法设置自定义显示名。错误码:{ $error }。错误讯息:{ $errorMsg }。
user-page-self-name-prompt = 自己在房间里的显示名:
user-page-self-name-placeholder = 自己在房间里的显示名...
user-page-save-self-name-action = 保存
user-page-update-self-name-failed-prompt = 无法设置自己在房间里的显示名。错误码:{ $error }。错误讯息:{ $errorMsg }。
room-invite-page-title = 邀请用户到 { $room }
room-invite-page-invite-failed-prompt = 无法邀请用户。错误码:{ $errorCode }。错误讯息:{ $errorMsg }。
room-invite-invitee-matrix-id-placeholder = 用户的 Matrix id,比如 @foo:example.org
room-invite-invitee-matrix-id-prompt = 要邀请的用户:
room-invite-page-invite-button = 邀请
room-invite-action = 邀请到房间...
room-explore-state-action = 查看完整房间状态...
room-state-page-title = { $room } 的状态
room-state-page-state-key = 状态键:「<code>{ $stateKey }</code>」
confirm-upload-popup-title = 确认上传
confirm-upload-popup-prompt = 即将上传「{ $file }」({ $current } / { $total })。
confirm-upload-popup-prompt-single-file = 即将上传「{ $file }」。
confirm-upload-popup-accept-button = 上传
confirm-upload-popup-cancel-button = 取消
action-cut = 剪切
action-copy = 复制
action-paste = 粘贴
action-undo = 撤销
action-redo = 重做
action-delete = 删除
action-select-all = 全选
logout-failed-prompt = 登出失败,请检查您的网络后重试。错误码:{ $errorCode }。错误讯息:{ $errorMsg }。
confirm-logout-popup-title = 确认登出
confirm-logout-popup-prompt = 你确定要登出吗?
confirm-logout-popup-accept-button = 是
confirm-logout-popup-cancel-button = 取消
emoji-smileys-and-emotion = 笑容与情绪
emoji-people-and-body = 人与身体
emoji-animals-and-nature = 动物与自然
emoji-food-and-drink = 食物与饮料
emoji-travel-and-places = 旅行与地点
emoji-activities = 活动
emoji-objects = 物体
emoji-symbols = 符号
emoji-flags = 旗帜
confirm-deletion-popup-title = 删除事件
confirm-deletion-popup-message = 你确定要删除这个事件吗?
confirm-deletion-popup-confirm-action = 删除
confirm-deletion-popup-cancel-action = 取消
get-room-id-by-alias-failed-prompt = 无法通过房间别名获取房间 id。错误码:{ $errorCode }。错误讯息:{ $errorMsg }。
verifications-page-title = 设备验证
verification-state-they-requested = 请求验证了此设备。
verification-state-waiting = 正在等待另一方。
verification-state-waiting-after-verified = 正在等待另一方。
verification-state-confirm-code = 请确认这些代码是否和另一个设备上一致。
verification-state-done = 过程已经完成。
verification-state-cancelled = 过程被取消了。
verification-action-ready = 继续
verification-action-cancel = 取消
verification-action-confirm-sas-match = 是一致的
verification-action-deny-sas-match = 并不一致
verification-show-numbers = 转而显示数字
verification-show-emojis = 转而显示表情文字
diff --git a/src/l10n/en/100-ui.ftl b/src/l10n/en/100-ui.ftl
index 19bbc87..7e237a3 100644
--- a/src/l10n/en/100-ui.ftl
+++ b/src/l10n/en/100-ui.ftl
@@ -1,495 +1,502 @@
### This file is part of kazv.
### SPDX-FileCopyrightText: 2020-2023 tusooa <tusooa@kazv.moe>
### SPDX-License-Identifier: AGPL-3.0-or-later
app-title-with-user-id = { -kt-app-name } - { $userId }
app-title-not-logged-in = { -kt-app-name } - Not Logged In
about-page-title = About { -kt-app-name }
about-copyright = (c) 2020- the Kazv Project
about-display-name = { -kt-app-name } { $version }
about-short-description = Convergent Matrix client and instant messaging app
about-license = licensed under AGPL 3 or later
about-used-libraries = Used libraries
about-authors = Authors
about-author-email-action = Write an email
about-author-website-action = Access website
about-author-task-maintainer = Maintainer
about-author-task-developer = Developer
user-name-with-id = { $name } ({ $userId })
user-name-overrided = { $overridedName } ({ $globalName })
global-drawer-title = { -kt-app-name }
global-drawer-action-switch-account = Switch account
global-drawer-action-hard-logout = Logout
global-drawer-action-save-session = Save current session
global-drawer-action-configure-shortcuts = Configure shortcuts
global-drawer-action-settings = Settings
global-drawer-action-create-room = Create room
global-drawer-action-join-room = Join room
global-drawer-action-verifications = Device Verification
global-drawer-action-about = About { -kt-app-name }
action-settings-page-title = Configure shortcuts
action-settings-shortcut-prompt = Shortcut: { $shortcut }
action-settings-shortcut-none = (none)
action-settings-shortcut-edit-action = Edit
action-settings-shortcut-remove-action = Clear
action-settings-shortcut-conflict-modal-title = Conflicting shortcuts
action-settings-shortcut-conflict = The shortcut { $shortcut } has conflicts with other actions. <br>If you continue, the shortcuts for other actions will be cleared. <br><br>Conflicting actions: <br>{ $conflictingAction }
action-settings-shortcut-conflict-continue = Continue
action-settings-shortcut-conflict-cancel = Cancel
empty-room-page-title = No rooms selected
empty-room-page-description = There is no room selected now.
login-page-title = Log in
login-page-userid-prompt = User id:
login-page-userid-input-placeholder = E.g.: @foo:example.org
login-page-password-prompt = Password:
login-page-login-button = Log in
login-page-close-button = Close
login-page-existing-sessions-prompt = Choose from one of the existing sessions:
login-page-alternative-password-login-prompt = Or start a new session with user id and password:
login-page-restore-session-button = Restore session
login-page-request-failed-prompt = Login failed. Error code: { $errorCode }. Error message: { $errorMsg }.
login-page-discover-failed-enter-prompt = Unable to detect the server this user is on, or the server is unavailable. Error code: { $errorCode }. Error message: { $errorMsg }. Please enter the server url manually.
login-page-server-url-placeholder = E.g.: https://example.org
login-page-server-url-prompt = Server url (optional):
login-page-next-action = Next
login-page-server-label = Connecting to { $serverUrl }
login-page-login-flow-choice = Choose a way to log in:
login-page-login-flow-password = Password
login-page-login-flow-sso = Single Sign On
login-page-login-with-sso-button = Log in using Single Sign On
login-page-login-sso-redirect-url-label = You should see a web page open in your browser. Follow the instructions on that page. If not, manually open the following URL:
login-page-change-server-button = Change server
login-page-no-flow-supported = This server does not have any login flow that { -kt-app-name } supports.
session-load-failure-not-found = The session { $sessionName } is not found.
session-load-failure-format-unknown = The session { $sessionName } contains an unsupported format. Is it saved using a future version of { -kt-app-name }?
session-load-failure-cannot-backup = Cannot make a backup of the session { $sessionName }.
session-load-failure-lock-failed = The session { $sessionName } is being used by another program.
session-load-failure-cannot-open-file = Unable to open the store file for the session { $sessionName }.
session-load-failure-deserialize-failed = The session { $sessionName } cannot be opened. Is it corrupted or saved using a future version of { -kt-app-name }?
main-page-title = { -kt-app-name } - { $userId }
main-page-recent-tab-title = Recent
main-page-favourites-tab-title = Favourites
main-page-people-tab-title = People
main-page-rooms-tab-title = Rooms
main-page-room-filter-prompt = Filter rooms by...
room-list-view-room-item-title-name = { $name }
room-list-view-room-item-title-heroes = { $hero } { $otherNum ->
[0] { "" }
[1] and { $secondHero }
*[other] and { $otherNum } others
}
room-list-view-room-item-title-id = Unnamed room ({ $roomId })
room-list-view-room-item-fav-action = Set as favourite
room-list-view-room-item-unread-indicator = Unread
room-list-view-room-item-unread-notification-count = { $count }
room-list-view-room-item-unread-notification-count-text = { $count } unread {
$count ->
[1] message
*[other] messages
}
room-tags-fav-action-notification = Set { $name } as favourite
room-tags-fav-action-notification-failed = Cannot set { $name } as favourite. Error code: { $errorCode }. Error message: { $errorMsg }.
room-tags-unfav-action-notification = Removed { $name } from favourites
room-tags-unfav-action-notification-failed = Cannot remove { $name } from favourites. Error code: { $errorCode }. Error message: { $errorMsg }.
room-tags-add-tag-action-notification = Added tag { $tag } to { $name }
room-tags-add-tag-action-notification-failed = Cannot add tag { $tag } to { $name }. Error code: { $errorCode }. Error message: { $errorMsg }.
room-tags-remove-tag-action-notification = Removed tag { $tag } from { $name }
room-tags-remove-tag-action-notification-failed = Cannot remove tag { $tag } from { $name }. Error code: { $errorCode }. Error message: { $errorMsg }.
room-list-view-room-item-more-action = More...
room-list-view-room-item-invited = (Invited)
room-list-view-room-item-left = (Left)
room-list-view-room-item-tombstone = (Deprecated)
room-settings-action = Room settings...
room-settings-page-title = Room settings for { $room }
room-settings-tags = Room tags
room-settings-favourited = Set as favourite
room-settings-remove-tag = Remove tag
room-settings-add-tag = Add tag
room-settings-members-action = Room members...
room-settings-banned-members-action = Banned members...
room-settings-enable-encryption-prompt-dialog-title = Enabling encryption
room-settings-enable-encryption-prompt-dialog-prompt = Once you enable encryption in this room, you cannot disable it again. Are you sure?
room-settings-enable-encryption-action = Enable encryption
room-settings-encrypted = Messages in this room are end-to-end encrypted.
room-settings-not-encrypted = Messages in this room are not end-to-end-encrypted.
room-settings-encryption-enabled-notification = Encryption is now enabled in this room.
room-settings-encryption-failed-to-enable-notification = Cannot enable encryption in this room. Error code: { $errorCode }. Error message: { $errorMsg }.
room-settings-name-missing = This room dose not have a name.
room-settings-topic-missing = This room does not have a topic.
room-settings-edit-name-action = Edit name
room-settings-edit-topic-action = Edit topic
room-settings-save-name-action = Save name
room-settings-save-topic-action = Save topic
room-settings-discard-name-action = Discard name
room-settings-discard-topic-action = Discard topic
room-settings-set-name-failed-prompt = Cannot set name. Error code: { $errorCode }. Error message: { $errorMsg }.
room-settings-set-topic-failed-prompt = Cannot set topic. Error code: { $errorCode }. Error message: { $errorMsg }.
room-sticker-packs-action = Sticker packs...
room-sticker-packs-page-title = Sticker packs in { $room }
room-sticker-packs-use-action = Use sticker pack
room-sticker-packs-use-failed = Failed to set sticker packs in use. Error code: { $errorCode }. Error message: { $errorMsg }.
room-sticker-packs-page-add-action = Add sticker pack
room-sticker-packs-page-add-popup-title = Add sticker pack
room-sticker-packs-page-add-success = Sticker pack added.
room-sticker-packs-page-add-failed = Cannot add sticker pack. Error code: { $errorCode }. Error message: { $errorMsg }.
room-sticker-packs-page-add-popup-name-prompt = Sticker pack name:
room-sticker-packs-page-add-popup-state-key-prompt = State key:
room-member-list-page-title = Members of { $room }
room-leave-action = Leave room
room-leave-confirm-popup-title = Leaving room
room-leave-confirm-popup-message = Are you sure you want to leave this room?
room-leave-confirm-popup-confirm-action = Leave
room-leave-confirm-popup-cancel-action = Stay
room-forget-action = Forget room
room-forget-confirm-popup-title = Forgetting room
room-forget-confirm-popup-message = Are you sure you want to forget this room?
room-forget-confirm-popup-confirm-action = Forget
room-forget-confirm-popup-cancel-action = Cancel
send-message-box-input-placeholder = Type your message here...
send-message-box-send = Send
send-message-box-send-file = Send file
send-message-box-reply-to = Replying to
send-message-box-edit = Editing
send-message-box-remove-reply-to-action = Remove reply-to relationship
send-message-box-remove-replace-action = Cancel editing
send-message-box-stickers = Send a sticker...
send-message-box-stickers-popup-title = Send a sticker
save-draft-confirmation-title = Save draft
save-draft-confirmation = You have an unsent draft, do you want to save it to restore it later?
save-draft-confirm-action = Save it
save-draft-discard-action = Discard it
save-draft-cancel-action = Do nothing
discard-draft-confirmation-title = Discard draft
discard-draft-confirmation = You have an unsent draft, your operation will dicard it.
discard-draft-confirm-action = Discard it
discard-draft-keep-action = Keep it
discard-draft-cancel-action = Do nothing
sticker-picker-user-stickers = My stickers
sticker-picker-room-sticker-pack-name = {$stateKey} in {$room}
sticker-picker-room-default-sticker-pack-name = Default pack in {$room}
room-timeline-load-more-action = Load more
room-timeline-scroll-to-latest-action = Scroll to latest message
room-invite-accept-action = Accept invite
room-invite-reject-action = Reject invite
room-invite-popup-title = Invited
room-invite-popup-text = You are invited to join this room.
room-invite-popup-text-with-inviter = You are invited to join this room by { $inviterName }.
room-pinned-events-action = { $count } pinned { $count ->
[1] message
*[other] messages
}...
room-pinned-events-page-title = Pinned messages of { $room }
## State events
## Common parameters:
## gender = gender of the sender (male/female/neutral)
## stateKeyUser = name of the state key user
## stateKeyUserGender = gender of the state key user
member-state-joined-room = joined the room.
member-state-changed-name-and-avatar = changed { $gender ->
[male] his
[female] her
*[neutral] their
} name and avatar.
member-state-changed-name = changed { $gender ->
[male] his
[female] her
*[neutral] their
} name.
member-state-changed-avatar = changed { $gender ->
[male] his
[female] her
*[neutral] their
} avatar.
member-state-invited = invited { $stateKeyUser } to the room.
member-state-left = left the room.
member-state-kicked = kicked { $stateKeyUser }.
member-state-banned = banned { $stateKeyUser }.
member-state-unbanned = unbanned { $stateKeyUser }.
state-room-created = created the room.
state-room-name-changed = changed the name of the room to { $newName }.
state-room-topic-changed = changed the topic of the room to { $newTopic }.
state-room-avatar-changed = changed the avatar of the room.
state-room-pinned-events-changed = changed the pinned events of the room.
state-room-alias-changed = changed the aliases of the room.
state-room-join-rules-changed = changed join rules of the room.
state-room-power-levels-changed = changed power levels of the room.
state-room-encryption-activated = enabled encryption for this room.
event-message-image-sent = sent an image "{ $body }".
event-message-sticker-sent = sent a sticker "{ $body }".
event-summary-image-sent = sent an image "{ $body }".
event-summary-sticker-sent = sent a sticker "{ $body }".
event-message-file-sent = sent a file "{ $body }".
event-message-video-sent = sent a video "{ $body }".
event-summary-video-sent = sent a video "{ $body }".
event-message-audio-sent = sent an audio "{ $body }".
event-message-audio-play-audio = Play audio
event-sending = Sending...
event-send-failed = Failed to send
event-resend = Retry sending this event
event-deleted = (Deleted)
event-delete = Delete
event-delete-failed = Error deleting event. Error code: { $errorCode }. Error message: { $errorMsg }.
event-view-source = View source...
event-view-history = View edit history...
event-source-popup-title = Event source
event-source-decrypted = Decrypted event source
event-source-original = Original event source
event-history-popup-title = Event edit history
event-reply-action = Reply
event-popup-action = More about this event...
event-reacted-with = Reacted with "{ $key }"
event-react-action = React...
event-react-popup-title = React to a message
event-react-accept-action = React
event-react-cancel-action = Cancel
event-react-with-prompt = React with:
event-react-button-description = React with { $key }
event-edit-action = Edit
event-encrypted = This message is encrypteed
event-msgtype-fallback-text = Unknown message type: { $msgtype }
event-fallback-text = Unknown event: { $type }
event-cannot-decrypt-text = (Encrypted content)
event-read-indicator-more = +{ $rest }
event-read-indicator-list-title = Read by { $numUsers ->
[1] 1 user
*[other] { $numUsers } users
}
event-edited-indicator = (edited)
event-pin-action = Pin to room
event-unpin-action = Unpin from room
event-pin-confirmation-title = Pin to room
event-pin-confirmation = Are you sure you want to pin this message?
event-pin-confirm-action = Pin
event-pin-cancel-action = Do not pin
event-pin-success-prompt = Message pinned to room.
event-pin-failed-prompt = Unable to pin message. Error code: { $errorCode }. Error message: { $errorMsg }.
event-unpin-confirmation-title = Unpin from room
event-unpin-confirmation = Are you sure you want to unpin this message?
event-unpin-confirm-action = Unpin
event-unpin-cancel-action = Do not unpin
event-unpin-success-prompt = Message unpinned from room.
event-unpin-failed-prompt = Unable to unpin message. Error code: { $errorCode }. Error message: { $errorMsg }.
media-file-menu-option-view = View
media-file-menu-option-save-as = Save as
media-file-menu-add-sticker-action = Add to sticker...
add-sticker-popup-title = Add to sticker
add-sticker-popup-pack-prompt = Add to pack:
add-sticker-popup-short-code-prompt = Short code:
add-sticker-popup-short-code-exists-warning = The short code already exists in this pack. The sticker above will override the existing one.
add-sticker-popup-add-sticker-button = Add
add-sticker-popup-cancel-button = Cancel
add-sticker-popup-failed-prompt = Unable to add sticker. Error code: { $errorCode }. Error message: { $errorMsg }.
+sticker-remove-action = Remove sticker...
+sticker-remove-confirmation-title = Remove sticker
+sticker-remove-confirmation-message = Are you sure you want to remove the sticker "{ $shortCode }"?
+sticker-remove-confirm-action = Remove
+sticker-remove-cancel-action = Do not remove
+sticker-remove-failed-prompt = Unable to remove sticker. Error code: { $errorCode }. Error message: { $errorMsg }.
+
kazv-io-download-success-prompt = Download successful
kazv-io-download-failure-prompt = Download failure: { $detail }
kazv-io-failure-detail-user-cancel = User canceled
kazv-io-failure-detail-network-error = Network error
kazv-io-failure-detail-open-file-error = Open file error
kazv-io-failure-detail-write-file-error = Write file error
kazv-io-failure-detail-hash-error = Hash check error
kazv-io-failure-detail-response-error = Get an invalid response
kazv-io-failure-detail-kazv-error = Unknow Error, please report this as bug.
kazv-io-upload-failure-prompt = Upload failure: { $detail }
kazv-io-downloading-prompt = Downloading: { $fileName }
kazv-io-uploading-prompt = Uploading: { $fileName }
kazv-io-prompt-close = Got it
kazv-io-pause = Pause
kazv-io-resume = Resume
kazv-io-cancel = Cancel
kazv-io-progress = { $progress }/100
create-room-page-title = Create room
create-room-page-type-prompt = Room type:
create-room-page-type-public = Public (everyone can join)
create-room-page-type-private = Private (only invited users can join)
create-room-page-type-direct = Direct message (same as private, but those initially invited get admin permissions)
create-room-page-name-prompt = Room name (optional):
create-room-page-name-placeholder = No name
create-room-page-alias-prompt = Room alias (optional):
create-room-page-alias-placeholder = #foo:example.org
create-room-page-topic-prompt = Room topic (optional):
create-room-page-topic-placeholder = No topic
create-room-page-allow-federate-prompt = Allow users from other servers to join
create-room-page-action-create-room = Create room
create-room-page-success-prompt = Room created.
create-room-page-failed-prompt = Unable to create room. Error code: { $errorCode }. Error message: { $errorMsg }.
create-room-invite-userids-prompt = Matrix ids of users to invite:
create-room-page-add-invite-prompt = Add a new user to invite:
create-room-page-add-invite-placeholder = Matrix id, e.g. @foo:example.org
create-room-page-action-add-invite = Add
create-room-page-action-remove-invite = Remove
create-room-page-encrypted-prompt = Enable end-to-end encryption
join-room-page-title = Join room
join-room-page-id-or-alias-prompt = Room id or alias:
join-room-page-id-or-alias-placeholder = #foo:example.org or !abcdef:example.org
join-room-page-servers-prompt = Via servers (optional, separated by newlines):
join-room-page-servers-placeholder =
example.org
example.com
join-room-page-action-join-room = Join room
join-room-page-success-prompt = Successfully joined room { $room }.
join-room-page-failed-prompt = Unable to join room { $room }. Error code: { $errorCode }. Error message: { $errorMsg }.
leave-room-success-prompt = Successfully left room { $room }.
leave-room-failed-prompt = Unable to leave room { $room }. Error code: { $errorCode }. Error message: { $errorMsg }.
forget-room-success-prompt = Successfully forgot room { $room }.
forget-room-failed-prompt = Unable to forget room { $room }. Error code: { $errorCode }. Error message: { $errorMsg }.
user-devices = Devices
device-trust-level-unseen = Unseen
device-trust-level-seen = Seen
device-trust-level-verified = Verified
device-trust-level-blocked = Blocked
device-set-trust-level = Set trust level...
device-set-trust-level-dialog-title = Set trust level
device-set-trust-level-dialog-name-label = Device name: { $name }
device-set-trust-level-dialog-id-label = Device id: { $id }
device-set-trust-level-dialog-ed25519-key-label = Ed25519 public key: { $key }
device-set-trust-level-dialog-curve25519-key-label = Curve25519 public key: { $key }
device-set-trust-level-dialog-save = Save
device-set-trust-level-dialog-cancel = Cancel
device-verify-action = Verify device...
device-verification-request-failed = Unable to send verification request. Error code: { $errorCode }. Error message: { $errorMsg }.
device-item-title-deleted = (Deleted) { $deviceId }
settings-page-title = Settings
settings-save = Save settings
settings-profile-load-failed-prompt = Unable to load profile. Error code: { $errorCode }. Error message: { $errorMsg }.
settings-profile-change-avatar = Change avatar...
settings-profile-display-name = Display name:
settings-profile-save-failed-prompt = Unable to save profile. Error code: { $errorCode }. Error message: { $errorMsg }.
settings-cache-directory = Cache directory:
settings-select-cache-directory = Select cache directory...
settings-security-import-keys-action = Import keys from a backup file...
settings-security-import-keys-password-input-title = Enter password
settings-security-import-keys-password-input-label = When you created the backup, you were prompted to set a password to encrypt the backup file. Enter the password you set at that time:
settings-security-import-keys-confirm-action = Import
settings-security-import-keys-cancel-action = Do not import
settings-security-import-keys-success-notification = Imported { $imported } keys.
settings-security-import-keys-failure-notification = Unable to load key backup file. Error code: { $errorCode }. Error message: { $errorMsg }.
typing-indicator = { $typingUser } { $otherNum ->
[0] is typing...
[1] and { $secondTypingUser } are typing...
*[other] and { $otherNum } others are typing...
}
notification-message = <b>{ $user }:</b> { $message }
notification-message-no-content = <b>{ $user }</b> sent you a message.
notification-open = Open
user-page-power-level = Power level: { $powerLevel }
user-page-edit-power-level-action = Edit
user-page-save-power-level-action = Save
user-page-discard-power-level-action = Discard
user-page-set-power-level-failed-prompt = Unable to set power level. Error code: { $errorCode }. Error message: { $errorMsg }.
user-page-kick-user-action = Kick
user-page-kick-user-confirm-dialog-title = Kicking user
user-page-kick-user-confirm-dialog-content = Are you sure you want to kick { $name } ({ $userId }) out of { $roomName }?
user-page-kick-user-reason-prompt = Reason (optional):
user-page-kick-user-failed-prompt = Unable to kick user. Error code: { $errorCode }. Error message: { $errorMsg }.
user-page-ban-user-action = Ban
user-page-ban-user-confirm-dialog-title = Banning user
user-page-ban-user-confirm-dialog-content = Are you sure you want to ban { $name } ({ $userId }) in { $roomName }?
user-page-ban-user-reason-prompt = Reason (optional):
user-page-ban-user-failed-prompt = Unable to ban user. Error code: { $errorCode }. Error message: { $errorMsg }.
user-page-unban-user-action = Unban
user-page-unban-user-failed-prompt = Unable to unban user. Error code: { $errorCode }. Error message: { $errorMsg }.
user-page-overrided-name-placeholder = Custom display name...
user-page-save-name-override-action = Save
user-page-update-name-override-failed-prompt = Unable to set custom display name. Error code: { $errorCode }. Error message: { $errorMsg }.
user-page-self-name-prompt = Own display name in room:
user-page-self-name-placeholder = Own display name in room...
user-page-save-self-name-action = Save
user-page-update-self-name-failed-prompt = Unable to set own display name in room. Error code: { $errorCode }. Error message: { $errorMsg }.
room-invite-page-title = Inviting user to { $room }
room-invite-page-invite-failed-prompt = Unable to invite user. Error code: { $errorCode }. Error message: { $errorMsg }.
room-invite-invitee-matrix-id-placeholder = Matrix id of the user, e.g. @foo:example.org
room-invite-invitee-matrix-id-prompt = User to invite:
room-invite-page-invite-button = Invite
room-invite-action = Invite to room...
room-explore-state-action = View full room state...
room-state-page-title = Room state of { $room }
room-state-page-state-key = State key: "<code>{ $stateKey }</code>"
confirm-upload-popup-title = Confirm upload
confirm-upload-popup-prompt = You are about to upload "{ $file }" ({ $current } / { $total }).
confirm-upload-popup-prompt-single-file = You are about to upload "{ $file }".
confirm-upload-popup-accept-button = Upload
confirm-upload-popup-cancel-button = Cancel
action-cut = Cut
action-copy = Copy
action-paste = Paste
action-undo = Undo
action-redo = Redo
action-delete = Delete
action-select-all = Select All
logout-failed-prompt = Logout failed. Please check your network and try again. Error code: { $errorCode }, error message: { $errorMsg }.
confirm-logout-popup-title = Confirm logout
confirm-logout-popup-prompt = Are you sure to logout?
confirm-logout-popup-accept-button = Yes
confirm-logout-popup-cancel-button = Cancel
emoji-smileys-and-emotion = Smiley && Emotion
emoji-people-and-body = People && Body
emoji-animals-and-nature = Animals && Nature
emoji-food-and-drink = Food && Drink
emoji-travel-and-places = Travel && Places
emoji-activities = Activities
emoji-objects = Objects
emoji-symbols = Symbols
emoji-flags = Flags
confirm-deletion-popup-title = Delete event
confirm-deletion-popup-message = Are you sure you want to delete this event?
confirm-deletion-popup-confirm-action = Delete
confirm-deletion-popup-cancel-action = Cancel
get-room-id-by-alias-failed-prompt = Unable to get room id by alias. Error code: { $errorCode }. Error message: { $errorMsg }.
verifications-page-title = Device Verification
verification-state-they-requested = requested to verify this device.
verification-state-waiting = Waiting for the other party.
verification-state-waiting-after-verified = Waiting for the other party.
verification-state-confirm-code = Please confirm whether the codes match the other device.
verification-state-done = The process is done.
verification-state-cancelled = The process is cancelled.
verification-action-ready = Continue
verification-action-cancel = Cancel
verification-action-confirm-sas-match = They match
verification-action-deny-sas-match = They do not match
verification-show-numbers = Show numbers instead
verification-show-emojis = Show emojis instead
diff --git a/src/matrix-sticker-pack.cpp b/src/matrix-sticker-pack.cpp
index 65bd294..23d370a 100644
--- a/src/matrix-sticker-pack.cpp
+++ b/src/matrix-sticker-pack.cpp
@@ -1,123 +1,138 @@
/*
* This file is part of kazv.
* SPDX-FileCopyrightText: 2020-2023 tusooa <tusooa@kazv.moe>
* SPDX-License-Identifier: AGPL-3.0-or-later
*/
#include <kazv-defs.hpp>
#include <cursorutil.hpp>
#include "helper.hpp"
#include "matrix-sticker.hpp"
#include "matrix-event.hpp"
#include "matrix-sticker-pack.hpp"
using namespace Qt::Literals::StringLiterals;
using namespace Kazv;
MatrixStickerPack::MatrixStickerPack(lager::reader<MatrixStickerPackSource> source, QObject *parent)
: KazvAbstractListModel(parent)
, m_source(source)
, m_event(m_source[&MatrixStickerPackSource::event])
, m_images(m_event.xform(
eventContent
| zug::map([](const JsonWrap &content) {
if (content.get().contains("images")
&& content.get()["images"].is_object()) {
auto size = content.get()["images"].size();
auto items = content.get()["images"].items();
auto array = json(size, json::object());
std::transform(
items.begin(), items.end(),
array.begin(), [](const auto &it) {
return json::array({it.key(), it.value()});
}
);
return array;
} else {
return json::array();
}
})))
, LAGER_QT(packName)(m_event.xform(eventContent | jsonAtOr("/pack/display_name"_json_pointer, std::string()) | strToQt))
, LAGER_QT(isAccountData)(m_source.map([](const auto &source) {
return source.source == MatrixStickerPackSource::AccountData;
}))
, LAGER_QT(isState)(m_source.map([](const auto &source) {
return source.source == MatrixStickerPackSource::RoomState;
}))
, LAGER_QT(roomId)(m_source[&MatrixStickerPackSource::roomId].xform(strToQt))
, LAGER_QT(stateKey)(m_source[&MatrixStickerPackSource::stateKey].xform(strToQt))
{
initCountCursor(m_images.map([](const JsonWrap &images) -> int {
return images.get().size();
}));
}
MatrixStickerPack::~MatrixStickerPack() = default;
MatrixSticker *MatrixStickerPack::at(int index) const
{
using namespace nlohmann::literals;
auto image = m_images.map([index](const json &j) {
if (j.size() > std::size_t(index)) {
return j[index];
} else {
return json::array();
}
}).make();
return new MatrixSticker(
image.xform(jsonAtOr("/0"_json_pointer, json(std::string())) | zug::map([](const json &j) {
return j.template get<std::string>();
})),
image.xform(jsonAtOr("/1"_json_pointer, json::object()))
);
}
bool MatrixStickerPack::hasShortCode(const QString &shortCode) const
{
return m_event.map([shortCode=shortCode.toStdString()](const Event &e) {
auto content = e.content();
return content.get().contains("images")
&& content.get()["images"].is_object()
&& content.get()["images"].contains(shortCode);
})
.make().get();
}
QVariant MatrixStickerPack::addSticker(const QString &shortCode, MatrixEvent *event) const
{
if (!event) {
return QVariant::fromValue(m_source.get());
}
auto source = m_source.get();
auto eventJson = source.event.raw().get();
eventJson.merge_patch(json{
{"content", {
{"images", {
{shortCode.toStdString(), json::object()},
}},
}},
});
auto &sticker = eventJson["content"]["images"][shortCode.toStdString()];
auto content = event->content();
auto body = event->content()[u"body"_s];
if (!body.isUndefined()) {
sticker["body"] = body;
}
auto url = event->content()[u"url"_s];
if (!url.isUndefined()) {
sticker["url"] = url;
}
auto info = event->content()[u"info"_s];
if (!info.isUndefined()) {
sticker["info"] = info;
}
source.event = Event(eventJson);
return QVariant::fromValue(source);
}
+
+QVariant MatrixStickerPack::removeSticker(const QString &shortCode) const
+{
+ auto source = m_source.get();
+
+ auto eventJson = source.event.raw().get();
+ if (eventJson.contains("/content/images"_json_pointer)
+ && eventJson["content"]["images"].is_object()) {
+ eventJson["content"]["images"].erase(shortCode.toStdString());
+ }
+
+ source.event = Event(eventJson);
+
+ return QVariant::fromValue(source);
+}
diff --git a/src/matrix-sticker-pack.hpp b/src/matrix-sticker-pack.hpp
index aa0a2b1..5f1c001 100644
--- a/src/matrix-sticker-pack.hpp
+++ b/src/matrix-sticker-pack.hpp
@@ -1,59 +1,68 @@
/*
* This file is part of kazv.
* SPDX-FileCopyrightText: 2024 tusooa <tusooa@kazv.moe>
* SPDX-License-Identifier: AGPL-3.0-or-later
*/
#pragma once
#include <kazv-defs.hpp>
#include <QObject>
#include <QQmlEngine>
#include <lager/reader.hpp>
#include <lager/extra/qt.hpp>
#include <base/event.hpp>
#include "kazv-abstract-list-model.hpp"
#include "matrix-sticker-pack-source.hpp"
#include "meta-types.hpp"
Q_MOC_INCLUDE("matrix-sticker.hpp")
Q_MOC_INCLUDE("matrix-event.hpp")
class MatrixSticker;
class MatrixEvent;
class MatrixStickerPack : public KazvAbstractListModel
{
Q_OBJECT
QML_ELEMENT
QML_UNCREATABLE("")
lager::reader<MatrixStickerPackSource> m_source;
lager::reader<Kazv::Event> m_event;
lager::reader<Kazv::json> m_images;
public:
explicit MatrixStickerPack(lager::reader<MatrixStickerPackSource> source, QObject *parent = 0);
~MatrixStickerPack() override;
Q_INVOKABLE MatrixSticker *at(int index) const;
LAGER_QT_READER(QString, packName);
LAGER_QT_READER(bool, isAccountData);
LAGER_QT_READER(bool, isState);
LAGER_QT_READER(QString, roomId);
LAGER_QT_READER(QString, stateKey);
Q_INVOKABLE bool hasShortCode(const QString &shortCode) const;
/**
* Add a sticker to the pack and return the source of the
* modified pack.
*
* @param shortCode The short code of the sticker.
* @param event The event of the sticker.
* @return The MatrixStickerPackSource of the modified pack.
*/
Q_INVOKABLE QVariant addSticker(const QString &shortCode, MatrixEvent *event) const;
+
+ /**
+ * Remove a sticker from the pack by short code and return the
+ * source of the modified pack.
+ *
+ * @param shortCode The short code of the sticker to remove.
+ * @return The MatrixStickerPackSource of the modified pack.
+ */
+ Q_INVOKABLE QVariant removeSticker(const QString &shortCode) const;
};
diff --git a/src/tests/CMakeLists.txt b/src/tests/CMakeLists.txt
index 93d0a08..878d198 100644
--- a/src/tests/CMakeLists.txt
+++ b/src/tests/CMakeLists.txt
@@ -1,154 +1,155 @@
set(KAZV_TEST_TEMP_DIR ${CMAKE_CURRENT_BINARY_DIR}/test-temp)
configure_file(test-temp.hpp.in test-temp.hpp)
set(kazvtestlib_SRCS
test-model.cpp
)
add_library(kazvtestlib STATIC ${kazvtestlib_SRCS})
target_include_directories(kazvtestlib PUBLIC ${CMAKE_CURRENT_BINARY_DIR})
target_link_libraries(kazvtestlib PUBLIC kazvprivlib libkazv::kazvtestfixtures)
add_executable(matrix-sdk-session-loader matrix-sdk-session-loader.cpp matrix-sdk-sessions-test.cpp)
target_compile_definitions(matrix-sdk-session-loader PRIVATE MATRIX_SDK_SESSIONS_TEST_NO_MAIN)
target_link_libraries(matrix-sdk-session-loader Qt${QT_MAJOR_VERSION}::Test kazvtestlib)
ecm_add_tests(
qt-job-handler-test.cpp
qt-promise-handler-test.cpp
qt-json-test.cpp
device-mgmt-test.cpp
matrix-session-test.cpp
matrix-session-controller-test.cpp
matrix-sdk-sessions-test.cpp
matrix-promise-test.cpp
matrix-room-timeline-test.cpp
matrix-room-member-list-model-test.cpp
matrix-room-list-test.cpp
matrix-room-state-test.cpp
kazv-markdown-test.cpp
matrix-sticker-pack-test.cpp
matrix-link-test.cpp
matrix-user-given-attrs-map-test.cpp
kazv-abstract-list-model-test.cpp
matrix-event-list-test.cpp
matrix-event-test.cpp
kazv-file-test.cpp
db-store-test.cpp
sso-login-process-test.cpp
LINK_LIBRARIES Qt${QT_MAJOR_VERSION}::Test kazvtestlib
)
ecm_add_test(
kazv-io-job-test.cpp
LINK_LIBRARIES Qt${QT_MAJOR_VERSION}::Test Qt${QT_MAJOR_VERSION}::HttpServer kazvtestlib
)
ecm_add_test(
kazv-io-manager-test.cpp
LINK_LIBRARIES Qt${QT_MAJOR_VERSION}::Test Qt${QT_MAJOR_VERSION}::HttpServer kazvtestlib
)
add_executable(quicktest quick-test.cpp)
target_link_libraries(quicktest PRIVATE Qt${QT_MAJOR_VERSION}::QuickTest kazvtestlib)
if(KAZV_BUILD_BENCHMARKS)
ecm_add_test(
matrix-room-timeline-benchmark-test.cpp
LINK_LIBRARIES Qt${QT_MAJOR_VERSION}::Test kazvtestlib
)
endif()
set(quicktest_QMLS
tst_About.qml
tst_AddStickerPopup.qml
tst_AvatarImageAdapters.qml
tst_ConfirmUploadPopup.qml
tst_CreateRoomPage.qml
tst_DeviceList.qml
tst_DevicePopup.qml
tst_Device.qml
tst_EventHistoryView.qml
tst_EventReactions.qml
tst_EventReadIndicator.qml
tst_EventSourceView.qml
tst_EventView.qml
tst_EventViewRedacted.qml
tst_EventViewVideo.qml
tst_FileHandler.qml
tst_JoinRoomPage.qml
tst_KazvIOMenu.qml
tst_kazvshortcutsActionItem.qml
tst_LoginPage.qml
tst_MatrixHelpers.qml
tst_Notifications.qml
tst_PageManager.qml
tst_ReactToEventPopup.qml
tst_RoomInvitePage.qml
tst_RoomListViewItemDelegate.qml
tst_RoomMemberListPage.qml
tst_RoomNameProvider.qml
tst_RoomPage.qml
tst_RoomPinnedEventsPage.qml
tst_RoomSettingsPage.qml
tst_RoomStatePage.qml
tst_RoomStickerPackItemDelegate.qml
tst_RoomStickerPacksPage.qml
tst_RoomTimelineView.qml
tst_SendMessageBoxDrafts.qml
tst_SendMessageBox.qml
tst_StickerPackNameProvider.qml
+ tst_StickerPicker.qml
tst_TypingIndicator.qml
tst_UserNameProvider.qml
tst_UserPage.qml
)
foreach(testfilename ${quicktest_QMLS})
message(STATUS "Adding quick test ${testfilename}")
string(REPLACE .qml "" testname ${testfilename})
add_test(NAME ${testname} COMMAND quicktest -input ${CMAKE_CURRENT_SOURCE_DIR}/quick-tests/${testfilename})
set_property(TEST ${testname}
PROPERTY LABELS "quicktest")
endforeach()
if(kazv_ENABLE_APPIUM_TESTS)
qt_add_qml_module(kazvappiumcomponentqmlmodule
STATIC
URI moe.kazv.mxc.kazvappiumcomponent
VERSION 0.0
)
kazv_add_qml_files(kazvappiumcomponentqmlmodule
BASE_DIRECTORY run-appium-component-qml
SOURCES
Main.qml
)
add_executable(run-appium-component run-appium-component.cpp)
target_link_libraries(run-appium-component
PRIVATE
kazvappiumcomponentqmlmodule
kazvappiumcomponentqmlmoduleplugin
kazvtestlib
Qt::Quick
)
set(appium_tests_SOURCES
TestStickerPicker.py
TestEventViewUnavailable.py
TestRoomTimelineView.py
)
foreach(testfilename ${appium_tests_SOURCES})
message(STATUS "Adding appium test ${testfilename}")
string(REPLACE .py "" testname ${testfilename})
add_test(NAME ${testname}
COMMAND
${SELENIUM_WEBDRIVER_AT_SPI_RUN_PATH}
${CMAKE_CURRENT_SOURCE_DIR}/appium-tests/${testfilename}
)
set_property(TEST ${testname}
PROPERTY ENVIRONMENT "KAZV_APPIUM_TEST_BIN=$<TARGET_FILE:run-appium-component>;TEST_WITH_VIDEO_RECORDER=0")
set_property(TEST ${testname}
PROPERTY LABELS "appiumtest")
endforeach()
endif()
diff --git a/src/tests/matrix-sticker-pack-test.cpp b/src/tests/matrix-sticker-pack-test.cpp
index 00e39f9..6b5a94f 100644
--- a/src/tests/matrix-sticker-pack-test.cpp
+++ b/src/tests/matrix-sticker-pack-test.cpp
@@ -1,343 +1,369 @@
/*
* This file is part of kazv.
* SPDX-FileCopyrightText: 2024 tusooa <tusooa@kazv.moe>
* SPDX-License-Identifier: AGPL-3.0-or-later
*/
#include <kazv-defs.hpp>
#include <memory>
#include <QtTest>
#include <QSet>
#include <QJsonObject>
#include <lager/state.hpp>
#include <base/event.hpp>
#include <testfixtures/factory.hpp>
#include "test-model.hpp"
#include "test-utils.hpp"
#include "matrix-sticker-pack-list.hpp"
#include "matrix-sticker-pack.hpp"
#include "matrix-sticker.hpp"
#include "matrix-event.hpp"
#include "matrix-sdk.hpp"
#include "matrix-room-list.hpp"
#include "matrix-room.hpp"
using namespace Qt::Literals::StringLiterals;
using namespace Kazv;
using namespace Kazv::Factory;
class MatrixStickerPackTest : public QObject
{
Q_OBJECT
private Q_SLOTS:
void testStickerPack();
void testStickerPackList();
void testStickerPackListInRoom();
void testAddToPack();
+ void testRemoveFromPack();
};
// https://github.com/Sorunome/matrix-doc/blob/soru/emotes/proposals/2545-emotes.md
static auto stickerPackEvent = Event{R"({
"content": {
"images": {
"myemote": {
"url": "mxc://example.org/blah"
},
"mysticker": {
"body": "my sticker",
"url": "mxc://example.org/sticker",
"usage": ["sticker"],
"info": {
"mimetype": "image/png"
}
}
},
"pack": {
"display_name": "Awesome Pack",
"usage": ["emoticon"]
}
},
"type": "im.ponies.user_emotes"
})"_json};
static const auto imagePackRoomsEvent = Event(R"({
"content": {
"rooms": {
"!someroom:example.org": {
"": {},
"de.sorunome.mx-puppet-bridge.discord": {}
},
"!someotherroom:example.org": {
"": {}
}
}
},
"type": "im.ponies.emote_rooms"
})"_json);
static Event getRoomStickersEvent(std::string stateKey, std::string name)
{
auto j = R"({
"content": {
"images": {
"myemote": {
"url": "mxc://example.org/blah"
},
"mysticker": {
"body": "my sticker",
"url": "mxc://example.org/sticker",
"usage": ["sticker"],
"info": {
"mimetype": "image/png"
}
}
},
"pack": {
"usage": ["emoticon"]
}
},
"type": "im.ponies.room_emotes"
})"_json;
j["state_key"] = stateKey;
j["content"]["pack"]["display_name"] = name;
return Event(j);
}
template<class Range>
static bool hasPack(const Range &packs, const QString &name)
{
return std::any_of(packs.begin(), packs.end(), [&name](const auto &pack) {
return pack->packName() == name;
});
}
void MatrixStickerPackTest::testStickerPack()
{
auto sourceCursor = lager::make_state(MatrixStickerPackSource{
MatrixStickerPackSource::AccountData,
"im.ponies.user_emotes",
stickerPackEvent,
"",
""
}, lager::automatic_tag{});
auto stickerPack = toUniquePtr(new MatrixStickerPack(sourceCursor));
QCOMPARE(stickerPack->rowCount(QModelIndex()), 2);
QCOMPARE(stickerPack->count(), 2);
QVERIFY(stickerPack->hasShortCode(QStringLiteral("myemote")));
QVERIFY(!stickerPack->hasShortCode(QStringLiteral("lolol")));
auto sticker0 = toUniquePtr(stickerPack->at(0));
QCOMPARE(sticker0->shortCode(), QStringLiteral("myemote"));
QCOMPARE(sticker0->body(), QStringLiteral("myemote"));
QCOMPARE(sticker0->mxcUri(), QStringLiteral("mxc://example.org/blah"));
QCOMPARE(sticker0->info(), QJsonObject());
QCOMPARE(sticker0->makeEventJson(), (QJsonObject{
{u"type"_s, u"m.sticker"_s},
{u"content"_s, QJsonObject{
{u"body"_s, u"myemote"_s},
{u"url"_s, u"mxc://example.org/blah"_s},
{u"info"_s, QJsonObject()},
}},
}));
auto sticker1 = toUniquePtr(stickerPack->at(1));
QCOMPARE(sticker1->shortCode(), QStringLiteral("mysticker"));
QCOMPARE(sticker1->body(), QStringLiteral("my sticker"));
QCOMPARE(sticker1->mxcUri(), QStringLiteral("mxc://example.org/sticker"));
QCOMPARE(sticker1->info(), (QJsonObject{{u"mimetype"_s, u"image/png"_s}}));
QCOMPARE(sticker1->makeEventJson(), (QJsonObject{
{u"type"_s, u"m.sticker"_s},
{u"content"_s, QJsonObject{
{u"body"_s, u"my sticker"_s},
{u"url"_s, u"mxc://example.org/sticker"_s},
{u"info"_s, QJsonObject{{u"mimetype"_s, u"image/png"_s}}},
}},
}));
}
void MatrixStickerPackTest::testStickerPackList()
{
auto model = makeClient(
withAccountData({stickerPackEvent, imagePackRoomsEvent})
| withRoom(makeRoom(
withRoomId("!someroom:example.org")
| withRoomState({
getRoomStickersEvent("", "Pack 1"),
getRoomStickersEvent("de.sorunome.mx-puppet-bridge.discord", "Pack 2"),
})
))
| withRoom(makeRoom(
withRoomId("!someotherroom:example.org")
| withRoomState({getRoomStickersEvent("", "Pack 3")})
))
);
SessionSetup s(SdkModel{model});
auto stickerPackList = toUniquePtr(s.session.stickerPackList());
QCOMPARE(stickerPackList->rowCount(QModelIndex()), 4);
QCOMPARE(stickerPackList->count(), 4);
auto packs = std::array<std::unique_ptr<MatrixStickerPack>, 4>{
toUniquePtr(stickerPackList->at(0)),
toUniquePtr(stickerPackList->at(1)),
toUniquePtr(stickerPackList->at(2)),
toUniquePtr(stickerPackList->at(3)),
};
QVERIFY(hasPack(packs, u"Awesome Pack"_s));
QVERIFY(hasPack(packs, u"Pack 1"_s));
QVERIFY(hasPack(packs, u"Pack 2"_s));
QVERIFY(hasPack(packs, u"Pack 3"_s));
auto expectedPacks = QSet<QJsonValue>{
QJsonObject{
{u"source"_s, MatrixStickerPackSource::AccountData},
{u"isAccountData"_s, true},
{u"isState"_s, false},
{u"eventType"_s, u"im.ponies.user_emotes"_s},
{u"roomId"_s, u""_s},
{u"stateKey"_s, u""_s},
{u"packName"_s, u"Awesome Pack"_s},
},
QJsonObject{
{u"source"_s, MatrixStickerPackSource::RoomState},
{u"isAccountData"_s, false},
{u"isState"_s, true},
{u"eventType"_s, u"im.ponies.room_emotes"_s},
{u"roomId"_s, u"!someroom:example.org"_s},
{u"stateKey"_s, u""_s},
{u"packName"_s, u"Pack 1"_s},
},
QJsonObject{
{u"source"_s, MatrixStickerPackSource::RoomState},
{u"isAccountData"_s, false},
{u"isState"_s, true},
{u"eventType"_s, u"im.ponies.room_emotes"_s},
{u"roomId"_s, u"!someroom:example.org"_s},
{u"stateKey"_s, u"de.sorunome.mx-puppet-bridge.discord"_s},
{u"packName"_s, u"Pack 2"_s},
},
QJsonObject{
{u"source"_s, MatrixStickerPackSource::RoomState},
{u"isAccountData"_s, false},
{u"isState"_s, true},
{u"eventType"_s, u"im.ponies.room_emotes"_s},
{u"roomId"_s, u"!someotherroom:example.org"_s},
{u"stateKey"_s, u""_s},
{u"packName"_s, u"Pack 3"_s},
},
};
auto packsArr = stickerPackList->packs().toArray();
QCOMPARE(QSet<QJsonValue>(packsArr.begin(), packsArr.end()), expectedPacks);
auto p1 = toUniquePtr(stickerPackList->packFor(QJsonObject{
{u"source"_s, MatrixStickerPackSource::RoomState},
{u"eventType"_s, u"im.ponies.room_emotes"_s},
{u"roomId"_s, u"!someroom:example.org"_s},
{u"stateKey"_s, u""_s},
}));
QCOMPARE(p1->packName(), u"Pack 1"_s);
auto p2 = toUniquePtr(stickerPackList->packFor(QJsonObject{
{u"source"_s, MatrixStickerPackSource::RoomState},
{u"eventType"_s, u"im.ponies.room_emotes"_s},
{u"roomId"_s, u"!someotherroom:example.org"_s},
{u"stateKey"_s, u"somestatekey"_s},
}));
QVERIFY(p2->isAccountData());
}
void MatrixStickerPackTest::testStickerPackListInRoom()
{
auto model = makeClient(
withRoom(makeRoom(
withRoomId("!someroom:example.org")
| withRoomState({
getRoomStickersEvent("", "Pack 1"),
getRoomStickersEvent("de.sorunome.mx-puppet-bridge.discord", "Pack 2"),
})
))
);
SessionSetup s(SdkModel{model});
auto roomList = toUniquePtr(s.session.roomList());
auto room = toUniquePtr(roomList->room(u"!someroom:example.org"_s));
auto stickerPackList = toUniquePtr(room->stickerPackList());
QCOMPARE(stickerPackList->count(), 2);
auto packs = std::array<std::unique_ptr<MatrixStickerPack>, 2>{
toUniquePtr(stickerPackList->at(0)),
toUniquePtr(stickerPackList->at(1)),
};
QVERIFY(hasPack(packs, u"Pack 1"_s));
QVERIFY(hasPack(packs, u"Pack 2"_s));
auto expectedPacks = QSet<QJsonValue>{
QJsonObject{
{u"source"_s, MatrixStickerPackSource::RoomState},
{u"isAccountData"_s, false},
{u"isState"_s, true},
{u"eventType"_s, u"im.ponies.room_emotes"_s},
{u"roomId"_s, u"!someroom:example.org"_s},
{u"stateKey"_s, u""_s},
{u"packName"_s, u"Pack 1"_s},
},
QJsonObject{
{u"source"_s, MatrixStickerPackSource::RoomState},
{u"isAccountData"_s, false},
{u"isState"_s, true},
{u"eventType"_s, u"im.ponies.room_emotes"_s},
{u"roomId"_s, u"!someroom:example.org"_s},
{u"stateKey"_s, u"de.sorunome.mx-puppet-bridge.discord"_s},
{u"packName"_s, u"Pack 2"_s},
},
};
auto packsArr = stickerPackList->packs().toArray();
QCOMPARE(QSet<QJsonValue>(packsArr.begin(), packsArr.end()), expectedPacks);
}
void MatrixStickerPackTest::testAddToPack()
{
using namespace nlohmann::literals;
auto sourceCursor = lager::make_state(MatrixStickerPackSource{
MatrixStickerPackSource::AccountData,
"im.ponies.user_emotes",
stickerPackEvent,
"",
"",
}, lager::automatic_tag{});
auto stickerPack = toUniquePtr(new MatrixStickerPack(sourceCursor));
auto contentJson = json{
{"url", "mxc://example.org/someotheremote"},
{"info", {{"mimetype", "image/png"}}},
{"body", "some other emote"},
};
auto eventCursor = lager::make_constant(makeEvent(
withEventContent(contentJson)
));
auto event = toUniquePtr(new MatrixEvent(eventCursor));
auto newSource = stickerPack->addSticker(u"someotheremote"_s, event.get()).template value<MatrixStickerPackSource>();
auto expected = sourceCursor.get().event.content().get();
expected["images"]["someotheremote"] = contentJson;
QVERIFY(newSource.event.content().get() == expected);
}
+void MatrixStickerPackTest::testRemoveFromPack()
+{
+ auto sourceCursor = lager::make_state(MatrixStickerPackSource{
+ MatrixStickerPackSource::AccountData,
+ "im.ponies.user_emotes",
+ stickerPackEvent,
+ "",
+ "",
+ }, lager::automatic_tag{});
+
+ auto stickerPack = toUniquePtr(new MatrixStickerPack(sourceCursor));
+
+ auto newSource = stickerPack->removeSticker(u"myemote"_s).template value<MatrixStickerPackSource>();
+
+ auto expected = sourceCursor.get().event.content().get();
+ expected["images"].erase("myemote");
+
+ QVERIFY(newSource.event.content().get() == expected);
+ QVERIFY(!newSource.event.content().get()["images"].contains("myemote"));
+
+ // Removing a non-existent sticker should leave the pack unchanged.
+ auto unchangedSource = stickerPack->removeSticker(u"doesnotexist"_s).template value<MatrixStickerPackSource>();
+ QVERIFY(unchangedSource.event.content().get() == sourceCursor.get().event.content().get());
+}
+
QTEST_MAIN(MatrixStickerPackTest)
#include "matrix-sticker-pack-test.moc"
diff --git a/src/tests/quick-tests/tst_StickerPicker.qml b/src/tests/quick-tests/tst_StickerPicker.qml
new file mode 100644
index 0000000..bf860aa
--- /dev/null
+++ b/src/tests/quick-tests/tst_StickerPicker.qml
@@ -0,0 +1,82 @@
+/*
+ * This file is part of kazv.
+ * SPDX-FileCopyrightText: 2026 tusooa <tusooa@kazv.moe>
+ * SPDX-License-Identifier: AGPL-3.0-or-later
+ */
+
+import QtQuick 2.15
+import QtQuick.Layouts 1.15
+import QtQuick.Window
+import QtQuick.Controls
+import QtTest 1.0
+
+import '../../contents/ui' as Kazv
+import 'test-helpers.js' as JsHelpers
+import 'test-helpers' as QmlHelpers
+
+QmlHelpers.TestItem {
+ id: item
+
+ property var someShortCode: 'myemote'
+
+ property var somePack: ListModel {
+ id: somePack
+ property string packName: 'Pack 0'
+ function removeSticker(shortCode) {
+ return { _removedShortCode: shortCode };
+ }
+ function at(index) {
+ return null;
+ }
+ }
+
+ Kazv.StickerPicker {
+ id: stickerPicker
+ anchors.fill: parent
+ stickerPackList: ListModel {
+ id: stickerPackListModel
+ ListElement {}
+ function at(index) {
+ return somePack;
+ }
+ }
+ }
+
+ TestCase {
+ id: stickerPickerTest
+ name: 'StickerPickerTest'
+ when: windowShown
+
+ function init() {
+ mockHelper.clearAll();
+ }
+
+ function _openConfirmation() {
+ stickerPicker.requestRemoveSticker(somePack, item.someShortCode);
+ const popup = findChild(Overlay.overlay, 'removeStickerConfirmation');
+ verify(popup);
+ tryVerify(() => popup.opened);
+ return popup;
+ }
+
+ function test_removeStickerConfirm() {
+ const popup = _openConfirmation();
+ mouseClick(findChild(popup, 'confirmButton'));
+ verify(matrixSession.updateStickerPack.calledTimes() === 1);
+ verify(JsHelpers.deepEqual(matrixSession.updateStickerPack.lastArgs()[0], { _removedShortCode: item.someShortCode }));
+ tryVerify(() => !popup.opened);
+ tryVerify(() => !findChild(Overlay.overlay, 'removeStickerConfirmation'));
+ compare(showPassiveNotification.calledTimes(), 0);
+ }
+
+ function test_removeStickerFailure() {
+ const popup = _openConfirmation();
+ mouseClick(findChild(popup, 'confirmButton'));
+ verify(matrixSession.updateStickerPack.calledTimes() === 1);
+ tryVerify(() => !popup.opened);
+ matrixSession.updateStickerPack.lastRetVal().resolve(false, { errorCode: 'M_FORBIDDEN', error: 'no' });
+ compare(showPassiveNotification.calledTimes(), 1);
+ compare(showPassiveNotification.lastArgs()[0], l10n.get('sticker-remove-failed-prompt', { errorCode: 'M_FORBIDDEN', errorMsg: 'no' }));
+ }
+ }
+}

File Metadata

Mime Type
text/x-diff
Expires
Sun, Aug 9, 5:38 AM (1 d, 15 h)
Storage Engine
blob
Storage Format
Raw Data
Storage Handle
1724255
Default Alt Text
(82 KB)

Event Timeline