From 76e4a6cba07125eb774c7e3318031ad9841456daf2180ce83f0e8bd67a7a517b Mon Sep 17 00:00:00 2001 From: AidarKC Date: Wed, 13 May 2026 02:42:57 +0300 Subject: [PATCH] =?UTF-8?q?UI/Channels:=20=D0=B2=D0=BA=D0=BB=D0=B0=D0=B4?= =?UTF-8?q?=D0=BA=D0=B8=20=D0=BF=D0=BE=20=D1=82=D0=B0=D0=BF=D1=83=20+=20Cr?= =?UTF-8?q?eateChannel=20fallback=20=D0=B4=D0=BB=D1=8F=20legacy=20=D1=84?= =?UTF-8?q?=D0=BE=D1=80=D0=BC=D0=B0=D1=82=D0=B0?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- ...-tabs-and-legacy-createchannel-fallback.md | 26 ++++++ VERSION.properties | 4 +- shine-UI/js/pages/channels-list.js | 26 +++++- shine-UI/js/services/auth-service.js | 87 +++++++++++++++---- 4 files changed, 123 insertions(+), 20 deletions(-) create mode 100644 Dev_Docs/Pending_Features/2026-05-13_0248_channels-tabs-and-legacy-createchannel-fallback.md diff --git a/Dev_Docs/Pending_Features/2026-05-13_0248_channels-tabs-and-legacy-createchannel-fallback.md b/Dev_Docs/Pending_Features/2026-05-13_0248_channels-tabs-and-legacy-createchannel-fallback.md new file mode 100644 index 0000000..43a1d87 --- /dev/null +++ b/Dev_Docs/Pending_Features/2026-05-13_0248_channels-tabs-and-legacy-createchannel-fallback.md @@ -0,0 +1,26 @@ +# Каналы: явные вкладки + fallback CreateChannel для legacy-сервера + +Статус: `pending` + +## Что сделано + +- На экране каналов добавлены явные вкладки: + - `Каналы` + - `Чаты` + - `Мои` +- Переключение теперь работает по обычному тапу, без необходимости long-press на кнопке toolbar. +- В `addBlockCreateChannel` добавлен fallback: + - сначала отправляется текущий формат CreateChannel (с description/type/version), + - если сервер возвращает `bad_block_format`, выполняется повтор с legacy-форматом тела (без description/type/version) для совместимости со старым сервером. + +## Как проверять + +1. Открыть экран каналов и проверить переключение всех трёх вкладок по тапу. +2. Нажимать на строки каналов и убедиться, что переход в канал работает. +3. Создать новый канал и убедиться, что при старом сервере создание не падает с `Некорректный формат блока`. + +## Ожидаемый результат + +- Вкладки `Каналы/Чаты/Мои` переключаются стабильно. +- Каналы открываются по тапу. +- Создание канала устойчиво к legacy-формату сервера. diff --git a/VERSION.properties b/VERSION.properties index db4c44e..bf24be9 100644 --- a/VERSION.properties +++ b/VERSION.properties @@ -1,2 +1,2 @@ -client.version=1.2.47 -server.version=1.2.41 +client.version=1.2.48 +server.version=1.2.42 diff --git a/shine-UI/js/pages/channels-list.js b/shine-UI/js/pages/channels-list.js index 6afd491..b5e4391 100644 --- a/shine-UI/js/pages/channels-list.js +++ b/shine-UI/js/pages/channels-list.js @@ -1089,6 +1089,26 @@ export function render({ navigate, route }) { const contentEl = document.createElement('div'); contentEl.className = 'channels-list-content'; + const tabsEl = document.createElement('div'); + tabsEl.className = 'channels-tabs'; + const tabLabels = { + feed: 'Каналы', + dialogs: 'Чаты', + my: 'Мои', + }; + TAB_ORDER.forEach((tabKey) => { + const tabBtn = document.createElement('button'); + tabBtn.type = 'button'; + tabBtn.className = `channels-tab-btn${listState.activeTab === tabKey ? ' is-active' : ''}`; + tabBtn.textContent = tabLabels[tabKey] || tabKey; + tabBtn.addEventListener('click', () => { + if (listState.activeTab === tabKey) return; + listState.activeTab = tabKey; + rerenderList(); + }); + tabsEl.append(tabBtn); + }); + const bottomCta = document.createElement('button'); bottomCta.type = 'button'; @@ -1123,6 +1143,10 @@ export function render({ navigate, route }) { onReload: reloadFeed, isTabEmpty, }); + tabsEl.querySelectorAll('.channels-tab-btn').forEach((btn, idx) => { + const key = TAB_ORDER[idx]; + btn.classList.toggle('is-active', key === listState.activeTab); + }); }; let touchStartX = 0; @@ -1146,7 +1170,7 @@ export function render({ navigate, route }) { rerenderList(); }, { passive: true }); - screen.append(contentEl, bottomCta); + screen.append(tabsEl, contentEl, bottomCta); if (createSuccessFlash) { showToast(createSuccessFlash); diff --git a/shine-UI/js/services/auth-service.js b/shine-UI/js/services/auth-service.js index 38d79fe..b039237 100644 --- a/shine-UI/js/services/auth-service.js +++ b/shine-UI/js/services/auth-service.js @@ -456,6 +456,31 @@ function makeCreateChannelBodyBytes({ ); } +function makeCreateChannelBodyBytesLegacy({ + lineCode, + prevLineNumber, + prevLineHashHex, + thisLineNumber, + channelName, +}) { + const check = validateChannelDisplayName(channelName); + if (!check.ok) throw new Error(channelNameErrorText(check.code)); + const cleanName = check.normalized; + const nameBytes = utf8Bytes(cleanName); + if (nameBytes.length < 1 || nameBytes.length > 255) { + throw new Error('Channel name must be 1..255 bytes'); + } + + return concatBytes( + int32Bytes(lineCode), + int32Bytes(prevLineNumber), + hexToBytes(normalizeHex32(prevLineHashHex)), + int32Bytes(thisLineNumber), + int8Byte(nameBytes.length), + nameBytes, + ); +} + function makeTextPostBodyBytes({ lineCode, prevLineNumber, prevLineHashHex, thisLineNumber, text }) { const message = String(text || '').trim(); if (!message) throw new Error('Message text is required'); @@ -1075,23 +1100,51 @@ export class AuthService { thisLineNumber = createdChannels.length + 1; } - const payload = await this.addBlockSigned({ - login: cleanLogin, - storagePwd, - msgType: MSG_TYPE_TECH, - msgSubType: MSG_SUBTYPE_TECH_CREATE_CHANNEL, - msgVersion: CREATE_CHANNEL_BODY_VERSION, - bodyBytes: makeCreateChannelBodyBytes({ - lineCode: 0, - prevLineNumber, - prevLineHashHex, - thisLineNumber, - channelName: cleanChannelName, - channelDescription: cleanChannelDescription, - channelType: typeCode, - channelTypeVersion: typeVersion, - }), - }); + let payload; + try { + payload = await this.addBlockSigned({ + login: cleanLogin, + storagePwd, + msgType: MSG_TYPE_TECH, + msgSubType: MSG_SUBTYPE_TECH_CREATE_CHANNEL, + msgVersion: CREATE_CHANNEL_BODY_VERSION, + bodyBytes: makeCreateChannelBodyBytes({ + lineCode: 0, + prevLineNumber, + prevLineHashHex, + thisLineNumber, + channelName: cleanChannelName, + channelDescription: cleanChannelDescription, + channelType: typeCode, + channelTypeVersion: typeVersion, + }), + }); + } catch (error) { + const rawCode = String(error?.code || '').toUpperCase(); + const rawText = String(error?.message || '').toLowerCase(); + const isLegacyFormatMismatch = ( + rawCode === 'BAD_BLOCK_FORMAT' || + rawText.includes('bad_block_format') || + rawText.includes('некорректный формат блока') + ); + if (!isLegacyFormatMismatch) throw error; + + // Совместимость со старыми серверами, где CreateChannel body без description/type. + payload = await this.addBlockSigned({ + login: cleanLogin, + storagePwd, + msgType: MSG_TYPE_TECH, + msgSubType: MSG_SUBTYPE_TECH_CREATE_CHANNEL, + msgVersion: CREATE_CHANNEL_BODY_VERSION, + bodyBytes: makeCreateChannelBodyBytesLegacy({ + lineCode: 0, + prevLineNumber, + prevLineHashHex, + thisLineNumber, + channelName: cleanChannelName, + }), + }); + } const selector = { ownerBlockchainName: blockchainName,