Compare commits

...

14 Commits
main ... 5.7.x

Author SHA1 Message Date
Evan Hahn 9329db949d v5.7.1 2021-06-30 12:32:13 -05:00
Evan Hahn 1c522ff99a
Update translations 2021-06-30 12:23:28 -05:00
Fedor Indutny 795fb9ee0f
Subscribe to native theme changes 2021-06-30 10:07:42 -07:00
automated-signal bb457dd086
Improve readability of image captions
Co-authored-by: Evan Hahn <69474926+EvanHahn-Signal@users.noreply.github.com>
2021-06-29 19:28:38 -05:00
automated-signal b3b210a9c5
Unify audio playback under App component
Co-authored-by: Fedor Indutny <79877362+indutny-signal@users.noreply.github.com>
2021-06-29 13:23:28 -07:00
automated-signal f344811e64
Create missing index for markRead
Co-authored-by: Fedor Indutny <79877362+indutny-signal@users.noreply.github.com>
2021-06-29 12:05:11 -05:00
Evan Hahn 601495babb v5.7.0 2021-06-28 13:53:56 -05:00
Evan Hahn 30514328e3 v5.7.0-beta.2 2021-06-28 13:53:56 -05:00
Fedor Indutny 24d969bbb3
Ensure that messages are in redux when scrolling 2021-06-28 13:52:18 -05:00
Evan Hahn 299d4b2807
Update translations 2021-06-28 13:40:22 -05:00
automated-signal e04476f2ca
Fix disappearing composition are during incoming call
Co-authored-by: Josh Perez <60019601+josh-signal@users.noreply.github.com>
2021-06-25 14:05:44 -07:00
automated-signal 16ada5f734
Fix UI for GIFs in groups
Co-authored-by: Fedor Indutny <79877362+indutny-signal@users.noreply.github.com>
2021-06-24 16:45:07 -05:00
automated-signal 2f7a2b19f2
Fix rendering of GIFs
Co-authored-by: Fedor Indutny <79877362+indutny-signal@users.noreply.github.com>
2021-06-24 12:25:52 -07:00
automated-signal 7fab45ef8d
Forwarded messages must use different timestamps
Co-authored-by: Fedor Indutny <79877362+indutny-signal@users.noreply.github.com>
2021-06-24 11:43:36 -07:00
38 changed files with 695 additions and 294 deletions

View File

