Было: word snap при drag handle → нельзя выделить часть слова
Стало: посимвольно при drag (word snap только при первом long press)
Magnifier: показывается на позиции handle (текущий символ),
а не на позиции пальца. По Y — центр строки текста.
Haptic: TEXT_HANDLE_MOVE на каждый символ (не на каждое слово).
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Magnifier:
- Конвертация overlay-local → view-local координаты для Magnifier.show()
- Builder: 240×64px, cornerRadius 12, elevation 4, offset -80 (над текстом)
Haptic:
- TEXT_HANDLE_MOVE при каждом изменении selectionStart/selectionEnd
- Как в Telegram: вибрация при перемещении handle по словам
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Root cause: overlay Canvas рисует в локальных координатах, но LayoutInfo
возвращает позицию в window coordinates. Разница = position status bar,
toolbar, и parent padding → highlight смещался вниз.
Фикс:
- onGloballyPositioned на overlay Box → знаем overlayWindowX/Y
- Canvas: offsetX/Y = info.windowX - overlayWindowX (window→local)
- getCharOffsetFromCoords: overlay-local → text-local через ту же delta
- Handle positions теперь в overlay-local координатах → drag работает
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
- setMagnifierView(view) в ChatDetailScreen через LaunchedEffect
- 9 unit тестов: initial state, clear, getSelectedText, boundary checks
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
- TextSelectionHelper инстанс в ChatDetailScreen
- TextSelectionOverlay поверх LazyColumn
- Clear selection при scroll и при message selection mode
- onTextLongPress + onViewCreated проброшены через MessageBubble к AppleEmojiText
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Root cause 1: startVoiceRecording() проверял только isVoiceRecording,
но isVoiceRecording=true ставился через 192ms в scope.launch. При быстром
двойном тапе два MediaRecorder создавались, первый терялся (утечка).
Фикс: добавлен guard на isVoiceRecordTransitioning и voiceRecorder!=null.
Root cause 2: isVoiceRecordTransitioning=true ставился перед scope.launch,
но если launch крашился или composable disposed, transitioning навсегда
оставался true — gesture guard блокировал все записи до перезапуска.
Фикс: try/catch в launch + reset в DisposableEffect.
Root cause 3: DisposableEffect проверял только isVoiceRecording, но не
voiceRecorder!=null — если recorder создан но isVoiceRecording ещё false,
recorder не освобождался при dispose.
Фикс: проверка voiceRecorder!=null в dispose.
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Telegram при LOCKED: таймер и dot СКРЫТЫ, вместо них:
- [Delete 44dp] — красная иконка удаления слева
- [Waveform] — заполняет оставшееся место
- Lock→Pause кнопка наверху (отдельный overlay)
- Circle = Send (без blob)
При RECORDING (без изменений):
- [dot][timer] [◀ Slide to cancel] [Circle+Blob]
Реализация: AnimatedContent crossfade между двумя полностью
разными panel layouts. RecordLockedControls больше не используется
в панели — delete в самой панели, pause в LockIcon overlay.
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Telegram: при sendButtonVisible=true gesture handler возвращает false,
полностью блокируя горизонтальный свайп. Slide-to-cancel исчезает,
вместо него кнопка Cancel.
Изменения:
- Gesture handler: только RECORDING обрабатывает slide (было RECORDING||LOCKED)
- slideDx/slideDy не обновляются при LOCKED/PAUSED
- При lock: slideDx=0, slideDy=0 — сбрасываем горизонтальное смещение
- AnimatedContent уже переключает SlideToCancel→waveform при LOCKED
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Telegram-exact SlideTextView:
- Chevron arrow: Canvas-drawn path 4×5dp, stroke 1.6dp, round caps (не текст ◀)
- Пульсация: ±6dp ТОЛЬКО при slideProgress > 0.8, скорость 12dp/s (3dp/250ms)
- Frame-based animation через LaunchedEffect (не infiniteTransition)
- Entry: slide in from right (translationX 20dp→0, 200ms) + fade in
- Текст: "Slide to cancel" 15sp normal weight (было 13sp medium)
- Цвет: #8E8E93 (Telegram key_chat_recordTime)
- Translation: finger × 0.3 damping + pulse offset × slideProgress
- Alpha: slideProgress × entryAlpha (плавно появляется и исчезает при свайпе)
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Telegram-exact lock icon:
- Body: 16×16dp прямоугольник, radius 3dp (заливка)
- Shackle: 8×8dp полукруг (stroke 1.7dp) + две ножки
- Левая ножка: idle "breathing" animation (1.2s cycle)
- Левая ножка: удлиняется при snap lock
- Keyhole: 4dp точка в центре body (цвет фона)
- Pause transform: body раздваивается с gap 1.66dp (Telegram exact)
- Pill background: 36×50dp с тенью
- Lock виден сразу при начале записи (не ждёт свайпа)
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
- Panel bar (timer + slide-to-cancel/waveform) как Layer 1
- Mic/Send circle (48dp) как overlay Layer 2 поверх панели
- LockIcon как Layer 3 над кругом через graphicsLayer (без clip)
- Убран padding(end=94dp), заменён на padding(end=44dp)
- Убран offset(x=8dp) который толкал круг за экран
- Controls увеличены 28dp→36dp для лучшей тач-зоны
- Blob scale 2.05→1.8 пропорционально новому 48dp размеру
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
preview содержит формат UUID::blurhash, а парсился как raw Base64 → серый фон.
Теперь сначала пробует BlurHash.decode, fallback на Base64.
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Desktop теперь шифрует аттачменты в группах hex-версией ключа.
Android пробует raw key, при неудаче — hex key. Фикс в 3 местах:
processDownloadedImage, downloadAndDecryptImage, loadBitmapForViewerImage.
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Двухэтапное удаление: pendingDeleteIds → AnimatedVisibility(shrinkVertically + fadeOut) → remove.
Остальные сообщения плавно сдвигаются на место удалённого.
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
decryptStoredMessageText возвращал зашифрованный текст при неудачной расшифровке.
Теперь возвращает пустую строку. Дополнительно: фильтр base64-like строк в caption viewer.
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
SharedPhotoItem не передавал transportTag/transportServer в ViewableImage —
фото из медиа-галереи профиля не загружались в полноэкранном режиме.
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Оставлена защита только в updateMessageStatusAndAttachmentsInDb (для фото race condition).
findMessageById обёрнут в отдельный try/catch чтобы ошибка в нём не блокировала основной update.
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
updateMessageStatusInDb и updateMessageStatusAndAttachmentsInDb теперь проверяют
текущий delivered в БД и никогда не понижают (DELIVERED→WAITING race condition).
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
- Forward пузырь подстраивается под размер контента (как Telegram)
- Длинные имена обрезаются "Forwarded from Alex M..." вместо растяжения
- Исправлена отправка forward — consumeForwardMessagesForChat при открытии чата
- Floating date header показывается только при активном скролле (убраны дубли дат)
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>