Улучшить открытие личного чата
This commit is contained in:
parent
0f3c4a621d
commit
827d2e9c3e
@ -3,6 +3,9 @@
|
|||||||
Доработан UX личного чата на мобильных устройствах:
|
Доработан UX личного чата на мобильных устройствах:
|
||||||
- при открытой экранной клавиатуре нижний тулбар на 5 кнопок временно скрывается;
|
- при открытой экранной клавиатуре нижний тулбар на 5 кнопок временно скрывается;
|
||||||
- после отправки собственного сообщения чат автоматически прокручивается вниз так, чтобы новое сообщение было видно сразу.
|
- после отправки собственного сообщения чат автоматически прокручивается вниз так, чтобы новое сообщение было видно сразу.
|
||||||
|
- при открытии уже существующего личного чата стартовая позиция выбирается по хвосту переписки:
|
||||||
|
- если есть непрочитанные сообщения, открытие происходит на линии `Новые сообщения`;
|
||||||
|
- если непрочитанных нет, чат открывается сразу в самом низу.
|
||||||
|
|
||||||
## Что проверять
|
## Что проверять
|
||||||
|
|
||||||
@ -12,12 +15,15 @@
|
|||||||
- отправить короткое сообщение, находясь не в самом низу переписки;
|
- отправить короткое сообщение, находясь не в самом низу переписки;
|
||||||
- убедиться, что после отправки экран прокручивается вниз и новое сообщение видно сразу;
|
- убедиться, что после отправки экран прокручивается вниз и новое сообщение видно сразу;
|
||||||
- проверить то же поведение после прихода подтверждения отправки/перерисовки списка.
|
- проверить то же поведение после прихода подтверждения отправки/перерисовки списка.
|
||||||
|
- открыть существующий чат с непрочитанными сообщениями и убедиться, что видна линия `Новые сообщения` и сообщения ниже неё;
|
||||||
|
- открыть существующий чат без непрочитанных сообщений и убедиться, что открыт конец переписки, а не начало.
|
||||||
|
|
||||||
## Ожидаемый результат
|
## Ожидаемый результат
|
||||||
|
|
||||||
- клавиатура не конфликтует по высоте с нижним тулбаром;
|
- клавиатура не конфликтует по высоте с нижним тулбаром;
|
||||||
- при наборе доступно больше вертикального места;
|
- при наборе доступно больше вертикального места;
|
||||||
- собственное только что отправленное сообщение сразу попадает в видимую область.
|
- собственное только что отправленное сообщение сразу попадает в видимую область.
|
||||||
|
- при открытии чата пользователь сразу попадает в актуальную часть переписки.
|
||||||
|
|
||||||
## Статус
|
## Статус
|
||||||
|
|
||||||
|
|||||||
@ -1,2 +1,2 @@
|
|||||||
client.version=1.2.264
|
client.version=1.2.265
|
||||||
server.version=1.2.248
|
server.version=1.2.248
|
||||||
|
|||||||
@ -281,7 +281,27 @@ function scrollToLatestMessage(list) {
|
|||||||
window.setTimeout(apply, 260);
|
window.setTimeout(apply, 260);
|
||||||
}
|
}
|
||||||
|
|
||||||
function renderLog(list, chatId, { onOpenActions } = {}) {
|
function scrollToUnreadSeparator(list) {
|
||||||
|
if (!list) return false;
|
||||||
|
const separator = list.querySelector('.chat-unread-separator');
|
||||||
|
if (!separator) return false;
|
||||||
|
const scrollContainer = list.closest('.screen-content') || list.parentElement || list;
|
||||||
|
const apply = () => {
|
||||||
|
if (separator?.scrollIntoView) {
|
||||||
|
separator.scrollIntoView({ block: 'start', inline: 'nearest' });
|
||||||
|
}
|
||||||
|
const bottomSlack = 72;
|
||||||
|
scrollContainer.scrollTop = Math.max(0, scrollContainer.scrollTop - bottomSlack);
|
||||||
|
};
|
||||||
|
apply();
|
||||||
|
window.requestAnimationFrame(apply);
|
||||||
|
window.requestAnimationFrame(() => window.requestAnimationFrame(apply));
|
||||||
|
window.setTimeout(apply, 60);
|
||||||
|
window.setTimeout(apply, 160);
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
function renderLog(list, chatId, { onOpenActions, markAsRead = true, scrollMode = 'latest' } = {}) {
|
||||||
list.innerHTML = '';
|
list.innerHTML = '';
|
||||||
const messages = getChatMessages(chatId);
|
const messages = getChatMessages(chatId);
|
||||||
let unreadSeparatorInserted = false;
|
let unreadSeparatorInserted = false;
|
||||||
@ -336,8 +356,14 @@ function renderLog(list, chatId, { onOpenActions } = {}) {
|
|||||||
});
|
});
|
||||||
list.append(bubble);
|
list.append(bubble);
|
||||||
});
|
});
|
||||||
scrollToLatestMessage(list);
|
if (scrollMode === 'unread' && !scrollToUnreadSeparator(list)) {
|
||||||
markChatRead(chatId);
|
scrollToLatestMessage(list);
|
||||||
|
} else if (scrollMode === 'latest') {
|
||||||
|
scrollToLatestMessage(list);
|
||||||
|
}
|
||||||
|
if (markAsRead) {
|
||||||
|
markChatRead(chatId);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
function preserveComposerSelection(input, callback) {
|
function preserveComposerSelection(input, callback) {
|
||||||
@ -383,6 +409,7 @@ export function render({ navigate, route }) {
|
|||||||
const isSpeechToTextReady = isSpeechToTextConfigured(state.entrySettings);
|
const isSpeechToTextReady = isSpeechToTextConfigured(state.entrySettings);
|
||||||
const isTextToSpeechReady = isTextToSpeechConfigured(state.entrySettings);
|
const isTextToSpeechReady = isTextToSpeechConfigured(state.entrySettings);
|
||||||
const isKnownContact = (state.contacts || []).some((x) => String(x || '').toLowerCase() === String(chatId || '').toLowerCase());
|
const isKnownContact = (state.contacts || []).some((x) => String(x || '').toLowerCase() === String(chatId || '').toLowerCase());
|
||||||
|
const hasUnreadIncoming = getChatMessages(chatId).some((msg) => msg?.from === 'in' && msg?.unread);
|
||||||
|
|
||||||
const handleReadAloud = async (msg) => {
|
const handleReadAloud = async (msg) => {
|
||||||
if (!isTextToSpeechConfigured(state.entrySettings)) {
|
if (!isTextToSpeechConfigured(state.entrySettings)) {
|
||||||
@ -759,7 +786,7 @@ export function render({ navigate, route }) {
|
|||||||
const updatedChatId = normalizeDmChatId(event?.detail?.chatId);
|
const updatedChatId = normalizeDmChatId(event?.detail?.chatId);
|
||||||
if (updatedChatId !== chatId) return;
|
if (updatedChatId !== chatId) return;
|
||||||
preserveComposerSelection(input, () => {
|
preserveComposerSelection(input, () => {
|
||||||
renderLog(log, chatId, { onOpenActions: handleOpenActions });
|
renderLog(log, chatId, { onOpenActions: handleOpenActions, scrollMode: 'latest' });
|
||||||
});
|
});
|
||||||
window.requestAnimationFrame(() => scrollToLatestMessage(log));
|
window.requestAnimationFrame(() => scrollToLatestMessage(log));
|
||||||
void sendReadReceiptsForVisible(chatId);
|
void sendReadReceiptsForVisible(chatId);
|
||||||
@ -771,9 +798,19 @@ export function render({ navigate, route }) {
|
|||||||
|
|
||||||
wrap.append(log, form);
|
wrap.append(log, form);
|
||||||
screen.append(wrap);
|
screen.append(wrap);
|
||||||
renderLog(log, chatId, { onOpenActions: handleOpenActions });
|
renderLog(log, chatId, {
|
||||||
window.requestAnimationFrame(() => scrollToLatestMessage(log));
|
onOpenActions: handleOpenActions,
|
||||||
window.setTimeout(() => scrollToLatestMessage(log), 180);
|
markAsRead: false,
|
||||||
|
scrollMode: hasUnreadIncoming ? 'unread' : 'latest',
|
||||||
|
});
|
||||||
|
if (hasUnreadIncoming) {
|
||||||
|
window.requestAnimationFrame(() => scrollToUnreadSeparator(log));
|
||||||
|
window.setTimeout(() => scrollToUnreadSeparator(log), 180);
|
||||||
|
} else {
|
||||||
|
window.requestAnimationFrame(() => scrollToLatestMessage(log));
|
||||||
|
window.setTimeout(() => scrollToLatestMessage(log), 180);
|
||||||
|
}
|
||||||
|
window.setTimeout(() => markChatRead(chatId), 220);
|
||||||
void sendReadReceiptsForVisible(chatId);
|
void sendReadReceiptsForVisible(chatId);
|
||||||
screen.cleanup = () => {
|
screen.cleanup = () => {
|
||||||
setChatKeyboardOpen(false);
|
setChatKeyboardOpen(false);
|
||||||
|
|||||||
Loading…
Reference in New Issue
Block a user