@ -55,6 +55,10 @@
"message": "Préférences…",
"description": "The label that is used for the Preferences menu in the program main menu. This should be consistent with the standard naming for Preferences on the operating system."
},
"appMenuServices": {
"message": "Services",
"description": "Application menu item for macOS 'Services'"
},
"appMenuHide": {
"message": "Cacher",
"description": "Application menu command to hide the window"
@ -1320,15 +1324,15 @@
"description": "Button tooltip label for turning on the microphone"
},
"calling__button--presenting-disabled": {
"message": "Presenting disabled",
"message": "Présentation désactivée",
"description": "Button tooltip label for when screen sharing is disabled"
},
"calling__button--presenting-on": {
"message": "Start presenting",
"message": "Commencer la présentation",
"description": "Button tooltip label for starting to share screen"
},
"calling__button--presenting-off": {
"message": "Stop presenting",
"message": "Arrêter la présentation",
"description": "Button tooltip label for stopping screen sharing"
},
"calling__your-video-is-off": {
@ -1452,7 +1456,7 @@
"description": "Body text for the share screen notification"
},
"calling__presenting--info": {
"message": "Signal is sharing $window$.",
"message": "Signal est en train de partager $window$.",
"description": "Text that appears in the screen sharing controller to inform person that they are presenting",
"placeholders": {
"name": {
@ -1470,7 +1474,7 @@
"description": "Toast that appears when someone stops presenting"
},
"calling__presenting--person-ongoing": {
"message": "$name$ is presenting",
"message": "$name$ est en présentation",
"description": "Title of call when someone is presenting",
"placeholders": {
"name": {
@ -1480,7 +1484,7 @@
}
},
"calling__presenting--person-stopped": {
"message": "$name$ stopped presenting",
"message": "$name$ a arrêté la présentation",
"description": "Toast that appears when someone stops presenting",
"placeholders": {
"name": {
@ -1494,7 +1498,7 @@
"description": "Shown as the title for the modal that requests screen recording permissions"
},
"calling__presenting--macos-permission-description": {
"message": "Signal needs permission to access your computer's screen recording.",
"message": "Signal a besoin dune autorisation pour accéder à lenregistrement de lécran de votre ordinateur.",
"description": "Shown as the description for the modal that requests screen recording permissions"
},
"calling__presenting--permission-instruction-step1": {
@ -5588,7 +5592,7 @@
"description": "Name of the 'weeks' unit select for the custom disappearing message timeout dialog"
},
"settings__DisappearingMessages__footer": {
"message": "Set a default disappearing message timer for all new chats started by you.",
"message": "Définissez une minuterie par défaut pour les messages éphémères pour toutes les nouvelles conversations que vous démarrez.",
"description": "Footer for the Disappearing Messages settings section"
},
"settings__DisappearingMessages__timer__label": {
@ -5610,7 +5614,7 @@
"description": "Button text when the group description is too long"
},
"EditConversationAttributesModal__description-warning": {
"message": "Group descriptions will be visible to members of this group and people who have been invited.",
"message": "Les descriptions de gruope seront visibles aux membres de ce groupe et aux personnes qui ont été invitées.",
"description": "Label text shown when editing group description"
},
"ConversationDetailsHeader--add-group-description": {

View File

@ -180,7 +180,7 @@
"description": "One of the menu options available in the Avatar Popup menu"
},
"avatarMenuChatColors": {
"message": "Gesprekskleur kiezen",
"message": "Standaardgesprekskleur kiezen",
"description": "One of the menu options available in the Avatar Popup menu"
},
"loading": {
@ -4712,7 +4712,7 @@
}
},
"GroupV1--Migration--was-upgraded": {
"message": "Deze groep is omgezet in een nieuwe groep.",
"message": "Deze groep is omgezet in een nieuwe-stijl groep.",
"description": "Shown in timeline when a legacy group (GV1) is upgraded to a new group (GV2)"
},
"GroupV1--Migration--learn-more": {
@ -4724,15 +4724,15 @@
"description": "Shown on Migrate dialog to kick off the process"
},
"GroupV1--Migration--info--title": {
"message": "Wat zijn nieuwe groepen?",
"message": "Wat zijn nieuwe-stijl groepen?",
"description": "Shown on Learn More popup after GV1 migration"
},
"GroupV1--Migration--migrate--title": {
"message": "Omzetten naar nieuwe groep",
"message": "Omzetten naar nieuwe-stijl groep",
"description": "Shown on Migration popup after choosing to migrate group"
},
"GroupV1--Migration--info--summary": {
"message": "Nieuwe groepen hebben functionaliteiten waaronder @vermeldingen en groepsbeheer en in de toekomst zullen er meer functionaliteiten aan worden toegevoegd.",
"message": "Nieuwe-stijl groepen hebben functionaliteiten waaronder @vermeldingen en groepsbeheer en in de toekomst zullen er meer functionaliteiten aan worden toegevoegd.",
"description": "Shown on Learn More popup after or Migration popup before GV1 migration"
},
"GroupV1--Migration--info--keep-history": {
@ -4756,27 +4756,27 @@
"description": "Shown on Learn More popup after or Migration popup before GV1 migration"
},
"GroupV1--Migration--info--removed--before--many": {
"message": "Deze groepsleden zijn niet in staat om tot nieuwe groepen toe te treden en worden daarom verwijderd uit de groep:",
"message": "Deze groepsleden zijn niet in staat om tot nieuwe-stijl groepen toe te treden en worden daarom verwijderd uit de groep:",
"description": "Shown on Learn More popup after or Migration popup before GV1 migration"
},
"GroupV1--Migration--info--removed--before--one": {
"message": "Dit groepslid is niet in staat om tot nieuwe groepen toe te treden en wordt daarom verwijderd uit de groep:",
"message": "Dit groepslid is niet in staat om tot nieuwe-stijl groepen toe te treden en wordt daarom verwijderd uit de groep:",
"description": "Shown on Learn More popup after or Migration popup before GV1 migration"
},
"GroupV1--Migration--info--removed--after--many": {
"message": "Deze groepsleden zijn niet in staat om tot nieuwe groepen toe te treden en worden daarom verwijderd uit de groep:",
"message": "Deze groepsleden zijn niet in staat om tot nieuwe-stijl groepen toe te treden en worden daarom verwijderd uit de groep:",
"description": "Shown on Learn More popup after or Migration popup before GV1 migration"
},
"GroupV1--Migration--info--removed--after--one": {
"message": "Dit groepslid is niet in staat om tot nieuwe groepen toe te treden en wordt daarom verwijderd uit de groep:",
"message": "Dit groepslid is niet in staat om tot nieuwe-stijl groepen toe te treden en wordt daarom verwijderd uit de groep:",
"description": "Shown on Learn More popup after or Migration popup before GV1 migration"
},
"GroupV1--Migration--invited--you": {
"message": "Je kon niet toegevoegd worden aan de nieuwe groep en je hebt daarom een uitnodiging gekregen om opnieuw lid te worden.",
"message": "Je kon niet toegevoegd worden aan de nieuwe-stijl groep en je hebt daarom een uitnodiging gekregen om opnieuw lid te worden.",
"description": "Shown in timeline when a group is upgraded and you were invited instead of added"
},
"GroupV1--Migration--invited--one": {
"message": "$contact$ niet toegevoegd worden aan de nieuwe groep en je hebt daarom een uitnodiging gekregen om opnieuw lid te worden.",
"message": "$contact$ niet toegevoegd worden aan de nieuwe-stijl groep en je hebt daarom een uitnodiging gekregen om opnieuw lid te worden.",
"description": "Shown in timeline when a group is upgraded and one person was invited, instead of added",
"placeholders": {
"contact": {
@ -4786,7 +4786,7 @@
}
},
"GroupV1--Migration--invited--many": {
"message": "$count$ groepsleden konden niet direct toegevoegd worden aan de nieuwe groep en hebben daarom een uitnodiging gekregen om opnieuw lid te worden.",
"message": "$count$ groepsleden konden niet direct toegevoegd worden aan de nieuwe-stijl groep en hebben daarom een uitnodiging gekregen om opnieuw lid te worden.",
"description": "Shown in timeline when a group is upgraded and some people were invited, instead of added",
"placeholders": {
"contact": {
@ -4904,7 +4904,7 @@
"description": "This is the label for the disappearing messages setting panel"
},
"ConversationDetails--disappearing-messages-info": {
"message": "Indien ingeschakeld zal elk nieuwe bericht dat in dit gesprek verzonden of ontvangen wordt voor iedere deelnemer aan het gesprek gewist worden nadat de ingestelde tijd verlopen is. De tijd gaat voor iedere deelnemer pas lopen vanaf het moment dat hij of zij het bericht heeft gezien. Gebruik verlopende berichten niet ter beveiliging, want Signal kan niet garanderen dat een bericht op het apparaat van een ander daadwerkelijk wordt gewist.",
"message": "Nieuwe berichten in dit gesprek zullen voor iedereen die ze heeft gezien nadat de ingestelde tijd is verlopen worden gewist. — Gebruik dit niet ter beveiliging, want Signal kan niet garanderen dat berichten op het apparaat van een ander ook daadwerkelijk worden gewist.",
"description": "This is the info about the disappearing messages setting"
},
"ConversationDetails--group-info-label": {
@ -5264,7 +5264,7 @@
"description": "When adding new members to an existing group, this is shown on the confirmation dialog button"
},
"createNewGroupButton": {
"message": "Nieuwe groep",
"message": "Nieuwe groep aanmaken",
"description": "The text of the button to create new groups"
},
"selectContact": {

View File

@ -55,6 +55,10 @@
"message": "Ustawienia...",
"description": "The label that is used for the Preferences menu in the program main menu. This should be consistent with the standard naming for Preferences on the operating system."
},
"appMenuServices": {
"message": "Usługi",
"description": "Application menu item for macOS 'Services'"
},
"appMenuHide": {
"message": "Ukryj",
"description": "Application menu command to hide the window"
@ -1736,7 +1740,7 @@
"description": "Brief timestamp for messages sent less than a minute ago. Displayed in the conversation list and message bubble."
},
"timestamp_m": {
"message": "1 min.",
"message": "1 min",
"description": "Brief timestamp for messages sent about one minute ago. Displayed in the conversation list and message bubble."
},
"timestamp_h": {
@ -1754,7 +1758,7 @@
}
},
"minutesAgo": {
"message": "$minutes$ min.",
"message": "$minutes$ min",
"description": "Contracted form of 'X minutes ago' which works both for singular and plural",
"placeholders": {
"minutes": {
@ -2265,15 +2269,15 @@
},
"message--getDescription--disappearing-media": {
"message": "Multimedia jednorazowe",
"description": "Shown in notifications and in the left pane after view-once message is deleted."
"description": "Shown in notifications and in the left pane after view-once message is deleted. Also shown when quoting a view once media."
},
"message--getDescription--disappearing-photo": {
"message": "Zdjęcie jednorazowe",
"description": "Shown in notifications and in the left pane when a message is a view once photo."
"description": "Shown in notifications and in the left pane when a message is a view once photo. Also shown when quoting a view once photo."
},
"message--getDescription--disappearing-video": {
"message": "Wideo jednorazowe",
"description": "Shown in notifications and in the left pane when a message is a view once video."
"description": "Shown in notifications and in the left pane when a message is a view once video. Also shown when quoting a view once video."
},
"message--deletedForEveryone": {
"message": "Ta wiadomość została usunięta.",
@ -3427,7 +3431,17 @@
},
"calling__SelectPresentingSourcesModal--entireScreen": {
"message": "Cały ekran",
"description": "Title for the select your screen sharing sources modal"
"description": "Title for the select your screen sharing sources modal and 'Entire Screen' source"
},
"calling__SelectPresentingSourcesModal--screen": {
"message": "Ekran $id$",
"description": "Title for `Screen #N` source in screen sharing sources modal and overlay",
"placeholders": {
"id": {
"content": "$1",
"example": "1"
}
}
},
"calling__SelectPresentingSourcesModal--window": {
"message": "Okno",
@ -5469,10 +5483,18 @@
"message": "Zresetuj kolor czatu",
"description": "Button label for resetting chat colors"
},
"ChatColorPicker__resetDefault": {
"message": "Zresetuj kolory czatów",
"description": "Confirmation dialog title for resetting all chat colors or only the global default one"
},
"ChatColorPicker__resetAll": {
"message": "Zresetuj wszystkie kolory czatów",
"description": "Button label for resetting all chat colors"
},
"ChatColorPicker__confirm-reset-default": {
"message": "Zresetuj domyślny",
"description": "Button label for resetting only global chat color"
},
"ChatColorPicker__confirm-reset": {
"message": "Zresetuj",
"description": "Confirm button label for resetting chat colors"

View File

@ -55,6 +55,10 @@
"message": "Preferințe...",
"description": "The label that is used for the Preferences menu in the program main menu. This should be consistent with the standard naming for Preferences on the operating system."
},
"appMenuServices": {
"message": "Services",
"description": "Application menu item for macOS 'Services'"
},
"appMenuHide": {
"message": "Ascunde",
"description": "Application menu command to hide the window"
@ -1144,7 +1148,7 @@
"description": "Shown on explainer dialog available from chat session refreshed timeline events"
},
"DeliveryIssue--preview": {
"message": "Delivery issue",
"message": "Problemă de livrare",
"description": "Shown in left pane preview when message delivery issue happens"
},
"DeliveryIssue--notification": {
@ -1162,7 +1166,7 @@
"description": "Shown in timeline when message delivery issue happens, to provide access to a popup info dialog"
},
"DeliveryIssue--title": {
"message": "Delivery Issue",
"message": "Problemă de livrare",
"description": "Shown on explainer dialog available from delivery issue timeline events"
},
"DeliveryIssue--summary": {
@ -2265,15 +2269,15 @@
},
"message--getDescription--disappearing-media": {
"message": "Media vizibilă o singură dată",
"description": "Shown in notifications and in the left pane after view-once message is deleted."
"description": "Shown in notifications and in the left pane after view-once message is deleted. Also shown when quoting a view once media."
},
"message--getDescription--disappearing-photo": {
"message": "Poză vizibilă o singură dată",
"description": "Shown in notifications and in the left pane when a message is a view once photo."
"description": "Shown in notifications and in the left pane when a message is a view once photo. Also shown when quoting a view once photo."
},
"message--getDescription--disappearing-video": {
"message": "Video vizibil o singură dată",
"description": "Shown in notifications and in the left pane when a message is a view once video."
"description": "Shown in notifications and in the left pane when a message is a view once video. Also shown when quoting a view once video."
},
"message--deletedForEveryone": {
"message": "Acest mesaj a fost șters.",
@ -3427,7 +3431,17 @@
},
"calling__SelectPresentingSourcesModal--entireScreen": {
"message": "Entire screen",
"description": "Title for the select your screen sharing sources modal"
"description": "Title for the select your screen sharing sources modal and 'Entire Screen' source"
},
"calling__SelectPresentingSourcesModal--screen": {
"message": "Screen $id$",
"description": "Title for `Screen #N` source in screen sharing sources modal and overlay",
"placeholders": {
"id": {
"content": "$1",
"example": "1"
}
}
},
"calling__SelectPresentingSourcesModal--window": {
"message": "A window",
@ -4846,7 +4860,7 @@
"description": "Button text for removing as admin button in Group Contact Details modal"
},
"ContactModal--make-admin": {
"message": "Make admin",
"message": "Faceți administrator",
"description": "Button text for make admin button in Group Contact Details modal"
},
"ContactModal--make-admin-info": {
@ -5469,10 +5483,18 @@
"message": "Resetare culoare conversație",
"description": "Button label for resetting chat colors"
},
"ChatColorPicker__resetDefault": {
"message": "Resetare culori conversație",
"description": "Confirmation dialog title for resetting all chat colors or only the global default one"
},
"ChatColorPicker__resetAll": {
"message": "Reset all chat colors",
"description": "Button label for resetting all chat colors"
},
"ChatColorPicker__confirm-reset-default": {
"message": "Reset default",
"description": "Button label for resetting only global chat color"
},
"ChatColorPicker__confirm-reset": {
"message": "Resetare",
"description": "Confirm button label for resetting chat colors"

View File

@ -55,6 +55,10 @@
"message": "首选项...",
"description": "The label that is used for the Preferences menu in the program main menu. This should be consistent with the standard naming for Preferences on the operating system."
},
"appMenuServices": {
"message": "服务",
"description": "Application menu item for macOS 'Services'"
},
"appMenuHide": {
"message": "隐藏",
"description": "Application menu command to hide the window"
@ -706,7 +710,7 @@
"description": "Item under the Help menu, which opens a small about window"
},
"screenShareWindow": {
"message": "Sharing screen",
"message": "屏幕共享",
"description": "Title for screen sharing window"
},
"speech": {
@ -758,7 +762,7 @@
}
},
"noSearchResults--sms-only": {
"message": "SMS/MMS contacts are not available on Desktop.",
"message": "短信/彩信联系人在 Desktop 上不可用。",
"description": "Shown in the search left pane when no results were found and primary device has SMS/MMS handling enabled"
},
"noSearchResultsInConversation": {
@ -1144,11 +1148,11 @@
"description": "Shown on explainer dialog available from chat session refreshed timeline events"
},
"DeliveryIssue--preview": {
"message": "Delivery issue",
"message": "发送问题",
"description": "Shown in left pane preview when message delivery issue happens"
},
"DeliveryIssue--notification": {
"message": "A message from $sender$ couldnt be delivered",
"message": "来自 $sender$ 的消息无法发送",
"description": "Shown in timeline when message delivery issue happens",
"placeholders": {
"name": {
@ -1162,11 +1166,11 @@
"description": "Shown in timeline when message delivery issue happens, to provide access to a popup info dialog"
},
"DeliveryIssue--title": {
"message": "Delivery Issue",
"message": "发送问题",
"description": "Shown on explainer dialog available from delivery issue timeline events"
},
"DeliveryIssue--summary": {
"message": "A message, sticker, reaction, read receipt or media couldnt be delivered to you from $sender$. They may have tried sending it to you directly, or in a group.",
"message": "无法将来自 $sender$ 的消息、贴纸、反应、已读回执或媒体发送给您。它们可能是直接发送或在群组中发送。",
"description": "Shown on explainer dialog available from delivery issue timeline events",
"placeholders": {
"name": {
@ -1320,15 +1324,15 @@
"description": "Button tooltip label for turning on the microphone"
},
"calling__button--presenting-disabled": {
"message": "Presenting disabled",
"message": "演示已禁用",
"description": "Button tooltip label for when screen sharing is disabled"
},
"calling__button--presenting-on": {
"message": "Start presenting",
"message": "开始演示",
"description": "Button tooltip label for starting to share screen"
},
"calling__button--presenting-off": {
"message": "Stop presenting",
"message": "停止演示",
"description": "Button tooltip label for stopping screen sharing"
},
"calling__your-video-is-off": {
@ -1444,15 +1448,15 @@
"description": "Label for the \"scroll down\" button in a call's overflow area"
},
"calling__presenting--notification-title": {
"message": "You're presenting to everyone.",
"message": "您正在对所有人进行演示。",
"description": "Title for the share screen notification"
},
"calling__presenting--notification-body": {
"message": "Click here to return to the call when you're ready to stop presenting.",
"message": "当您准备好停止演示时,点击此处返回通话。",
"description": "Body text for the share screen notification"
},
"calling__presenting--info": {
"message": "Signal is sharing $window$.",
"message": "Signal 正在共享 $window$。",
"description": "Text that appears in the screen sharing controller to inform person that they are presenting",
"placeholders": {
"name": {
@ -1462,15 +1466,15 @@
}
},
"calling__presenting--stop": {
"message": "Stop sharing",
"message": "停止共享",
"description": "Button for stopping screen sharing"
},
"calling__presenting--you-stopped": {
"message": "You stopped presenting",
"message": "您已停止演示",
"description": "Toast that appears when someone stops presenting"
},
"calling__presenting--person-ongoing": {
"message": "$name$ is presenting",
"message": "$name$ 正在演示",
"description": "Title of call when someone is presenting",
"placeholders": {
"name": {
@ -1480,7 +1484,7 @@
}
},
"calling__presenting--person-stopped": {
"message": "$name$ stopped presenting",
"message": "$name$ 已停止演示",
"description": "Toast that appears when someone stops presenting",
"placeholders": {
"name": {
@ -1490,27 +1494,27 @@
}
},
"calling__presenting--permission-title": {
"message": "Permission needed",
"message": "所需权限",
"description": "Shown as the title for the modal that requests screen recording permissions"
},
"calling__presenting--macos-permission-description": {
"message": "Signal needs permission to access your computer's screen recording.",
"message": "Signal 需相关访问权限,以进行计算机屏幕录制。",
"description": "Shown as the description for the modal that requests screen recording permissions"
},
"calling__presenting--permission-instruction-step1": {
"message": "Go to System Preferences.",
"message": "打开“系统首选项”。",
"description": "Shown as the description for the modal that requests screen recording permissions"
},
"calling__presenting--permission-instruction-step2": {
"message": "Click on the lock icon on the bottom left and enter your computers password.",
"message": "点击左下角的锁定图标,然后输入计算机密码。",
"description": "Shown as the description for the modal that requests screen recording permissions"
},
"calling__presenting--permission-instruction-step3": {
"message": "On the right, check the box next to Signal. If you dont see Signal in the list, click the + to add it.",
"message": "在右侧,选中 Signal 旁边的选择框。如果列表中没有 Signal请点击 + 添加。",
"description": "Shown as the description for the modal that requests screen recording permissions"
},
"calling__presenting--permission-open": {
"message": "Open System Preferences",
"message": "打开系统首选项",
"description": "The button that opens your system preferences for the needs screen record permissions modal"
},
"calling__presenting--permission-cancel": {
@ -1542,7 +1546,7 @@
"description": "Header for general options on the settings screen"
},
"spellCheckDescription": {
"message": "Enable spell check",
"message": "启用拼写检查",
"description": "Description of the spell check setting"
},
"spellCheckWillBeEnabled": {
@ -1554,7 +1558,7 @@
"description": "Shown when the user disables spellcheck to indicate that they must restart Signal."
},
"autoLaunchDescription": {
"message": "Open at computer login",
"message": "计算机登录时打开",
"description": "Description for the automatic launch setting"
},
"clearDataHeader": {
@ -1996,7 +2000,7 @@
"description": "Label shown when there are no contacts to compose to"
},
"noConversationsFound": {
"message": "No conversations found",
"message": "未找到对话",
"description": "Label shown when there are no conversations to compose to"
},
"chooseGroupMembers__title": {
@ -2070,7 +2074,7 @@
"description": "The placeholder for the group name placeholder"
},
"setGroupMetadata__group-description-placeholder": {
"message": "Description",
"message": "描述",
"description": "The placeholder for the group description"
},
"setGroupMetadata__create-group": {
@ -2265,15 +2269,15 @@
},
"message--getDescription--disappearing-media": {
"message": "一次性浏览媒体文件",
"description": "Shown in notifications and in the left pane after view-once message is deleted."
"description": "Shown in notifications and in the left pane after view-once message is deleted. Also shown when quoting a view once media."
},
"message--getDescription--disappearing-photo": {
"message": "一次性图片",
"description": "Shown in notifications and in the left pane when a message is a view once photo."
"description": "Shown in notifications and in the left pane when a message is a view once photo. Also shown when quoting a view once photo."
},
"message--getDescription--disappearing-video": {
"message": "一次性视频",
"description": "Shown in notifications and in the left pane when a message is a view once video."
"description": "Shown in notifications and in the left pane when a message is a view once video. Also shown when quoting a view once video."
},
"message--deletedForEveryone": {
"message": "消息已删除。",
@ -3036,7 +3040,7 @@
}
},
"MessageRequests--block-and-report-spam": {
"message": "Report Spam and Block",
"message": "举报垃圾与屏蔽",
"description": "Shown as a button to let the user block a message request and report spam"
},
"MessageRequests--block-and-report-spam-success-toast": {
@ -3418,19 +3422,29 @@
"description": "Title for hang up button"
},
"calling__SelectPresentingSourcesModal--title": {
"message": "Share your screen",
"message": "共享您的屏幕",
"description": "Title for the select your screen sharing sources modal"
},
"calling__SelectPresentingSourcesModal--confirm": {
"message": "Share screen",
"message": "共享屏幕",
"description": "Confirm button for sharing screen modal"
},
"calling__SelectPresentingSourcesModal--entireScreen": {
"message": "Entire screen",
"description": "Title for the select your screen sharing sources modal"
"message": "整个屏幕",
"description": "Title for the select your screen sharing sources modal and 'Entire Screen' source"
},
"calling__SelectPresentingSourcesModal--screen": {
"message": "屏幕 $id$",
"description": "Title for `Screen #N` source in screen sharing sources modal and overlay",
"placeholders": {
"id": {
"content": "$1",
"example": "1"
}
}
},
"calling__SelectPresentingSourcesModal--window": {
"message": "A window",
"message": "窗口",
"description": "Title for the select your screen sharing sources modal"
},
"callingDeviceSelection__label--video": {
@ -3462,7 +3476,7 @@
"description": "Label for muting the conversation"
},
"muteEightHours": {
"message": "Mute for eight hours",
"message": "静音 8 个小时",
"description": "Label for muting the conversation"
},
"muteDay": {
@ -3474,7 +3488,7 @@
"description": "Label for muting the conversation"
},
"muteAlways": {
"message": "Mute always",
"message": "总是静音",
"description": "Label for muting the conversation"
},
"unmute": {
@ -3482,7 +3496,7 @@
"description": "Label for unmuting the conversation"
},
"muteExpirationLabelAlways": {
"message": "Muted always",
"message": "已总是静音",
"description": "Shown in the mute notifications submenu whenever a conversation has been muted"
},
"muteExpirationLabel": {
@ -4652,11 +4666,11 @@
"description": "Shown in timeline or conversation preview when v2 group changes"
},
"GroupV2--description--remove--you": {
"message": "You removed the group description.",
"message": "您已删除群组描述。",
"description": "Shown in timeline or conversation preview when v2 group changes"
},
"GroupV2--description--remove--other": {
"message": "$memberName$ removed the group description.",
"message": "$memberName$ 已删除群组描述。",
"description": "Shown in timeline or conversation preview when v2 group changes",
"placeholders": {
"adminName": {
@ -4666,7 +4680,7 @@
}
},
"GroupV2--description--remove--unknown": {
"message": "The group description was removed.",
"message": "群组描述已删除。",
"description": "Shown in timeline or conversation preview when v2 group changes"
},
"GroupV2--description--change--you": {
@ -4674,7 +4688,7 @@
"description": "Shown in timeline or conversation preview when v2 group changes"
},
"GroupV2--description--change--other": {
"message": "$memberName$ changed the group description.",
"message": "$memberName$ 已更改群组描述。",
"description": "Shown in timeline or conversation preview when v2 group changes",
"placeholders": {
"adminName": {
@ -4684,7 +4698,7 @@
}
},
"GroupV2--description--change--unknown": {
"message": "The group description was changed.",
"message": "群组描述已更改。",
"description": "Shown in timeline or conversation preview when v2 group changes"
},
"GroupV1--Migration--disabled": {
@ -4822,15 +4836,15 @@
"description": "Aria label for file attachment button in composition area"
},
"CompositionArea--sms-only__title": {
"message": "This person isnt using Signal",
"message": "此人未使用 Signal",
"description": "Title for the composition area for the SMS-only contact"
},
"CompositionArea--sms-only__body": {
"message": "Signal Desktop does not support messaging non-Signal contacts. Ask this person to install Signal for a more secure messaging experience.",
"message": "Signal Desktop 不支持向非 Signal 联系人发送消息。请此人安装 Signal以体验更安全的通信。",
"description": "Body for the composition area for the SMS-only contact"
},
"CompositionArea--sms-only__spinner-label": {
"message": "Checking contact's registration status",
"message": "正在检查联系人的注册状态",
"description": "Displayed while checking if the contact is SMS-only"
},
"countMutedConversationsDescription": {
@ -4898,7 +4912,7 @@
"description": "This is the label for the 'who can edit the group' panel"
},
"ConversationDetails--group-info-info": {
"message": "Choose who can edit group name, photo, description, and disappearing messages timer.",
"message": "选择谁可以编辑群组名称、图片、描述和限时消息计时器。",
"description": "This is the additional info for the 'who can edit the group' panel"
},
"ConversationDetails--add-members-label": {
@ -5290,7 +5304,7 @@
"description": "Aria label for audio attachment's playback time slider"
},
"emptyInboxMessage": {
"message": "Click the $composeIcon$ above and search for your contacts or groups to message.",
"message": "点击上方 的 $composeIcon$,搜索发送消息的联系人或群组。",
"description": "Shown in the left-pane when the inbox is empty",
"placeholders": {
"composeIcon": {
@ -5300,7 +5314,7 @@
}
},
"composeIcon": {
"message": "compose button",
"message": "撰写按钮",
"description": "Shown in the left-pane when the inbox is empty. Describes the button that composes a new message."
},
"ForwardMessageModal--continue": {
@ -5316,11 +5330,11 @@
"description": "Shown in the message request warning dialog. Gives more information about message requests"
},
"MessageRequestWarning__dialog__learn-even-more": {
"message": "About Message Requests",
"message": "关于消息请求",
"description": "Shown in the message request warning dialog. Clicking this button will open a page on Signal's support site"
},
"ContactSpoofing__same-name": {
"message": "Review requests carefully. Signal found another contact with the same name. $link$",
"message": "请仔细审查请求。Signal 发现另一个同名联系人。$link$",
"description": "Shown in the timeline warning when you have a message request from someone with the same name as someone else",
"placeholders": {
"link": {
@ -5330,7 +5344,7 @@
}
},
"ContactSpoofing__same-name-in-group": {
"message": "$count$ group members have the same name. $link$",
"message": "$count$ 个群组成员同名。$link$",
"description": "Shown in the timeline warning when you multiple group members have the same name",
"placeholders": {
"count": {
@ -5344,15 +5358,15 @@
}
},
"ContactSpoofing__same-name__link": {
"message": "Review request",
"message": "审查请求",
"description": "Shown in the timeline warning when you have a message request from someone with the same name as someone else"
},
"ContactSpoofing__same-name-in-group__link": {
"message": "Click to review",
"message": "点击查看",
"description": "Shown in the timeline warning when you multiple group members have the same name"
},
"ContactSpoofingReviewDialog__title": {
"message": "Review request",
"message": "审查请求",
"description": "Title for the contact name spoofing review dialog"
},
"ContactSpoofingReviewDialog__description": {
@ -5368,11 +5382,11 @@
"description": "Header in the contact spoofing review dialog, shown above the \"safe\" user"
},
"ContactSpoofingReviewDialog__group__title": {
"message": "Review members",
"message": "查看成员",
"description": "Title for the contact name spoofing review dialog in groups"
},
"ContactSpoofingReviewDialog__group__description": {
"message": "$count$ group members have similar names. Review the members below or choose to take action.",
"message": "$count$ 个群组成员具有类似名称。请查看下方成员或选择操作。",
"description": "Description for the group contact spoofing review dialog"
},
"ContactSpoofingReviewDialog__group__members-header": {
@ -5380,7 +5394,7 @@
"description": "Header in the group contact spoofing review dialog. After this header, there will be a list of members"
},
"ContactSpoofingReviewDialog__group__name-change-info": {
"message": "Recently changed their profile name from $oldName$ to $newName$",
"message": "最近将其个人资料名称从 $oldName$ 更改为 $newName$",
"description": "In the group contact spoofing review dialog, this text is shown when someone has changed their name recently",
"placeholders": {
"oldName": {
@ -5398,7 +5412,7 @@
"description": "When confirming the removal of a group member, show this text in the button"
},
"RemoveGroupMemberConfirmation__description": {
"message": "Remove \"$name$\" from the group?",
"message": "是否从群组移除“$name$”?",
"description": "When confirming the removal of a group member, show this text in the dialog",
"placeholders": {
"name": {
@ -5420,27 +5434,27 @@
"description": "First paragraph in the captcha dialog"
},
"CaptchaDialog--can-close__title": {
"message": "Continue Without Verifying?",
"message": "是否继续且不验证?",
"description": "Header in the captcha dialog that can be closed"
},
"CaptchaDialog--can-close__body": {
"message": "If you choose to skip verification, you may miss messages from other people and your messages may fail to send.",
"message": "如果跳过验证,您可能错过他人发送的消息,并且您的消息可能发送失败。",
"description": "Body of the captcha dialog that can be closed"
},
"CaptchaDialog--can_close__skip-verification": {
"message": "Skip verification",
"message": "通过验证",
"description": "Skip button of the captcha dialog that can be closed"
},
"verificationComplete": {
"message": "Verification complete.",
"message": "验证完成。",
"description": "Displayed after successful captcha"
},
"verificationFailed": {
"message": "Verification failed. Please retry later.",
"message": "验证失败,请稍后重试。",
"description": "Displayed after unsuccessful captcha"
},
"deleteForEveryoneFailed": {
"message": "Failed to delete message for everyone. Please retry later.",
"message": "删除所有人的消息失败,请稍后重试。",
"description": "Displayed when delete-for-everyone has failed to send to all recepients"
},
"ChatColorPicker__delete--title": {
@ -5448,7 +5462,7 @@
"description": "Confirm title for deleting custom color"
},
"ChatColorPicker__delete--message": {
"message": "This custom color is used in $num$ chats. Do you want to delete it for all chats?",
"message": "$num$ 个对话使用该自定义颜色。是否想要对全部对话将其删除?",
"description": "Confirm message for deleting custom color",
"placeholders": {
"num": {
@ -5458,7 +5472,7 @@
}
},
"ChatColorPicker__global-chat-color": {
"message": "Global Chat Color",
"message": "全局聊天颜色",
"description": "Modal title for the chat color picker and editor for all conversations"
},
"ChatColorPicker__menu-title": {
@ -5469,10 +5483,18 @@
"message": "重置聊天颜色",
"description": "Button label for resetting chat colors"
},
"ChatColorPicker__resetDefault": {
"message": "重置聊天颜色",
"description": "Confirmation dialog title for resetting all chat colors or only the global default one"
},
"ChatColorPicker__resetAll": {
"message": "Reset all chat colors",
"message": "重置全部聊天颜色",
"description": "Button label for resetting all chat colors"
},
"ChatColorPicker__confirm-reset-default": {
"message": "重置默认",
"description": "Button label for resetting only global chat color"
},
"ChatColorPicker__confirm-reset": {
"message": "重置",
"description": "Confirm button label for resetting chat colors"
@ -5482,7 +5504,7 @@
"description": "Modal message text for confirming resetting of chat colors"
},
"ChatColorPicker__custom-color--label": {
"message": "Show custom color editor",
"message": "显示自定义颜色编辑器",
"description": "aria-label for custom color editor button"
},
"ChatColorPicker__sampleBubble1": {
@ -5490,7 +5512,7 @@
"description": "An example message bubble for selecting the chat color"
},
"ChatColorPicker__sampleBubble2": {
"message": "Another bubble.",
"message": "另一个气泡图。",
"description": "An example message bubble for selecting the chat color"
},
"ChatColorPicker__sampleBubble3": {
@ -5526,11 +5548,11 @@
"description": "Label for the saturation slider"
},
"CustomColorEditor__title": {
"message": "Custom Color",
"message": "自定义颜色",
"description": "Modal title for the custom color editor"
},
"customDisappearingTimeOption": {
"message": "Custom time...",
"message": "自定义时间...",
"description": "Text for an option in Disappearing Messages menu and Conversation Details Disappearing Messages setting when no user value is available"
},
"selectedCustomDisappearingTimeOption": {
@ -5538,11 +5560,11 @@
"description": "Text for an option in Conversation Details Disappearing Messages setting when user previously selected custom time"
},
"DisappearingTimeDialog__title": {
"message": "Custom Time",
"message": "自定义时间",
"description": "Title for the custom disappearing message timeout dialog"
},
"DisappearingTimeDialog__body": {
"message": "Choose a custom time for disappearing messages.",
"message": "自定义限时消息时间。",
"description": "Body for the custom disappearing message timeout dialog"
},
"DisappearingTimeDialog__set": {
@ -5550,23 +5572,23 @@
"description": "Text for the dialog button confirming the custom disappearing message timeout"
},
"DisappearingTimeDialog__seconds": {
"message": "Seconds",
"message": "",
"description": "Name of the 'seconds' unit select for the custom disappearing message timeout dialog"
},
"DisappearingTimeDialog__minutes": {
"message": "Minutes",
"message": "分钟",
"description": "Name of the 'minutes' unit select for the custom disappearing message timeout dialog"
},
"DisappearingTimeDialog__hours": {
"message": "Hours",
"message": "小时",
"description": "Name of the 'hours' unit select for the custom disappearing message timeout dialog"
},
"DisappearingTimeDialog__days": {
"message": "Days",
"message": "",
"description": "Name of the 'days' unit select for the custom disappearing message timeout dialog"
},
"DisappearingTimeDialog__weeks": {
"message": "Weeks",
"message": "",
"description": "Name of the 'weeks' unit select for the custom disappearing message timeout dialog"
},
"settings__DisappearingMessages__footer": {
@ -5578,7 +5600,7 @@
"description": "Label for the Disapearring Messages default timer setting"
},
"UniversalTimerNotification__text": {
"message": "The disappearing message time will be set to $timeValue$ when you message them.",
"message": "发送消息时,限时消息将设置为 $timeValue$。",
"description": "A message displayed when default disappearing message timeout is about to be applied",
"placeholders": {
"timeValue": {
@ -5588,7 +5610,7 @@
}
},
"GroupDescription__read-more": {
"message": "read more",
"message": "查看更多",
"description": "Button text when the group description is too long"
},
"EditConversationAttributesModal__description-warning": {
@ -5596,7 +5618,7 @@
"description": "Label text shown when editing group description"
},
"ConversationDetailsHeader--add-group-description": {
"message": "Add group description...",
"message": "添加群组描述...",
"description": "Placeholder text in the details header for those that can edit the group description"
}
}

Binary file not shown.

After

Width:  |  Height:  |  Size: 585 B

View File

@ -4,7 +4,7 @@
"description": "Private messaging from your desktop",
"desktopName": "signal.desktop",
"repository": "https://github.com/signalapp/Signal-Desktop.git",
"version": "5.7.0-beta.1",
"version": "5.7.1",
"license": "AGPL-3.0-only",
"author": {
"name": "Open Whisper Systems",

View File

@ -3938,7 +3938,6 @@ button.module-conversation-details__action-button {
.module-image {
position: relative;
display: inline-block;
margin: 1px;
vertical-align: middle;
overflow: hidden;
}
@ -4061,6 +4060,10 @@ button.module-conversation-details__action-button {
border-radius: 4px;
}
.module-image--cropped {
overflow: hidden;
}
.module-image--curved-top-left {
border-top-left-radius: 16px;
}
@ -4105,8 +4108,6 @@ button.module-conversation-details__action-button {
}
.module-image--gif {
border-radius: 18px;
&__filesize {
position: absolute;
top: 10px;
@ -4279,7 +4280,7 @@ button.module-image__border-overlay:focus {
flex-direction: row;
align-items: center;
margin: -1px;
gap: 1px;
}
.module-image-grid--one-image {
@ -4294,6 +4295,7 @@ button.module-image__border-overlay:focus {
display: inline-flex;
flex-direction: column;
align-items: center;
gap: 1px;
}
.module-image-grid__row {
@ -4301,6 +4303,7 @@ button.module-image__border-overlay:focus {
flex-direction: row;
align-items: center;
flex-grow: 1;
gap: 1px;
}
// Module: Typing Animation
@ -5380,6 +5383,9 @@ button.module-image__border-overlay:focus {
display: flex;
justify-content: space-between;
padding-top: var(--title-bar-drag-area-height);
position: absolute;
width: 100%;
z-index: 2;
}
.module-incoming-call__contact {
@ -10621,11 +10627,6 @@ $contact-modal-padding: 18px;
&--deleted-for-everyone {
font-style: italic;
}
&--gif {
border-radius: inherit;
background: inherit;
}
}
.module-message__context {

View File

@ -1,5 +1,6 @@
.App {
height: 100%;
position: relative;
&.light-theme {
background-color: $color-white;

View File

@ -2284,6 +2284,10 @@ export async function startApp(): Promise<void> {
}
}
window.subscribeToSystemThemeChange(() => {
onChangeTheme();
});
const FIVE_MINUTES = 5 * 60 * 1000;
// Note: once this function returns, there still might be messages being processed on

View File

@ -175,7 +175,8 @@ const globalContents: Contents = {
export const GlobalAudioContext = React.createContext<Contents>(globalContents);
export type GlobalAudioProps = {
conversationId: string;
conversationId: string | undefined;
isPaused: boolean;
children?: React.ReactNode | React.ReactChildren;
};
@ -185,6 +186,7 @@ export type GlobalAudioProps = {
*/
export const GlobalAudioProvider: React.FC<GlobalAudioProps> = ({
conversationId,
isPaused,
children,
}) => {
// When moving between conversations - stop audio
@ -194,6 +196,13 @@ export const GlobalAudioProvider: React.FC<GlobalAudioProps> = ({
};
}, [conversationId]);
// Pause when requested by parent
React.useEffect(() => {
if (isPaused) {
globalContents.audio.pause();
}
}, [isPaused]);
return (
<GlobalAudioContext.Provider value={globalContents}>
{children}

View File

@ -42,7 +42,7 @@ story.add('Image', () => {
return <Lightbox {...props} />;
});
story.add('Image with Caption', () => {
story.add('Image with Caption (normal image)', () => {
const props = createProps({
caption:
'This is the user-provided caption. It can get long and wrap onto multiple lines.',
@ -52,6 +52,16 @@ story.add('Image with Caption', () => {
return <Lightbox {...props} />;
});
story.add('Image with Caption (all-white image)', () => {
const props = createProps({
caption:
'This is the user-provided caption. It should be visible on light backgrounds.',
objectURL: '/fixtures/2000x2000-white.png',
});
return <Lightbox {...props} />;
});
story.add('Video', () => {
const props = createProps({
contentType: VIDEO_MP4,

View File

@ -107,6 +107,8 @@ const styles = {
right: 0,
textAlign: 'center',
color: 'white',
fontWeight: 'bold',
textShadow: '0 0 1px black, 0 0 2px black, 0 0 3px black, 0 0 4px black',
padding: '1em',
paddingLeft: '3em',
paddingRight: '3em',

View File

@ -4,7 +4,6 @@
import React, { useRef, useState, useEffect } from 'react';
import classNames from 'classnames';
import { Blurhash } from 'react-blurhash';
import formatFileSize from 'filesize';
import { LocalizerType, ThemeType } from '../../types/Util';
import { Spinner } from '../Spinner';
@ -170,7 +169,7 @@ export const GIF: React.FC<Props> = props => {
if (isNotDownloaded && attachment.fileSize) {
fileSize = (
<div className="module-image--gif__filesize">
{formatFileSize(attachment.fileSize || 0)} · GIF
{attachment.fileSize} · GIF
</div>
);
}

View File

@ -20,6 +20,8 @@ export type Props = {
height?: number;
width?: number;
cropWidth?: number;
cropHeight?: number;
tabIndex?: number;
overlayText?: string;
@ -158,6 +160,8 @@ export class Image extends React.Component<Props> {
theme,
url,
width = 0,
cropWidth = 0,
cropHeight = 0,
} = this.props;
const { caption, pending } = attachment || { caption: null, pending: true };
@ -204,8 +208,10 @@ export class Image extends React.Component<Props> {
curveTopLeft ? 'module-image--curved-top-left' : null,
curveTopRight ? 'module-image--curved-top-right' : null,
smallCurveTopLeft ? 'module-image--small-curved-top-left' : null,
softCorners ? 'module-image--soft-corners' : null
softCorners ? 'module-image--soft-corners' : null,
cropWidth || cropHeight ? 'module-image--cropped' : null
)}
style={{ width: width - cropWidth, height: height - cropHeight }}
>
{pending ? (
this.renderPending()

View File

@ -34,6 +34,8 @@ export type Props = {
onClick?: (attachment: AttachmentType) => void;
};
const GAP = 1;
export const ImageGrid = ({
attachments,
bottomOverlay,
@ -113,8 +115,9 @@ export const ImageGrid = ({
curveTopLeft={curveTopLeft}
curveBottomLeft={curveBottomLeft}
playIconOverlay={isVideoAttachment(attachments[0])}
height={149}
width={149}
height={150}
width={150}
cropWidth={GAP}
url={getThumbnailUrl(attachments[0])}
onClick={onClick}
onError={onError}
@ -129,8 +132,8 @@ export const ImageGrid = ({
curveTopRight={curveTopRight}
curveBottomRight={curveBottomRight}
playIconOverlay={isVideoAttachment(attachments[1])}
height={149}
width={149}
height={150}
width={150}
attachment={attachments[1]}
url={getThumbnailUrl(attachments[1])}
onClick={onClick}
@ -155,7 +158,8 @@ export const ImageGrid = ({
attachment={attachments[0]}
playIconOverlay={isVideoAttachment(attachments[0])}
height={200}
width={199}
width={200}
cropWidth={GAP}
url={getUrl(attachments[0])}
onClick={onClick}
onError={onError}
@ -167,8 +171,9 @@ export const ImageGrid = ({
theme={theme}
blurHash={attachments[1].blurHash}
curveTopRight={curveTopRight}
height={99}
width={99}
height={100}
width={100}
cropHeight={GAP}
attachment={attachments[1]}
playIconOverlay={isVideoAttachment(attachments[1])}
url={getThumbnailUrl(attachments[1])}
@ -183,8 +188,8 @@ export const ImageGrid = ({
bottomOverlay={withBottomOverlay}
noBorder={false}
curveBottomRight={curveBottomRight}
height={99}
width={99}
height={100}
width={100}
attachment={attachments[2]}
playIconOverlay={isVideoAttachment(attachments[2])}
url={getThumbnailUrl(attachments[2])}
@ -210,8 +215,10 @@ export const ImageGrid = ({
noBorder={false}
attachment={attachments[0]}
playIconOverlay={isVideoAttachment(attachments[0])}
height={149}
width={149}
height={150}
width={150}
cropHeight={GAP}
cropWidth={GAP}
url={getThumbnailUrl(attachments[0])}
onClick={onClick}
onError={onError}
@ -224,8 +231,9 @@ export const ImageGrid = ({
curveTopRight={curveTopRight}
playIconOverlay={isVideoAttachment(attachments[1])}
noBorder={false}
height={149}
width={149}
height={150}
width={150}
cropHeight={GAP}
attachment={attachments[1]}
url={getThumbnailUrl(attachments[1])}
onClick={onClick}
@ -242,8 +250,9 @@ export const ImageGrid = ({
noBorder={false}
curveBottomLeft={curveBottomLeft}
playIconOverlay={isVideoAttachment(attachments[2])}
height={149}
width={149}
height={150}
width={150}
cropWidth={GAP}
attachment={attachments[2]}
url={getThumbnailUrl(attachments[2])}
onClick={onClick}
@ -258,8 +267,8 @@ export const ImageGrid = ({
noBorder={false}
curveBottomRight={curveBottomRight}
playIconOverlay={isVideoAttachment(attachments[3])}
height={149}
width={149}
height={150}
width={150}
attachment={attachments[3]}
url={getThumbnailUrl(attachments[3])}
onClick={onClick}
@ -288,8 +297,9 @@ export const ImageGrid = ({
curveTopLeft={curveTopLeft}
attachment={attachments[0]}
playIconOverlay={isVideoAttachment(attachments[0])}
height={149}
width={149}
height={150}
width={150}
cropWidth={GAP}
url={getThumbnailUrl(attachments[0])}
onClick={onClick}
onError={onError}
@ -301,8 +311,8 @@ export const ImageGrid = ({
blurHash={attachments[1].blurHash}
curveTopRight={curveTopRight}
playIconOverlay={isVideoAttachment(attachments[1])}
height={149}
width={149}
height={150}
width={150}
attachment={attachments[1]}
url={getThumbnailUrl(attachments[1])}
onClick={onClick}
@ -319,8 +329,9 @@ export const ImageGrid = ({
noBorder={isSticker}
curveBottomLeft={curveBottomLeft}
playIconOverlay={isVideoAttachment(attachments[2])}
height={99}
width={99}
height={100}
width={100}
cropWidth={GAP}
attachment={attachments[2]}
url={getThumbnailUrl(attachments[2])}
onClick={onClick}
@ -334,8 +345,9 @@ export const ImageGrid = ({
bottomOverlay={withBottomOverlay}
noBorder={isSticker}
playIconOverlay={isVideoAttachment(attachments[3])}
height={99}
width={98}
height={100}
width={100}
cropWidth={GAP}
attachment={attachments[3]}
url={getThumbnailUrl(attachments[3])}
onClick={onClick}
@ -350,8 +362,8 @@ export const ImageGrid = ({
noBorder={isSticker}
curveBottomRight={curveBottomRight}
playIconOverlay={isVideoAttachment(attachments[4])}
height={99}
width={99}
height={100}
width={100}
darkOverlay={moreMessagesOverlay}
overlayText={moreMessagesOverlayText}
attachment={attachments[4]}

View File

@ -47,19 +47,22 @@ const renderEmojiPicker: Props['renderEmojiPicker'] = ({
);
const MessageAudioContainer: React.FC<AudioAttachmentProps> = props => {
const [activeAudioID, setActiveAudioID] = React.useState<string | undefined>(
undefined
);
const [active, setActive] = React.useState<{
id?: string;
context?: string;
}>({});
const audio = React.useMemo(() => new Audio(), []);
return (
<MessageAudio
{...props}
id="storybook"
renderingContext="storybook"
audio={audio}
computePeaks={computePeaks}
setActiveAudioID={setActiveAudioID}
activeAudioID={activeAudioID}
setActiveAudioID={(id, context) => setActive({ id, context })}
activeAudioID={active.id}
activeAudioContext={active.context}
/>
);
};
@ -101,6 +104,7 @@ const createProps = (overrideProps: Partial<Props> = {}): Props => ({
undefined,
i18n,
id: text('id', overrideProps.id || ''),
renderingContext: 'storybook',
interactionMode: overrideProps.interactionMode || 'keyboard',
isSticker: isBoolean(overrideProps.isSticker)
? overrideProps.isSticker
@ -720,43 +724,52 @@ story.add('Image', () => {
return renderBothDirections(props);
});
story.add('Multiple Images', () => {
const props = createProps({
attachments: [
{
url: '/fixtures/tina-rolf-269345-unsplash.jpg',
fileName: 'tina-rolf-269345-unsplash.jpg',
contentType: IMAGE_JPEG,
width: 128,
height: 128,
},
{
url: '/fixtures/tina-rolf-269345-unsplash.jpg',
fileName: 'tina-rolf-269345-unsplash.jpg',
contentType: IMAGE_JPEG,
width: 128,
height: 128,
},
{
url: '/fixtures/tina-rolf-269345-unsplash.jpg',
fileName: 'tina-rolf-269345-unsplash.jpg',
contentType: IMAGE_JPEG,
width: 128,
height: 128,
},
{
url: '/fixtures/tina-rolf-269345-unsplash.jpg',
fileName: 'tina-rolf-269345-unsplash.jpg',
contentType: IMAGE_JPEG,
width: 128,
height: 128,
},
],
status: 'sent',
});
for (let i = 2; i <= 5; i += 1) {
story.add(`Multiple Images x${i}`, () => {
const props = createProps({
attachments: [
{
url: '/fixtures/tina-rolf-269345-unsplash.jpg',
fileName: 'tina-rolf-269345-unsplash.jpg',
contentType: IMAGE_JPEG,
width: 128,
height: 128,
},
{
url: '/fixtures/tina-rolf-269345-unsplash.jpg',
fileName: 'tina-rolf-269345-unsplash.jpg',
contentType: IMAGE_JPEG,
width: 128,
height: 128,
},
{
url: '/fixtures/tina-rolf-269345-unsplash.jpg',
fileName: 'tina-rolf-269345-unsplash.jpg',
contentType: IMAGE_JPEG,
width: 128,
height: 128,
},
{
url: '/fixtures/tina-rolf-269345-unsplash.jpg',
fileName: 'tina-rolf-269345-unsplash.jpg',
contentType: IMAGE_JPEG,
width: 128,
height: 128,
},
{
url: '/fixtures/tina-rolf-269345-unsplash.jpg',
fileName: 'tina-rolf-269345-unsplash.jpg',
contentType: IMAGE_JPEG,
width: 128,
height: 128,
},
].slice(0, i),
status: 'sent',
});
return renderBothDirections(props);
});
return renderBothDirections(props);
});
}
story.add('Image with Caption', () => {
const props = createProps({
@ -794,6 +807,25 @@ story.add('GIF', () => {
return renderBothDirections(props);
});
story.add('GIF in a group', () => {
const props = createProps({
attachments: [
{
contentType: VIDEO_MP4,
flags: SignalService.AttachmentPointer.Flags.GIF,
fileName: 'cat-gif.mp4',
url: '/fixtures/cat-gif.mp4',
width: 400,
height: 332,
},
],
conversationType: 'group',
status: 'sent',
});
return renderBothDirections(props);
});
story.add('Not Downloaded GIF', () => {
const props = createProps({
attachments: [
@ -801,7 +833,7 @@ story.add('Not Downloaded GIF', () => {
contentType: VIDEO_MP4,
flags: SignalService.AttachmentPointer.Flags.GIF,
fileName: 'cat-gif.mp4',
fileSize: 188610,
fileSize: '188.61 KB',
blurHash: 'LDA,FDBnm+I=p{tkIUI;~UkpELV]',
width: 400,
height: 332,
@ -821,7 +853,7 @@ story.add('Pending GIF', () => {
contentType: VIDEO_MP4,
flags: SignalService.AttachmentPointer.Flags.GIF,
fileName: 'cat-gif.mp4',
fileSize: 188610,
fileSize: '188.61 KB',
blurHash: 'LDA,FDBnm+I=p{tkIUI;~UkpELV]',
width: 400,
height: 332,

View File

@ -89,6 +89,7 @@ export type DirectionType = typeof Directions[number];
export type AudioAttachmentProps = {
id: string;
renderingContext: string;
i18n: LocalizerType;
buttonRef: React.RefObject<HTMLButtonElement>;
direction: DirectionType;
@ -103,6 +104,7 @@ export type AudioAttachmentProps = {
export type PropsData = {
id: string;
renderingContext: string;
contactNameColor?: ContactNameColorType;
conversationColor: ConversationColorType;
customColor?: CustomColorType;
@ -751,6 +753,7 @@ export class Message extends React.Component<Props, State> {
direction,
i18n,
id,
renderingContext,
kickOffAttachmentDownload,
markAttachmentAsCorrupted,
quote,
@ -849,6 +852,7 @@ export class Message extends React.Component<Props, State> {
i18n,
buttonRef: this.audioButtonRef,
id,
renderingContext,
direction,
theme,
attachment: firstAttachment,
@ -1662,8 +1666,8 @@ export class Message extends React.Component<Props, State> {
if (attachments && attachments.length) {
if (isGIF(attachments)) {
// Message container border + image border
return GIF_SIZE + 4;
// Message container border
return GIF_SIZE + 2;
}
if (isSticker) {

View File

@ -14,6 +14,7 @@ import { ComputePeaksResult } from '../GlobalAudioContext';
export type Props = {
direction?: 'incoming' | 'outgoing';
id: string;
renderingContext: string;
i18n: LocalizerType;
attachment: AttachmentType;
withContentAbove: boolean;
@ -28,7 +29,8 @@ export type Props = {
computePeaks(url: string, barCount: number): Promise<ComputePeaksResult>;
activeAudioID: string | undefined;
setActiveAudioID: (id: string | undefined) => void;
activeAudioContext: string | undefined;
setActiveAudioID: (id: string | undefined, context: string) => void;
};
type ButtonProps = {
@ -121,14 +123,19 @@ const Button: React.FC<ButtonProps> = props => {
* toggle Play/Pause button.
*
* A global audio player is used for playback and access is managed by the
* `activeAudioID` property. Whenever `activeAudioID` property is equal to `id`
* the instance of the `MessageAudio` assumes the ownership of the `Audio`
* instance and fully manages it.
* `activeAudioID` and `activeAudioContext` properties. Whenever both
* `activeAudioID` and `activeAudioContext` are equal to `id` and `context`
* respectively the instance of the `MessageAudio` assumes the ownership of the
* `Audio` instance and fully manages it.
*
* `context` is required for displaying separate MessageAudio instances in
* MessageDetails and Message React components.
*/
export const MessageAudio: React.FC<Props> = (props: Props) => {
const {
i18n,
id,
renderingContext,
direction,
attachment,
withContentAbove,
@ -142,12 +149,14 @@ export const MessageAudio: React.FC<Props> = (props: Props) => {
computePeaks,
activeAudioID,
activeAudioContext,
setActiveAudioID,
} = props;
assert(audio !== null, 'GlobalAudioContext always provides audio');
const isActive = activeAudioID === id;
const isActive =
activeAudioID === id && activeAudioContext === renderingContext;
const waveformRef = useRef<HTMLDivElement | null>(null);
const [isPlaying, setIsPlaying] = useState(isActive && !audio.paused);
@ -317,7 +326,7 @@ export const MessageAudio: React.FC<Props> = (props: Props) => {
if (!isActive && !isPlaying) {
window.log.info('MessageAudio: changing owner', id);
setActiveAudioID(id);
setActiveAudioID(id, renderingContext);
// Pause old audio
if (!audio.paused) {

View File

@ -30,6 +30,7 @@ const defaultMessage: MessageDataPropsType = {
conversationType: 'direct',
direction: 'incoming',
id: 'my-message',
renderingContext: 'storybook',
isBlocked: false,
isMessageRequestAccepted: true,
previews: [],

View File

@ -6,7 +6,6 @@ import classNames from 'classnames';
import moment from 'moment';
import { noop } from 'lodash';
import { GlobalAudioProvider } from '../GlobalAudioContext';
import { Avatar } from '../Avatar';
import { ContactName } from './ContactName';
import {
@ -46,7 +45,7 @@ export type Props = {
contacts: Array<Contact>;
contactNameColor?: ContactNameColorType;
errors: Array<Error>;
message: MessagePropsDataType;
message: Omit<MessagePropsDataType, 'renderingContext'>;
receivedAt: number;
sentAt: number;
@ -266,57 +265,54 @@ export class MessageDetail extends React.Component<Props> {
// eslint-disable-next-line jsx-a11y/no-noninteractive-tabindex
<div className="module-message-detail" tabIndex={0} ref={this.focusRef}>
<div className="module-message-detail__message-container">
<GlobalAudioProvider conversationId={message.conversationId}>
<Message
{...message}
checkForAccount={checkForAccount}
clearSelectedMessage={clearSelectedMessage}
contactNameColor={contactNameColor}
deleteMessage={deleteMessage}
deleteMessageForEveryone={deleteMessageForEveryone}
disableMenu
disableScroll
displayTapToViewMessage={displayTapToViewMessage}
downloadAttachment={downloadAttachment}
doubleCheckMissingQuoteReference={
doubleCheckMissingQuoteReference
}
i18n={i18n}
interactionMode={interactionMode}
kickOffAttachmentDownload={kickOffAttachmentDownload}
markAttachmentAsCorrupted={markAttachmentAsCorrupted}
onHeightChange={noop}
openConversation={openConversation}
openLink={openLink}
reactToMessage={reactToMessage}
renderAudioAttachment={renderAudioAttachment}
renderEmojiPicker={renderEmojiPicker}
replyToMessage={replyToMessage}
retrySend={retrySend}
showForwardMessageModal={showForwardMessageModal}
scrollToQuotedMessage={() => {
assert(
false,
'scrollToQuotedMessage should never be called because scrolling is disabled'
);
}}
showContactDetail={showContactDetail}
showContactModal={showContactModal}
showExpiredIncomingTapToViewToast={
showExpiredIncomingTapToViewToast
}
showExpiredOutgoingTapToViewToast={
showExpiredOutgoingTapToViewToast
}
showMessageDetail={() => {
assert(
false,
"showMessageDetail should never be called because the menu is disabled (and we're already in the message detail!)"
);
}}
showVisualAttachment={showVisualAttachment}
/>
</GlobalAudioProvider>
<Message
{...message}
renderingContext="conversation/MessageDetail"
checkForAccount={checkForAccount}
clearSelectedMessage={clearSelectedMessage}
contactNameColor={contactNameColor}
deleteMessage={deleteMessage}
deleteMessageForEveryone={deleteMessageForEveryone}
disableMenu
disableScroll
displayTapToViewMessage={displayTapToViewMessage}
downloadAttachment={downloadAttachment}
doubleCheckMissingQuoteReference={doubleCheckMissingQuoteReference}
i18n={i18n}
interactionMode={interactionMode}
kickOffAttachmentDownload={kickOffAttachmentDownload}
markAttachmentAsCorrupted={markAttachmentAsCorrupted}
onHeightChange={noop}
openConversation={openConversation}
openLink={openLink}
reactToMessage={reactToMessage}
renderAudioAttachment={renderAudioAttachment}
renderEmojiPicker={renderEmojiPicker}
replyToMessage={replyToMessage}
retrySend={retrySend}
showForwardMessageModal={showForwardMessageModal}
scrollToQuotedMessage={() => {
assert(
false,
'scrollToQuotedMessage should never be called because scrolling is disabled'
);
}}
showContactDetail={showContactDetail}
showContactModal={showContactModal}
showExpiredIncomingTapToViewToast={
showExpiredIncomingTapToViewToast
}
showExpiredOutgoingTapToViewToast={
showExpiredOutgoingTapToViewToast
}
showMessageDetail={() => {
assert(
false,
"showMessageDetail should never be called because the menu is disabled (and we're already in the message detail!)"
);
}}
showVisualAttachment={showVisualAttachment}
/>
</div>
<table className="module-message-detail__info">
<tbody>

View File

@ -50,6 +50,7 @@ const defaultMessageProps: MessagesProps = {
),
i18n,
id: 'messageId',
renderingContext: 'storybook',
interactionMode: 'keyboard',
isBlocked: false,
isMessageRequestAccepted: true,

View File

@ -15,8 +15,6 @@ import Measure from 'react-measure';
import { ScrollDownButton } from './ScrollDownButton';
import { GlobalAudioProvider } from '../GlobalAudioContext';
import { LocalizerType } from '../../types/Util';
import { ConversationType } from '../../state/ducks/conversations';
import { assert } from '../../util/assert';
@ -1424,9 +1422,8 @@ export class Timeline extends React.PureComponent<PropsType, StateType> {
>
{timelineWarning}
<GlobalAudioProvider conversationId={id}>
{autoSizer}
</GlobalAudioProvider>
{autoSizer}
{shouldShowScrollDownButton ? (
<ScrollDownButton
conversationId={id}

View File

@ -80,7 +80,7 @@ type LinkNotificationType = {
};
type MessageType = {
type: 'message';
data: MessageProps;
data: Omit<MessageProps, 'renderingContext'>;
};
type UnsupportedMessageType = {
type: 'unsupportedMessage';
@ -189,7 +189,13 @@ export class TimelineItem extends React.PureComponent<PropsType> {
if (item.type === 'message') {
return (
<Message {...this.props} {...item.data} i18n={i18n} theme={theme} />
<Message
{...this.props}
{...item.data}
i18n={i18n}
theme={theme}
renderingContext="conversation/TimelineItem"
/>
);
}

View File

@ -1913,6 +1913,28 @@ function updateToSchemaVersion34(currentVersion: number, db: Database) {
console.log('updateToSchemaVersion34: success!');
}
function updateToSchemaVersion35(currentVersion: number, db: Database) {
if (currentVersion >= 35) {
return;
}
db.transaction(() => {
db.exec(`
CREATE INDEX expiring_message_by_conversation_and_received_at
ON messages
(
expirationStartTimestamp,
expireTimer,
conversationId,
received_at
);
`);
db.pragma('user_version = 35');
})();
console.log('updateToSchemaVersion35: success!');
}
const SCHEMA_VERSIONS = [
updateToSchemaVersion1,
updateToSchemaVersion2,
@ -1948,6 +1970,7 @@ const SCHEMA_VERSIONS = [
updateToSchemaVersion32,
updateToSchemaVersion33,
updateToSchemaVersion34,
updateToSchemaVersion35,
];
function updateSchema(db: Database): void {
@ -3358,6 +3381,7 @@ async function getUnreadByConversationAndMarkRead(
db.prepare<Query>(
`
UPDATE messages
INDEXED BY expiring_message_by_conversation_and_received_at
SET
expirationStartTimestamp = $expirationStartTimestamp,
json = json_patch(json, $jsonPatch)
@ -3380,8 +3404,9 @@ async function getUnreadByConversationAndMarkRead(
const rows = db
.prepare<Query>(
`
SELECT id, json
FROM messages WHERE
SELECT id, json FROM messages
INDEXED BY messages_unread
WHERE
unread = $unread AND
conversationId = $conversationId AND
received_at <= $newestUnreadId

View File

@ -3,12 +3,17 @@
import { useBoundActions } from '../../util/hooks';
import { SwitchToAssociatedViewActionType } from './conversations';
import {
SwitchToAssociatedViewActionType,
MessageDeletedActionType,
MessageChangedActionType,
} from './conversations';
// State
export type AudioPlayerStateType = {
readonly activeAudioID: string | undefined;
readonly activeAudioContext: string | undefined;
};
// Actions
@ -17,6 +22,7 @@ type SetActiveAudioIDAction = {
type: 'audioPlayer/SET_ACTIVE_AUDIO_ID';
payload: {
id: string | undefined;
context: string | undefined;
};
};
@ -30,10 +36,13 @@ export const actions = {
export const useActions = (): typeof actions => useBoundActions(actions);
function setActiveAudioID(id: string | undefined): SetActiveAudioIDAction {
function setActiveAudioID(
id: string | undefined,
context: string
): SetActiveAudioIDAction {
return {
type: 'audioPlayer/SET_ACTIVE_AUDIO_ID',
payload: { id },
payload: { id, context },
};
}
@ -42,12 +51,18 @@ function setActiveAudioID(id: string | undefined): SetActiveAudioIDAction {
function getEmptyState(): AudioPlayerStateType {
return {
activeAudioID: undefined,
activeAudioContext: undefined,
};
}
export function reducer(
state: Readonly<AudioPlayerStateType> = getEmptyState(),
action: Readonly<AudioPlayerActionType | SwitchToAssociatedViewActionType>
action: Readonly<
| AudioPlayerActionType
| SwitchToAssociatedViewActionType
| MessageDeletedActionType
| MessageChangedActionType
>
): AudioPlayerStateType {
if (action.type === 'audioPlayer/SET_ACTIVE_AUDIO_ID') {
const { payload } = action;
@ -55,6 +70,7 @@ export function reducer(
return {
...state,
activeAudioID: payload.id,
activeAudioContext: payload.context,
};
}
@ -66,5 +82,36 @@ export function reducer(
};
}
// Reset activeAudioID on when played message is deleted on expiration.
if (action.type === 'MESSAGE_DELETED') {
const { id } = action.payload;
if (state.activeAudioID !== id) {
return state;
}
return {
...state,
activeAudioID: undefined,
};
}
// Reset activeAudioID on when played message is deleted for everyone.
if (action.type === 'MESSAGE_CHANGED') {
const { id, data } = action.payload;
if (state.activeAudioID !== id) {
return state;
}
if (!data.deletedForEveryone) {
return state;
}
return {
...state,
activeAudioID: undefined,
};
}
return state;
}

View File

@ -7,9 +7,12 @@ import { Provider } from 'react-redux';
import { Store } from 'redux';
import { SmartApp } from '../smart/App';
import { SmartGlobalAudioProvider } from '../smart/GlobalAudioProvider';
export const createApp = (store: Store): ReactElement => (
<Provider store={store}>
<SmartApp />
<SmartGlobalAudioProvider>
<SmartApp />
</SmartGlobalAudioProvider>
</Provider>
);

View File

@ -0,0 +1,8 @@
// Copyright 2021 Signal Messenger, LLC
// SPDX-License-Identifier: AGPL-3.0-only
import { StateType } from '../reducer';
export const isPaused = (state: StateType): boolean => {
return state.audioPlayer.activeAudioID === undefined;
};

View File

@ -8,7 +8,6 @@ import {
LastMessageStatus,
MessageAttributesType,
ShallowChallengeError,
WhatIsThis,
} from '../../model-types.d';
import { TimelineItemType } from '../../components/conversation/TimelineItem';
@ -310,7 +309,7 @@ export function getPropsForMessage(
readReceiptSetting: boolean,
regionCode: string,
accountSelector: (identifier?: string) => boolean
): PropsForMessage {
): Omit<PropsForMessage, 'renderingContext'> {
const contact = getContact(
message,
conversationSelector,
@ -940,7 +939,7 @@ export function getPropsForEmbeddedContact(
}
export function getPropsForAttachment(
attachment: WhatIsThis
attachment: AttachmentType
): AttachmentType | null {
if (!attachment) {
return null;
@ -950,10 +949,12 @@ export function getPropsForAttachment(
return {
...attachment,
fileSize: size ? filesize(size) : null,
fileSize: size ? filesize(size) : undefined,
isVoiceMessage: isVoiceMessage(attachment),
pending,
url: path ? window.Signal.Migrations.getAbsoluteAttachmentPath(path) : null,
url: path
? window.Signal.Migrations.getAbsoluteAttachmentPath(path)
: undefined,
screenshot: screenshot
? {
...screenshot,
@ -961,7 +962,7 @@ export function getPropsForAttachment(
screenshot.path
),
}
: null,
: undefined,
thumbnail: thumbnail
? {
...thumbnail,
@ -969,7 +970,7 @@ export function getPropsForAttachment(
thumbnail.path
),
}
: null,
: undefined,
};
}

View File

@ -0,0 +1,20 @@
// Copyright 2021 Signal Messenger, LLC
// SPDX-License-Identifier: AGPL-3.0-only
import { connect } from 'react-redux';
import { mapDispatchToProps } from '../actions';
import { GlobalAudioProvider } from '../../components/GlobalAudioContext';
import { StateType } from '../reducer';
import { isPaused } from '../selectors/audioPlayer';
import { getSelectedConversationId } from '../selectors/conversations';
const mapStateToProps = (state: StateType) => {
return {
conversationId: getSelectedConversationId(state),
isPaused: isPaused(state),
};
};
const smart = connect(mapStateToProps, mapDispatchToProps);
export const SmartGlobalAudioProvider = smart(GlobalAudioProvider);

View File

@ -16,6 +16,7 @@ export type Props = {
direction?: 'incoming' | 'outgoing';
id: string;
renderingContext: string;
i18n: LocalizerType;
attachment: AttachmentType;
withContentAbove: boolean;

View File

@ -24,7 +24,7 @@ export { Contact } from '../../components/conversation/MessageDetail';
export type OwnProps = {
contacts: Array<Contact>;
errors: Array<Error>;
message: MessagePropsDataType;
message: Omit<MessagePropsDataType, 'renderingContext'>;
receivedAt: number;
sentAt: number;

View File

@ -77,6 +77,7 @@ function renderItem(
function renderLastSeenIndicator(id: string): JSX.Element {
return <SmartLastSeenIndicator id={id} />;
}
function renderHeroRow(
id: string,
onHeightChange: () => unknown,

View File

@ -4,22 +4,93 @@
import { assert } from 'chai';
import { actions } from '../../../state/ducks/audioPlayer';
import {
actions as conversationsActions,
SwitchToAssociatedViewActionType,
} from '../../../state/ducks/conversations';
import { noopAction } from '../../../state/ducks/noop';
import { StateType, reducer as rootReducer } from '../../../state/reducer';
const { messageDeleted, messageChanged } = conversationsActions;
const MESSAGE_ID = 'message-id';
describe('both/state/ducks/audioPlayer', () => {
const getEmptyRootState = (): StateType => {
return rootReducer(undefined, noopAction());
};
const getInitializedState = (): StateType => {
const state = getEmptyRootState();
const updated = rootReducer(
state,
actions.setActiveAudioID(MESSAGE_ID, 'context')
);
assert.strictEqual(updated.audioPlayer.activeAudioID, MESSAGE_ID);
assert.strictEqual(updated.audioPlayer.activeAudioContext, 'context');
return updated;
};
describe('setActiveAudioID', () => {
it("updates `activeAudioID` in the audioPlayer's state", () => {
const state = getEmptyRootState();
assert.strictEqual(state.audioPlayer.activeAudioID, undefined);
const updated = rootReducer(state, actions.setActiveAudioID('test'));
const updated = rootReducer(
state,
actions.setActiveAudioID('test', 'context')
);
assert.strictEqual(updated.audioPlayer.activeAudioID, 'test');
assert.strictEqual(updated.audioPlayer.activeAudioContext, 'context');
});
});
it('resets activeAudioID when changing the conversation', () => {
const state = getInitializedState();
const updated = rootReducer(state, <SwitchToAssociatedViewActionType>{
type: 'SWITCH_TO_ASSOCIATED_VIEW',
payload: { conversationId: 'any' },
});
assert.strictEqual(updated.audioPlayer.activeAudioID, undefined);
assert.strictEqual(updated.audioPlayer.activeAudioContext, 'context');
});
it('resets activeAudioID when message was deleted', () => {
const state = getInitializedState();
const updated = rootReducer(
state,
messageDeleted(MESSAGE_ID, 'conversation-id')
);
assert.strictEqual(updated.audioPlayer.activeAudioID, undefined);
assert.strictEqual(updated.audioPlayer.activeAudioContext, 'context');
});
it('resets activeAudioID when message was erased', () => {
const state = getInitializedState();
const updated = rootReducer(
state,
messageChanged(MESSAGE_ID, 'conversation-id', {
id: MESSAGE_ID,
type: 'incoming',
sent_at: 1,
received_at: 1,
timestamp: 1,
conversationId: 'conversation-id',
deletedForEveryone: true,
})
);
assert.strictEqual(updated.audioPlayer.activeAudioID, undefined);
assert.strictEqual(updated.audioPlayer.activeAudioContext, 'context');
});
});

View File

@ -0,0 +1,32 @@
// Copyright 2021 Signal Messenger, LLC
// SPDX-License-Identifier: AGPL-3.0-only
import { assert } from 'chai';
import { actions } from '../../../state/ducks/audioPlayer';
import { noopAction } from '../../../state/ducks/noop';
import { isPaused } from '../../../state/selectors/audioPlayer';
import { StateType, reducer as rootReducer } from '../../../state/reducer';
describe('state/selectors/audioPlayer', () => {
const getEmptyRootState = (): StateType => {
return rootReducer(undefined, noopAction());
};
describe('isPaused', () => {
it('returns true if state.audioPlayer.activeAudioID is undefined', () => {
const state = getEmptyRootState();
assert.isTrue(isPaused(state));
});
it('returns false if state.audioPlayer.activeAudioID is not undefined', () => {
const state = getEmptyRootState();
const updated = rootReducer(
state,
actions.setActiveAudioID('id', 'context')
);
assert.isFalse(isPaused(updated));
});
});
});

View File

@ -31,7 +31,7 @@ export type AttachmentType = {
/** For messages not already on disk, this will be a data url */
url?: string;
size?: number;
fileSize?: number;
fileSize?: string;
pending?: boolean;
width?: number;
height?: number;
@ -280,21 +280,38 @@ export function getGridDimensions(
}
if (attachments.length === 2) {
// A B
return {
height: 150,
width: 300,
};
}
if (attachments.length === 3) {
// A A B
// A A C
return {
height: 200,
width: 300,
};
}
if (attachments.length === 4) {
// A B
// C D
return {
height: 300,
width: 300,
};
}
// A A A B B B
// A A A B B B
// A A A B B B
// C C D D E E
// C C D D E E
return {
height: 200,
height: 250,
width: 300,
};
}

View File

@ -32,6 +32,7 @@ import {
isOutgoing,
isTapToView,
} from '../state/selectors/message';
import { getMessagesByConversation } from '../state/selectors/conversations';
import { ConversationDetailsMembershipList } from '../components/conversation/conversation-details/ConversationDetailsMembershipList';
import { showSafetyNumberChangeDialog } from '../shims/showSafetyNumberChangeDialog';
@ -1144,7 +1145,20 @@ Whisper.ConversationView = Whisper.View.extend({
throw new Error(`scrollToMessage: failed to load message ${messageId}`);
}
const isInMemory = Boolean(window.MessageController.getById(messageId));
const state = window.reduxStore.getState();
let isInMemory = true;
if (!window.MessageController.getById(messageId)) {
isInMemory = false;
}
// Message might be in memory, but not in the redux anymore because
// we call `messageReset()` in `loadAndScroll()`.
const messagesByConversation = getMessagesByConversation(state)[model.id];
if (!messagesByConversation?.messageIds.includes(messageId)) {
isInMemory = false;
}
if (isInMemory) {
const { scrollToMessage } = window.reduxActions.conversations;
@ -2416,15 +2430,14 @@ Whisper.ConversationView = Whisper.View.extend({
}
const sendMessageOptions = { dontClearDraft: true };
let timestamp = Date.now();
const baseTimestamp = Date.now();
// Actually send the message
// load any sticker data, attachments, or link previews that we need to
// send along with the message and do the send to each conversation.
await Promise.all(
conversations.map(async conversation => {
timestamp += 1;
conversations.map(async (conversation, offset) => {
const timestamp = baseTimestamp + offset;
if (conversation) {
const sticker = message.get('sticker');
if (sticker) {

2
ts/window.d.ts vendored
View File

@ -166,6 +166,8 @@ declare global {
WhatIsThis: WhatIsThis;
subscribeToSystemThemeChange: (fn: () => void) => void;
registerScreenShareControllerRenderer: (
f: (
component: typeof CallingScreenSharingController,