Compare commits
2 Commits
e7d0a5a9ee
...
9d726f4549
| Author | SHA1 | Date | |
|---|---|---|---|
| 9d726f4549 | |||
| 01198a4d14 |
@@ -4,6 +4,7 @@
|
|||||||
|
|
||||||
- 2026-07-13 | (без бампа — версия/релиз за лидом) | **Контакты — ядро правки/удаления (P1, contacts.diff, шаг 1/2).** `CardDavClient`: `updateContact` (PUT If-Match по ETag, round-trip неизвестных свойств vCard — PHOTO/UID/X-* сохраняются) и `deleteContact` (DELETE If-Match, 404=уже удалён, 412=конфликт ETag). `DavContact` +`href`/`etag`/`raw` (заполняются чтением книги; сигнатуры list/create НЕ менялись). `parseContacts` теперь ловит href/getetag на каждый `<response>`; общий `davContactFromVCard`. Чистый движок `VCardEditor.applyEdits` (замена управляемых FN/N/EMAIL/TEL/ORG/TITLE/ADR/URL/BDAY/NOTE, фолдинг 75, CRLF, экранирование) + модели `VCardFields`/`VCardTypedValue`. **10 юнит-тестов** (`VCardEditorTest`) зелёные: round-trip неизвестных полей, FN с запятыми, PHOTO base64, folded ADR, CRLF-фолдинг. Гейт `:app:compileReleaseKotlin` зелёный. Шаг 2/2 (Room href/etag/raw + миграция, форма правки в карточке, delete в UI) — следующим коммитом. | контракты: core `CardDavClient` +2 метода, `DavContact` +3 опц. поля (обратно совместимо, list/create не тронуты); одобрено лидом mail/072 | низкий: только добавление методов + поля с дефолтами, синк не задет; на устройстве/сервере ещё не гонялось (юниты + компил-гейт)
|
- 2026-07-13 | (без бампа — версия/релиз за лидом) | **Контакты — ядро правки/удаления (P1, contacts.diff, шаг 1/2).** `CardDavClient`: `updateContact` (PUT If-Match по ETag, round-trip неизвестных свойств vCard — PHOTO/UID/X-* сохраняются) и `deleteContact` (DELETE If-Match, 404=уже удалён, 412=конфликт ETag). `DavContact` +`href`/`etag`/`raw` (заполняются чтением книги; сигнатуры list/create НЕ менялись). `parseContacts` теперь ловит href/getetag на каждый `<response>`; общий `davContactFromVCard`. Чистый движок `VCardEditor.applyEdits` (замена управляемых FN/N/EMAIL/TEL/ORG/TITLE/ADR/URL/BDAY/NOTE, фолдинг 75, CRLF, экранирование) + модели `VCardFields`/`VCardTypedValue`. **10 юнит-тестов** (`VCardEditorTest`) зелёные: round-trip неизвестных полей, FN с запятыми, PHOTO base64, folded ADR, CRLF-фолдинг. Гейт `:app:compileReleaseKotlin` зелёный. Шаг 2/2 (Room href/etag/raw + миграция, форма правки в карточке, delete в UI) — следующим коммитом. | контракты: core `CardDavClient` +2 метода, `DavContact` +3 опц. поля (обратно совместимо, list/create не тронуты); одобрено лидом mail/072 | низкий: только добавление методов + поля с дефолтами, синк не задет; на устройстве/сервере ещё не гонялось (юниты + компил-гейт)
|
||||||
- 2026-07-13 | (без бампа — версия/релиз за лидом) | **Задачи — умные коллекции в UI (P1, tasks.diff).** Умные коллекции (Важное/Сегодня/Неделя/Все/Текущие/Завершённые) выведены в `TasksListNavView` над реальными списками: агрегат задач всех списков (`loadAllTasks`), счётчики-бейджи, тап открывает отфильтрованный вид; создание в коллекции идёт в список по умолчанию. 3-state видимость (Скрыта/Видима/Авто) в шите настроек с персистом в SharedPreferences (`TasksPrefsStore`, VM через `Factory(context)`). Движок `TasksSmartLists` расширен (`SmartListMode`/`visibleKeys`/`counts`), 25 юнит-тестов зелёные. Ff_app 16.7→24.6%, Fd_app 41.5→52.7% (`tasks.diff.md`). | контракты не менялись | низкий: UI изолирован в модуле, на устройстве не гонялось
|
- 2026-07-13 | (без бампа — версия/релиз за лидом) | **Задачи — умные коллекции в UI (P1, tasks.diff).** Умные коллекции (Важное/Сегодня/Неделя/Все/Текущие/Завершённые) выведены в `TasksListNavView` над реальными списками: агрегат задач всех списков (`loadAllTasks`), счётчики-бейджи, тап открывает отфильтрованный вид; создание в коллекции идёт в список по умолчанию. 3-state видимость (Скрыта/Видима/Авто) в шите настроек с персистом в SharedPreferences (`TasksPrefsStore`, VM через `Factory(context)`). Движок `TasksSmartLists` расширен (`SmartListMode`/`visibleKeys`/`counts`), 25 юнит-тестов зелёные. Ff_app 16.7→24.6%, Fd_app 41.5→52.7% (`tasks.diff.md`). | контракты не менялись | низкий: UI изолирован в модуле, на устройстве не гонялось
|
||||||
|
- 2026-07-13 | (без бампа — версия/релиз за лидом) | **Конференции — меню сообщения: «Переслать…» в любую комнату (P1).** Пункт «Переслать…» открывает лист выбора комнаты (`ForwardTargetSheet` — список бесед кроме текущей) → `TalkViewModel.forwardToRoom` шлёт текст в выбранную (OCS `/chat` v1). Диалог удаления вынесен в `ConfirmDeleteMessageDialog` (держит новый `MessageActionOverlays` <120 строк). Завершает историю пересылки (в «Личные заметки» + в произвольную комнату). Baseline пере-привязан (сигнатура ChatContent от нового параметра). | контракты не менялись (OCS /chat v1) | низкий: UI-действие в модуле, на устройстве не гонялось
|
||||||
- 2026-07-13 | (без бампа — версия/релиз за лидом) | **Конференции — меню сообщения: «Ответить лично» + рефактор (P1).** Пункт «Ответить лично» в меню сообщения (для чужих сообщений): `TalkViewModel.replyPrivately` открывает/создаёт 1:1 с автором через существующий `createOneToOneRoom` (OCS spreed v4, сервер идемпотентен) и `openRoom`. Заодно рефактор: блок оверлеев сообщения (лист действий + диалоги правки/удаления) вынесен из `ChatContent` в отдельный `MessageActionOverlays` — `ChatContent` LongMethod 267→174, cyclomatic 29→<25 (перестал плодить baseline-churn на каждый пункт меню). Baseline пере-привязан (сигнатура ChatContent сдвинулась от нового параметра); новый код чист (компиляция+detekt зелёные). | контракты не менялись (OCS spreed v4/v1) | низкий: изолированное UI-действие + чистый рефактор, на устройстве не гонялось
|
- 2026-07-13 | (без бампа — версия/релиз за лидом) | **Конференции — меню сообщения: «Ответить лично» + рефактор (P1).** Пункт «Ответить лично» в меню сообщения (для чужих сообщений): `TalkViewModel.replyPrivately` открывает/создаёт 1:1 с автором через существующий `createOneToOneRoom` (OCS spreed v4, сервер идемпотентен) и `openRoom`. Заодно рефактор: блок оверлеев сообщения (лист действий + диалоги правки/удаления) вынесен из `ChatContent` в отдельный `MessageActionOverlays` — `ChatContent` LongMethod 267→174, cyclomatic 29→<25 (перестал плодить baseline-churn на каждый пункт меню). Baseline пере-привязан (сигнатура ChatContent сдвинулась от нового параметра); новый код чист (компиляция+detekt зелёные). | контракты не менялись (OCS spreed v4/v1) | низкий: изолированное UI-действие + чистый рефактор, на устройстве не гонялось
|
||||||
- 2026-07-13 | (без бампа — версия/релиз за лидом) | **Конференции — меню сообщения: напоминание (P1).** Пункт «Напоминание» в `TalkMessageActionSheet` с пресетами «Сегодня» (18:00) / «Завтра» (08:00) / «Выходные» (сб 08:00) / «Неделя» (пн 08:00): `TalkRepository.setReminder` (OCS `POST /chat/{token}/{messageId}/reminder`, timestamp сек) + `TalkViewModel.setMessageReminder`/`reminderTimestamp` + enum `ReminderPreset`. Закрывает пункт «Напоминание» дыр меню сообщения (talk.diff.md). Baseline `feature/talk` пере-привязан (сигнатура ChatContent + импорт сдвинули существующие записи; новый код чист, компиляция+detekt зелёные). | контракты не менялись (OCS spreed v1) | низкий: изолированное UI-действие, на устройстве не гонялось
|
- 2026-07-13 | (без бампа — версия/релиз за лидом) | **Конференции — меню сообщения: напоминание (P1).** Пункт «Напоминание» в `TalkMessageActionSheet` с пресетами «Сегодня» (18:00) / «Завтра» (08:00) / «Выходные» (сб 08:00) / «Неделя» (пн 08:00): `TalkRepository.setReminder` (OCS `POST /chat/{token}/{messageId}/reminder`, timestamp сек) + `TalkViewModel.setMessageReminder`/`reminderTimestamp` + enum `ReminderPreset`. Закрывает пункт «Напоминание» дыр меню сообщения (talk.diff.md). Baseline `feature/talk` пере-привязан (сигнатура ChatContent + импорт сдвинули существующие записи; новый код чист, компиляция+detekt зелёные). | контракты не менялись (OCS spreed v1) | низкий: изолированное UI-действие, на устройстве не гонялось
|
||||||
- 2026-07-13 | (без бампа — версия/релиз за лидом) | **Конференции — меню сообщения, P1 итерации 1:1 (talk.diff.md).** В `TalkMessageActionSheet` добавлены пункты «Копировать» (текст → буфер), «Копировать ссылку» (`TalkUrls.messageLink` = `/call/<token>#message_<id>` → буфер) и «Переслать в «Личные заметки»» (`TalkViewModel.forwardToNoteToSelf` — шлёт текст в комнату note-to-self, spreed type 6, через существующий OCS `/chat` v1). Закрывает часть дыр меню сообщения из `docs/app-map/talk.diff.md` (Копировать/Копировать ссылку/частично Переслать). Обновлён `feature/talk/detekt-baseline.xml` — модуль был detekt-красным ДО моей правки (устаревший baseline от чужих коммитов v0.5.133+); мои новые строки чисты (компиляция+detekt зелёные), рефреш лишь пере-привязал существующие записи к сдвинутым сигнатурам. | контракты не менялись (та же OCS /chat v1) | низкий: изолированные UI-действия в своём модуле, на устройстве не гонялось
|
- 2026-07-13 | (без бампа — версия/релиз за лидом) | **Конференции — меню сообщения, P1 итерации 1:1 (talk.diff.md).** В `TalkMessageActionSheet` добавлены пункты «Копировать» (текст → буфер), «Копировать ссылку» (`TalkUrls.messageLink` = `/call/<token>#message_<id>` → буфер) и «Переслать в «Личные заметки»» (`TalkViewModel.forwardToNoteToSelf` — шлёт текст в комнату note-to-self, spreed type 6, через существующий OCS `/chat` v1). Закрывает часть дыр меню сообщения из `docs/app-map/talk.diff.md` (Копировать/Копировать ссылку/частично Переслать). Обновлён `feature/talk/detekt-baseline.xml` — модуль был detekt-красным ДО моей правки (устаревший baseline от чужих коммитов v0.5.133+); мои новые строки чисты (компиляция+detekt зелёные), рефреш лишь пере-привязал существующие записи к сдвинутым сигнатурам. | контракты не менялись (та же OCS /chat v1) | низкий: изолированные UI-действия в своём модуле, на устройстве не гонялось
|
||||||
|
|||||||
@@ -100,6 +100,7 @@ class DeckRepository {
|
|||||||
labels = parseLabels(card.optJSONArray("labels")),
|
labels = parseLabels(card.optJSONArray("labels")),
|
||||||
assignees = parseAssignees(card.optJSONArray("assignedUsers")),
|
assignees = parseAssignees(card.optJSONArray("assignedUsers")),
|
||||||
assigneeUids = parseAssigneeUids(card.optJSONArray("assignedUsers")),
|
assigneeUids = parseAssigneeUids(card.optJSONArray("assignedUsers")),
|
||||||
|
commentsCount = card.optInt("commentsCount", 0),
|
||||||
)
|
)
|
||||||
|
|
||||||
private fun parseLabels(array: JSONArray?): List<DeckLabel> {
|
private fun parseLabels(array: JSONArray?): List<DeckLabel> {
|
||||||
@@ -287,6 +288,7 @@ data class DeckCard(
|
|||||||
val labels: List<DeckLabel> = emptyList(),
|
val labels: List<DeckLabel> = emptyList(),
|
||||||
val assignees: List<String> = emptyList(),
|
val assignees: List<String> = emptyList(),
|
||||||
val assigneeUids: List<String> = emptyList(),
|
val assigneeUids: List<String> = emptyList(),
|
||||||
|
val commentsCount: Int = 0,
|
||||||
)
|
)
|
||||||
|
|
||||||
data class DeckBoardDetail(
|
data class DeckBoardDetail(
|
||||||
|
|||||||
@@ -292,7 +292,7 @@ private fun DeckCardRow(card: DeckCard, onClick: () -> Unit, onToggleDone: () ->
|
|||||||
)
|
)
|
||||||
}
|
}
|
||||||
val due = card.duedate?.let { formatDeckDue(it) }
|
val due = card.duedate?.let { formatDeckDue(it) }
|
||||||
if (due != null || card.assignees.isNotEmpty()) {
|
if (due != null || card.assignees.isNotEmpty() || card.commentsCount > 0) {
|
||||||
Row(
|
Row(
|
||||||
verticalAlignment = Alignment.CenterVertically,
|
verticalAlignment = Alignment.CenterVertically,
|
||||||
horizontalArrangement = Arrangement.spacedBy(8.dp),
|
horizontalArrangement = Arrangement.spacedBy(8.dp),
|
||||||
@@ -301,6 +301,10 @@ private fun DeckCardRow(card: DeckCard, onClick: () -> Unit, onToggleDone: () ->
|
|||||||
if (due != null) {
|
if (due != null) {
|
||||||
Text("🗓 $due", style = MaterialTheme.typography.labelMedium, color = F7Colors.TextSecondary)
|
Text("🗓 $due", style = MaterialTheme.typography.labelMedium, color = F7Colors.TextSecondary)
|
||||||
}
|
}
|
||||||
|
if (card.commentsCount > 0) {
|
||||||
|
val comments = "💬 ${card.commentsCount}"
|
||||||
|
Text(comments, style = MaterialTheme.typography.labelMedium, color = F7Colors.TextSecondary)
|
||||||
|
}
|
||||||
if (card.assignees.isNotEmpty()) {
|
if (card.assignees.isNotEmpty()) {
|
||||||
Text("👤 ${card.assignees.joinToString(", ")}", style = MaterialTheme.typography.labelMedium, color = F7Colors.TextSecondary, maxLines = 1, overflow = TextOverflow.Ellipsis)
|
Text("👤 ${card.assignees.joinToString(", ")}", style = MaterialTheme.typography.labelMedium, color = F7Colors.TextSecondary, maxLines = 1, overflow = TextOverflow.Ellipsis)
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -15,7 +15,7 @@
|
|||||||
<ID>LongMethod:TalkComponents.kt$@Composable fun TalkRoomRow( room: TalkRoom, session: AuthSession, modifier: Modifier = Modifier, onClick: () -> Unit, onToggleFavorite: () -> Unit = {}, onMarkUnread: () -> Unit = {}, )</ID>
|
<ID>LongMethod:TalkComponents.kt$@Composable fun TalkRoomRow( room: TalkRoom, session: AuthSession, modifier: Modifier = Modifier, onClick: () -> Unit, onToggleFavorite: () -> Unit = {}, onMarkUnread: () -> Unit = {}, )</ID>
|
||||||
<ID>LongMethod:TalkComponents.kt$@OptIn(ExperimentalMaterial3Api::class, ExperimentalLayoutApi::class) @Composable fun TalkCreateRoomSheet( visible: Boolean, session: AuthSession, mode: CreateRoomMode, roomName: String, userSearchQuery: String, userSearchResults: List<TalkUserCandidate>, selectedUsers: List<TalkUserCandidate>, userSearchLoading: Boolean, creating: Boolean, onDismiss: () -> Unit, onModeChange: (CreateRoomMode) -> Unit, onRoomNameChange: (String) -> Unit, onUserSearchChange: (String) -> Unit, onUserToggle: (TalkUserCandidate) -> Unit, onCreate: () -> Unit, )</ID>
|
<ID>LongMethod:TalkComponents.kt$@OptIn(ExperimentalMaterial3Api::class, ExperimentalLayoutApi::class) @Composable fun TalkCreateRoomSheet( visible: Boolean, session: AuthSession, mode: CreateRoomMode, roomName: String, userSearchQuery: String, userSearchResults: List<TalkUserCandidate>, selectedUsers: List<TalkUserCandidate>, userSearchLoading: Boolean, creating: Boolean, onDismiss: () -> Unit, onModeChange: (CreateRoomMode) -> Unit, onRoomNameChange: (String) -> Unit, onUserSearchChange: (String) -> Unit, onUserToggle: (TalkUserCandidate) -> Unit, onCreate: () -> Unit, )</ID>
|
||||||
<ID>LongMethod:TalkScreen.kt$@Composable fun TalkScreen( session: AuthSession, modifier: Modifier = Modifier, openRoomToken: String? = null, scrollToMessageId: Long? = null, chatsListRequest: Int = 0, pushSyncRequest: Int = 0, pushRoomToken: String? = null, onOpenRoomConsumed: () -> Unit = {}, onRoomOpenStateChange: (Boolean) -> Unit = {}, onOpenCalendar: () -> Unit = {}, onUnauthorized: () -> Unit = {}, )</ID>
|
<ID>LongMethod:TalkScreen.kt$@Composable fun TalkScreen( session: AuthSession, modifier: Modifier = Modifier, openRoomToken: String? = null, scrollToMessageId: Long? = null, chatsListRequest: Int = 0, pushSyncRequest: Int = 0, pushRoomToken: String? = null, onOpenRoomConsumed: () -> Unit = {}, onRoomOpenStateChange: (Boolean) -> Unit = {}, onOpenCalendar: () -> Unit = {}, onUnauthorized: () -> Unit = {}, )</ID>
|
||||||
<ID>LongMethod:TalkScreen.kt$@Composable private fun ChatContent( session: AuthSession, state: TalkUiState, draft: String, onDraftChange: (String) -> Unit, onSend: () -> Unit, onAttach: () -> Unit, onVoiceToggle: () -> Unit, voiceRecording: Boolean, onRetry: () -> Unit, onStartCall: () -> Unit, onBackClick: () -> Unit, onOpenRoomInfo: () -> Unit, onScheduleMeeting: () -> Unit, onToggleSystemMessages: () -> Unit, onReply: (TalkMessage) -> Unit, onReplyPrivately: (TalkMessage) -> Unit, onReaction: (TalkMessage, String) -> Unit, onDeleteMessage: (TalkMessage) -> Unit, onEditMessage: (TalkMessage, String) -> Unit, onForwardNoteToSelf: (TalkMessage) -> Unit, onSetReminder: (TalkMessage, ReminderPreset) -> Unit, onMentionQuery: (String?) -> Unit, onMentionSelected: (TalkMention) -> Unit, onClearReply: () -> Unit, onClearHighlight: () -> Unit, )</ID>
|
<ID>LongMethod:TalkScreen.kt$@Composable private fun ChatContent( session: AuthSession, state: TalkUiState, draft: String, onDraftChange: (String) -> Unit, onSend: () -> Unit, onAttach: () -> Unit, onVoiceToggle: () -> Unit, voiceRecording: Boolean, onRetry: () -> Unit, onStartCall: () -> Unit, onBackClick: () -> Unit, onOpenRoomInfo: () -> Unit, onScheduleMeeting: () -> Unit, onToggleSystemMessages: () -> Unit, onReply: (TalkMessage) -> Unit, onReplyPrivately: (TalkMessage) -> Unit, onReaction: (TalkMessage, String) -> Unit, onDeleteMessage: (TalkMessage) -> Unit, onEditMessage: (TalkMessage, String) -> Unit, onForwardNoteToSelf: (TalkMessage) -> Unit, onForwardToRoom: (TalkMessage, String) -> Unit, onSetReminder: (TalkMessage, ReminderPreset) -> Unit, onMentionQuery: (String?) -> Unit, onMentionSelected: (TalkMention) -> Unit, onClearReply: () -> Unit, onClearHighlight: () -> Unit, )</ID>
|
||||||
<ID>LoopWithTooManyJumpStatements:TalkRepository.kt$TalkRepository$for</ID>
|
<ID>LoopWithTooManyJumpStatements:TalkRepository.kt$TalkRepository$for</ID>
|
||||||
<ID>LoopWithTooManyJumpStatements:TalkViewModel.kt$TalkViewModel$while</ID>
|
<ID>LoopWithTooManyJumpStatements:TalkViewModel.kt$TalkViewModel$while</ID>
|
||||||
<ID>MatchingDeclarationName:TalkFilters.kt$TalkRoomFilter</ID>
|
<ID>MatchingDeclarationName:TalkFilters.kt$TalkRoomFilter</ID>
|
||||||
|
|||||||
@@ -794,6 +794,39 @@ fun TalkUnreadMarker(modifier: Modifier = Modifier) {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@OptIn(ExperimentalMaterial3Api::class)
|
||||||
|
@Composable
|
||||||
|
fun ForwardTargetSheet(
|
||||||
|
message: TalkMessage?,
|
||||||
|
rooms: List<TalkRoom>,
|
||||||
|
onDismiss: () -> Unit,
|
||||||
|
onPick: (TalkMessage, TalkRoom) -> Unit,
|
||||||
|
) {
|
||||||
|
if (message == null) return
|
||||||
|
val sheetState = rememberModalBottomSheetState(skipPartiallyExpanded = true)
|
||||||
|
ModalBottomSheet(onDismissRequest = onDismiss, sheetState = sheetState, containerColor = F7Colors.Surface) {
|
||||||
|
Column(
|
||||||
|
modifier = Modifier
|
||||||
|
.fillMaxWidth()
|
||||||
|
.padding(horizontal = 16.dp, vertical = 8.dp),
|
||||||
|
verticalArrangement = Arrangement.spacedBy(4.dp),
|
||||||
|
) {
|
||||||
|
Text(
|
||||||
|
"Переслать в…",
|
||||||
|
style = MaterialTheme.typography.labelMedium,
|
||||||
|
color = F7Colors.TextSecondary,
|
||||||
|
)
|
||||||
|
LazyColumn(modifier = Modifier.heightIn(max = 360.dp)) {
|
||||||
|
items(rooms, key = { it.token }) { room ->
|
||||||
|
F7TextButton(text = room.displayName, onClick = { onPick(message, room) })
|
||||||
|
}
|
||||||
|
}
|
||||||
|
F7TextButton(text = "Отмена", onClick = onDismiss)
|
||||||
|
Spacer(modifier = Modifier.height(16.dp))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
@OptIn(ExperimentalMaterial3Api::class)
|
@OptIn(ExperimentalMaterial3Api::class)
|
||||||
@Composable
|
@Composable
|
||||||
fun TalkMessageActionSheet(
|
fun TalkMessageActionSheet(
|
||||||
@@ -809,6 +842,7 @@ fun TalkMessageActionSheet(
|
|||||||
onCopy: (TalkMessage) -> Unit,
|
onCopy: (TalkMessage) -> Unit,
|
||||||
onCopyLink: (TalkMessage) -> Unit,
|
onCopyLink: (TalkMessage) -> Unit,
|
||||||
onForwardNoteToSelf: (TalkMessage) -> Unit,
|
onForwardNoteToSelf: (TalkMessage) -> Unit,
|
||||||
|
onForward: (TalkMessage) -> Unit,
|
||||||
onSetReminder: (TalkMessage, ReminderPreset) -> Unit,
|
onSetReminder: (TalkMessage, ReminderPreset) -> Unit,
|
||||||
) {
|
) {
|
||||||
if (message == null) return
|
if (message == null) return
|
||||||
@@ -847,6 +881,7 @@ fun TalkMessageActionSheet(
|
|||||||
text = "Переслать в «Личные заметки»",
|
text = "Переслать в «Личные заметки»",
|
||||||
onClick = { onForwardNoteToSelf(message) },
|
onClick = { onForwardNoteToSelf(message) },
|
||||||
)
|
)
|
||||||
|
F7TextButton(text = "Переслать…", onClick = { onForward(message) })
|
||||||
Text(
|
Text(
|
||||||
"Напоминание:",
|
"Напоминание:",
|
||||||
style = MaterialTheme.typography.labelMedium,
|
style = MaterialTheme.typography.labelMedium,
|
||||||
|
|||||||
@@ -297,6 +297,7 @@ fun TalkScreen(
|
|||||||
onDeleteMessage = { vm.deleteMessage(session, it.id) },
|
onDeleteMessage = { vm.deleteMessage(session, it.id) },
|
||||||
onEditMessage = { msg, text -> vm.editMessage(session, msg.id, text) },
|
onEditMessage = { msg, text -> vm.editMessage(session, msg.id, text) },
|
||||||
onForwardNoteToSelf = { vm.forwardToNoteToSelf(session, it) },
|
onForwardNoteToSelf = { vm.forwardToNoteToSelf(session, it) },
|
||||||
|
onForwardToRoom = { msg, token -> vm.forwardToRoom(session, msg, token) },
|
||||||
onSetReminder = { msg, preset -> vm.setMessageReminder(session, msg.id, preset) },
|
onSetReminder = { msg, preset -> vm.setMessageReminder(session, msg.id, preset) },
|
||||||
onMentionQuery = { vm.setMentionQuery(session, it) },
|
onMentionQuery = { vm.setMentionQuery(session, it) },
|
||||||
onMentionSelected = { mention ->
|
onMentionSelected = { mention ->
|
||||||
@@ -416,16 +417,42 @@ private fun RoomListContent(
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Composable
|
||||||
|
private fun ConfirmDeleteMessageDialog(
|
||||||
|
message: TalkMessage?,
|
||||||
|
onConfirm: (TalkMessage) -> Unit,
|
||||||
|
onDismiss: () -> Unit,
|
||||||
|
) {
|
||||||
|
if (message == null) return
|
||||||
|
AlertDialog(
|
||||||
|
onDismissRequest = onDismiss,
|
||||||
|
title = { Text("Удалить сообщение?") },
|
||||||
|
text = { Text("Сообщение будет удалено для всех участников.") },
|
||||||
|
confirmButton = {
|
||||||
|
TextButton(onClick = { onConfirm(message) }) {
|
||||||
|
Text("Удалить", color = F7Colors.Primary)
|
||||||
|
}
|
||||||
|
},
|
||||||
|
dismissButton = {
|
||||||
|
TextButton(onClick = onDismiss) {
|
||||||
|
Text("Отмена")
|
||||||
|
}
|
||||||
|
},
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
@Composable
|
@Composable
|
||||||
private fun MessageActionOverlays(
|
private fun MessageActionOverlays(
|
||||||
actionMessage: TalkMessage?,
|
actionMessage: TalkMessage?,
|
||||||
session: AuthSession,
|
session: AuthSession,
|
||||||
roomToken: String?,
|
roomToken: String?,
|
||||||
|
rooms: List<TalkRoom>,
|
||||||
onClearAction: () -> Unit,
|
onClearAction: () -> Unit,
|
||||||
onReply: (TalkMessage) -> Unit,
|
onReply: (TalkMessage) -> Unit,
|
||||||
onReplyPrivately: (TalkMessage) -> Unit,
|
onReplyPrivately: (TalkMessage) -> Unit,
|
||||||
onReaction: (TalkMessage, String) -> Unit,
|
onReaction: (TalkMessage, String) -> Unit,
|
||||||
onForwardNoteToSelf: (TalkMessage) -> Unit,
|
onForwardNoteToSelf: (TalkMessage) -> Unit,
|
||||||
|
onForwardToRoom: (TalkMessage, String) -> Unit,
|
||||||
onSetReminder: (TalkMessage, ReminderPreset) -> Unit,
|
onSetReminder: (TalkMessage, ReminderPreset) -> Unit,
|
||||||
onEditMessage: (TalkMessage, String) -> Unit,
|
onEditMessage: (TalkMessage, String) -> Unit,
|
||||||
onDeleteMessage: (TalkMessage) -> Unit,
|
onDeleteMessage: (TalkMessage) -> Unit,
|
||||||
@@ -435,6 +462,7 @@ private fun MessageActionOverlays(
|
|||||||
var editingMessage by remember { mutableStateOf<TalkMessage?>(null) }
|
var editingMessage by remember { mutableStateOf<TalkMessage?>(null) }
|
||||||
var editDraft by remember { mutableStateOf("") }
|
var editDraft by remember { mutableStateOf("") }
|
||||||
var pendingDelete by remember { mutableStateOf<TalkMessage?>(null) }
|
var pendingDelete by remember { mutableStateOf<TalkMessage?>(null) }
|
||||||
|
var forwardMessage by remember { mutableStateOf<TalkMessage?>(null) }
|
||||||
|
|
||||||
TalkMessageActionSheet(
|
TalkMessageActionSheet(
|
||||||
message = actionMessage,
|
message = actionMessage,
|
||||||
@@ -483,6 +511,10 @@ private fun MessageActionOverlays(
|
|||||||
Toast.makeText(context, "Переслано в «Личные заметки»", Toast.LENGTH_SHORT).show()
|
Toast.makeText(context, "Переслано в «Личные заметки»", Toast.LENGTH_SHORT).show()
|
||||||
onClearAction()
|
onClearAction()
|
||||||
},
|
},
|
||||||
|
onForward = { msg ->
|
||||||
|
forwardMessage = msg
|
||||||
|
onClearAction()
|
||||||
|
},
|
||||||
onSetReminder = { msg, preset ->
|
onSetReminder = { msg, preset ->
|
||||||
onSetReminder(msg, preset)
|
onSetReminder(msg, preset)
|
||||||
Toast.makeText(context, "Напоминание установлено", Toast.LENGTH_SHORT).show()
|
Toast.makeText(context, "Напоминание установлено", Toast.LENGTH_SHORT).show()
|
||||||
@@ -518,27 +550,26 @@ private fun MessageActionOverlays(
|
|||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
pendingDelete?.let { message ->
|
ConfirmDeleteMessageDialog(
|
||||||
AlertDialog(
|
message = pendingDelete,
|
||||||
onDismissRequest = { pendingDelete = null },
|
onConfirm = {
|
||||||
title = { Text("Удалить сообщение?") },
|
onDeleteMessage(it)
|
||||||
text = { Text("Сообщение будет удалено для всех участников.") },
|
|
||||||
confirmButton = {
|
|
||||||
TextButton(onClick = {
|
|
||||||
onDeleteMessage(message)
|
|
||||||
pendingDelete = null
|
pendingDelete = null
|
||||||
}) {
|
|
||||||
Text("Удалить", color = F7Colors.Primary)
|
|
||||||
}
|
|
||||||
},
|
},
|
||||||
dismissButton = {
|
onDismiss = { pendingDelete = null },
|
||||||
TextButton(onClick = { pendingDelete = null }) {
|
)
|
||||||
Text("Отмена")
|
|
||||||
}
|
ForwardTargetSheet(
|
||||||
|
message = forwardMessage,
|
||||||
|
rooms = rooms.filter { it.token != roomToken },
|
||||||
|
onDismiss = { forwardMessage = null },
|
||||||
|
onPick = { msg, room ->
|
||||||
|
onForwardToRoom(msg, room.token)
|
||||||
|
forwardMessage = null
|
||||||
|
Toast.makeText(context, "Переслано", Toast.LENGTH_SHORT).show()
|
||||||
},
|
},
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
}
|
|
||||||
|
|
||||||
@Composable
|
@Composable
|
||||||
private fun ChatContent(
|
private fun ChatContent(
|
||||||
@@ -562,6 +593,7 @@ private fun ChatContent(
|
|||||||
onDeleteMessage: (TalkMessage) -> Unit,
|
onDeleteMessage: (TalkMessage) -> Unit,
|
||||||
onEditMessage: (TalkMessage, String) -> Unit,
|
onEditMessage: (TalkMessage, String) -> Unit,
|
||||||
onForwardNoteToSelf: (TalkMessage) -> Unit,
|
onForwardNoteToSelf: (TalkMessage) -> Unit,
|
||||||
|
onForwardToRoom: (TalkMessage, String) -> Unit,
|
||||||
onSetReminder: (TalkMessage, ReminderPreset) -> Unit,
|
onSetReminder: (TalkMessage, ReminderPreset) -> Unit,
|
||||||
onMentionQuery: (String?) -> Unit,
|
onMentionQuery: (String?) -> Unit,
|
||||||
onMentionSelected: (TalkMention) -> Unit,
|
onMentionSelected: (TalkMention) -> Unit,
|
||||||
@@ -618,11 +650,13 @@ private fun ChatContent(
|
|||||||
actionMessage = actionMessage,
|
actionMessage = actionMessage,
|
||||||
session = session,
|
session = session,
|
||||||
roomToken = state.selectedRoomToken,
|
roomToken = state.selectedRoomToken,
|
||||||
|
rooms = state.rooms,
|
||||||
onClearAction = { actionMessage = null },
|
onClearAction = { actionMessage = null },
|
||||||
onReply = onReply,
|
onReply = onReply,
|
||||||
onReplyPrivately = onReplyPrivately,
|
onReplyPrivately = onReplyPrivately,
|
||||||
onReaction = onReaction,
|
onReaction = onReaction,
|
||||||
onForwardNoteToSelf = onForwardNoteToSelf,
|
onForwardNoteToSelf = onForwardNoteToSelf,
|
||||||
|
onForwardToRoom = onForwardToRoom,
|
||||||
onSetReminder = onSetReminder,
|
onSetReminder = onSetReminder,
|
||||||
onEditMessage = onEditMessage,
|
onEditMessage = onEditMessage,
|
||||||
onDeleteMessage = onDeleteMessage,
|
onDeleteMessage = onDeleteMessage,
|
||||||
|
|||||||
@@ -625,6 +625,21 @@ class TalkViewModel(
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/** Переслать текст сообщения в выбранную комнату. */
|
||||||
|
fun forwardToRoom(session: AuthSession, message: TalkMessage, targetToken: String) {
|
||||||
|
val text = message.displayText.trim()
|
||||||
|
if (text.isEmpty() || targetToken.isBlank()) return
|
||||||
|
viewModelScope.launch(Dispatchers.IO) {
|
||||||
|
runCatching { repository.sendMessage(session, targetToken, text) }
|
||||||
|
.onFailure { t ->
|
||||||
|
_state.value = _state.value.copy(
|
||||||
|
error = t.message ?: "Не удалось переслать",
|
||||||
|
unauthorized = t is UnauthorizedException,
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
/** Ответить лично: открыть (или создать) 1:1 с автором сообщения. */
|
/** Ответить лично: открыть (или создать) 1:1 с автором сообщения. */
|
||||||
fun replyPrivately(session: AuthSession, message: TalkMessage) {
|
fun replyPrivately(session: AuthSession, message: TalkMessage) {
|
||||||
val userId = message.actorId
|
val userId = message.actorId
|
||||||
|
|||||||
Reference in New Issue
Block a user