add profile placeholder
This commit is contained in:
parent
ac274ec885
commit
1c8e80df1f
@ -182,6 +182,9 @@
|
|||||||
},
|
},
|
||||||
"Profile" : {
|
"Profile" : {
|
||||||
|
|
||||||
|
},
|
||||||
|
"Push включены — приходят все новые сообщения." : {
|
||||||
|
"comment" : "Message profile notifications subtitle on"
|
||||||
},
|
},
|
||||||
"Push-уведомления" : {
|
"Push-уведомления" : {
|
||||||
"localizations" : {
|
"localizations" : {
|
||||||
@ -214,6 +217,9 @@
|
|||||||
},
|
},
|
||||||
"Yobble Passport" : {
|
"Yobble Passport" : {
|
||||||
|
|
||||||
|
},
|
||||||
|
"Автоудаление" : {
|
||||||
|
"comment" : "Message profile auto delete alert title\nMessage profile auto delete title"
|
||||||
},
|
},
|
||||||
"Автоудаление аккаунта" : {
|
"Автоудаление аккаунта" : {
|
||||||
"localizations" : {
|
"localizations" : {
|
||||||
@ -227,6 +233,9 @@
|
|||||||
},
|
},
|
||||||
"Аккаунт не найден." : {
|
"Аккаунт не найден." : {
|
||||||
|
|
||||||
|
},
|
||||||
|
"Аккаунт удалён" : {
|
||||||
|
"comment" : "Message profile deleted tag"
|
||||||
},
|
},
|
||||||
"Активные сессии" : {
|
"Активные сессии" : {
|
||||||
"comment" : "Заголовок экрана активных сессий",
|
"comment" : "Заголовок экрана активных сессий",
|
||||||
@ -249,7 +258,7 @@
|
|||||||
|
|
||||||
},
|
},
|
||||||
"Безопасность" : {
|
"Безопасность" : {
|
||||||
"comment" : "Заголовок экрана настроек безопасности",
|
"comment" : "Message profile safety section title\nЗаголовок экрана настроек безопасности",
|
||||||
"localizations" : {
|
"localizations" : {
|
||||||
"en" : {
|
"en" : {
|
||||||
"stringUnit" : {
|
"stringUnit" : {
|
||||||
@ -261,13 +270,25 @@
|
|||||||
},
|
},
|
||||||
"Безопасность аккаунта" : {
|
"Безопасность аккаунта" : {
|
||||||
|
|
||||||
|
},
|
||||||
|
"Блокировка и жалобы доступны из профиля, как в Telegram." : {
|
||||||
|
"comment" : "Message profile safety section description"
|
||||||
},
|
},
|
||||||
"Блокировка контакта \"%1$@\" появится позже." : {
|
"Блокировка контакта \"%1$@\" появится позже." : {
|
||||||
"comment" : "Contacts block placeholder message"
|
"comment" : "Contacts block placeholder message"
|
||||||
},
|
},
|
||||||
|
"Блокировка чата пока в дизайне. Готовим отдельный экран со статусом и жалобой." : {
|
||||||
|
"comment" : "Message profile block alert message"
|
||||||
|
},
|
||||||
"Бот" : {
|
"Бот" : {
|
||||||
"comment" : "Тип сессии — бот"
|
"comment" : "Тип сессии — бот"
|
||||||
},
|
},
|
||||||
|
"был(а) %@" : {
|
||||||
|
"comment" : "Message profile last seen relative format"
|
||||||
|
},
|
||||||
|
"в сети" : {
|
||||||
|
"comment" : "Message profile online status"
|
||||||
|
},
|
||||||
"В чате пока нет сообщений." : {
|
"В чате пока нет сообщений." : {
|
||||||
|
|
||||||
},
|
},
|
||||||
@ -319,7 +340,10 @@
|
|||||||
}
|
}
|
||||||
},
|
},
|
||||||
"Видео" : {
|
"Видео" : {
|
||||||
"comment" : "Video message placeholder"
|
"comment" : "Message profile video action\nVideo message placeholder"
|
||||||
|
},
|
||||||
|
"Видео созвоны появятся вместе с звонками. Интерфейс повторит Telegram." : {
|
||||||
|
"comment" : "Message profile video action description"
|
||||||
},
|
},
|
||||||
"Видимость и контент" : {
|
"Видимость и контент" : {
|
||||||
"localizations" : {
|
"localizations" : {
|
||||||
@ -424,6 +448,12 @@
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
"Вы в его контактах" : {
|
||||||
|
"comment" : "Message profile contact tag"
|
||||||
|
},
|
||||||
|
"Вы в его чёрном списке" : {
|
||||||
|
"comment" : "Message profile blacklist tag"
|
||||||
|
},
|
||||||
"Вы всегда можете отключить двухфакторную защиту, но мы рекомендуем оставлять её включённой для безопасности." : {
|
"Вы всегда можете отключить двухфакторную защиту, но мы рекомендуем оставлять её включённой для безопасности." : {
|
||||||
"comment" : "Рекомендация оставить 2FA включенной"
|
"comment" : "Рекомендация оставить 2FA включенной"
|
||||||
},
|
},
|
||||||
@ -475,6 +505,9 @@
|
|||||||
"Глобальный поиск" : {
|
"Глобальный поиск" : {
|
||||||
"comment" : "Global search section"
|
"comment" : "Global search section"
|
||||||
},
|
},
|
||||||
|
"Голосовые звонки пока недоступны. Как только включим WebRTC, кнопка оживёт." : {
|
||||||
|
"comment" : "Message profile call action description"
|
||||||
|
},
|
||||||
"Готово" : {
|
"Готово" : {
|
||||||
"comment" : "Profile update success title\nЗаголовок успешного уведомления",
|
"comment" : "Profile update success title\nЗаголовок успешного уведомления",
|
||||||
"localizations" : {
|
"localizations" : {
|
||||||
@ -513,11 +546,17 @@
|
|||||||
"Двухфакторная аутентификация настроена." : {
|
"Двухфакторная аутентификация настроена." : {
|
||||||
"comment" : "Сообщение после успешного подтверждения кода 2FA"
|
"comment" : "Сообщение после успешного подтверждения кода 2FA"
|
||||||
},
|
},
|
||||||
|
"Действия в чате" : {
|
||||||
|
"comment" : "Message profile quick actions title"
|
||||||
|
},
|
||||||
"Десктоп" : {
|
"Десктоп" : {
|
||||||
"comment" : "Тип сессии — десктоп"
|
"comment" : "Тип сессии — десктоп"
|
||||||
},
|
},
|
||||||
"Для начала, мы рекомендуем настроить параметры безопасности вашего аккаунта." : {
|
"Для начала, мы рекомендуем настроить параметры безопасности вашего аккаунта." : {
|
||||||
|
|
||||||
|
},
|
||||||
|
"Добавить в контакты" : {
|
||||||
|
"comment" : "Message profile add to contacts title"
|
||||||
},
|
},
|
||||||
"Добавить друзей" : {
|
"Добавить друзей" : {
|
||||||
"comment" : "Add friends",
|
"comment" : "Add friends",
|
||||||
@ -530,6 +569,9 @@
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
"Добавить контакт" : {
|
||||||
|
"comment" : "Message profile add contact alert title"
|
||||||
|
},
|
||||||
"Добавление новых блокировок появится позже." : {
|
"Добавление новых блокировок появится позже." : {
|
||||||
"comment" : "Add blocked user placeholder message"
|
"comment" : "Add blocked user placeholder message"
|
||||||
},
|
},
|
||||||
@ -568,9 +610,18 @@
|
|||||||
},
|
},
|
||||||
"Если предпочитаете классический вход, используйте логин и пароль." : {
|
"Если предпочитаете классический вход, используйте логин и пароль." : {
|
||||||
|
|
||||||
|
},
|
||||||
|
"Жалоба" : {
|
||||||
|
"comment" : "Message profile report alert title"
|
||||||
|
},
|
||||||
|
"Заблокирован" : {
|
||||||
|
"comment" : "Message profile blocked tag"
|
||||||
},
|
},
|
||||||
"Заблокированные" : {
|
"Заблокированные" : {
|
||||||
|
|
||||||
|
},
|
||||||
|
"Заблокировать" : {
|
||||||
|
"comment" : "Message profile block alert title\nMessage profile block title"
|
||||||
},
|
},
|
||||||
"Заблокировать контакт" : {
|
"Заблокировать контакт" : {
|
||||||
"comment" : "Contacts context action block"
|
"comment" : "Contacts context action block"
|
||||||
@ -688,6 +739,9 @@
|
|||||||
"Защита приложением будет добавлена в будущих обновлениях." : {
|
"Защита приложением будет добавлена в будущих обновлениях." : {
|
||||||
"comment" : "Сообщение заглушки пароля на приложение"
|
"comment" : "Сообщение заглушки пароля на приложение"
|
||||||
},
|
},
|
||||||
|
"Звонок" : {
|
||||||
|
"comment" : "Message profile call action"
|
||||||
|
},
|
||||||
"Здесь не будут чаты" : {
|
"Здесь не будут чаты" : {
|
||||||
"localizations" : {
|
"localizations" : {
|
||||||
"en" : {
|
"en" : {
|
||||||
@ -718,6 +772,9 @@
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
"Избранное" : {
|
||||||
|
"comment" : "Message profile self chat type"
|
||||||
|
},
|
||||||
"Избранные сообщения" : {
|
"Избранные сообщения" : {
|
||||||
"comment" : "Saved messages title"
|
"comment" : "Saved messages title"
|
||||||
},
|
},
|
||||||
@ -746,6 +803,12 @@
|
|||||||
"Изображение" : {
|
"Изображение" : {
|
||||||
"comment" : "Image message placeholder"
|
"comment" : "Image message placeholder"
|
||||||
},
|
},
|
||||||
|
"Имя в чате" : {
|
||||||
|
|
||||||
|
},
|
||||||
|
"Имя, логин и статус — как в профиле Telegram." : {
|
||||||
|
"comment" : "Message profile about description"
|
||||||
|
},
|
||||||
"Инвайт-код (необязательно)" : {
|
"Инвайт-код (необязательно)" : {
|
||||||
"comment" : "Инвайт-код",
|
"comment" : "Инвайт-код",
|
||||||
"localizations" : {
|
"localizations" : {
|
||||||
@ -768,9 +831,15 @@
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
"История медиа синхронизируется. Как только появятся первые вложения, они покажутся здесь списком превью." : {
|
||||||
|
"comment" : "Message profile media footer"
|
||||||
|
},
|
||||||
"Ищем пользователей…" : {
|
"Ищем пользователей…" : {
|
||||||
"comment" : "Global search loading"
|
"comment" : "Global search loading"
|
||||||
},
|
},
|
||||||
|
"Как в Telegram — главные кнопки всегда под рукой." : {
|
||||||
|
"comment" : "Message profile quick actions description"
|
||||||
|
},
|
||||||
"Как сбросить пароль?" : {
|
"Как сбросить пароль?" : {
|
||||||
"comment" : "FAQ question: reset password"
|
"comment" : "FAQ question: reset password"
|
||||||
},
|
},
|
||||||
@ -801,6 +870,9 @@
|
|||||||
"Кликер в разработке" : {
|
"Кликер в разработке" : {
|
||||||
"comment" : "Concept tab placeholder title"
|
"comment" : "Concept tab placeholder title"
|
||||||
},
|
},
|
||||||
|
"Кнопка поделиться соберёт ссылку, QR и кнопку пересылки контакта." : {
|
||||||
|
"comment" : "Message profile share alert message"
|
||||||
|
},
|
||||||
"Код дружбы" : {
|
"Код дружбы" : {
|
||||||
"comment" : "Friend code badge"
|
"comment" : "Friend code badge"
|
||||||
},
|
},
|
||||||
@ -813,6 +885,9 @@
|
|||||||
"Коды восстановления" : {
|
"Коды восстановления" : {
|
||||||
"comment" : "Раздел кодов восстановления 2FA"
|
"comment" : "Раздел кодов восстановления 2FA"
|
||||||
},
|
},
|
||||||
|
"Контакт" : {
|
||||||
|
"comment" : "Message profile contact section title"
|
||||||
|
},
|
||||||
"Контактов пока нет" : {
|
"Контактов пока нет" : {
|
||||||
"comment" : "Contacts empty state title"
|
"comment" : "Contacts empty state title"
|
||||||
},
|
},
|
||||||
@ -931,6 +1006,9 @@
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
"Личный чат" : {
|
||||||
|
"comment" : "Message profile private chat type"
|
||||||
|
},
|
||||||
"Логин" : {
|
"Логин" : {
|
||||||
"comment" : "Логин",
|
"comment" : "Логин",
|
||||||
"extractionState" : "stale",
|
"extractionState" : "stale",
|
||||||
@ -979,6 +1057,9 @@
|
|||||||
},
|
},
|
||||||
"Массовая отчистка" : {
|
"Массовая отчистка" : {
|
||||||
|
|
||||||
|
},
|
||||||
|
"Медиа, ссылки и файлы" : {
|
||||||
|
"comment" : "Message profile media title"
|
||||||
},
|
},
|
||||||
"Мессенджер-режим сейчас проработан примерно на 50%." : {
|
"Мессенджер-режим сейчас проработан примерно на 50%." : {
|
||||||
|
|
||||||
@ -1050,6 +1131,9 @@
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
"Мы постепенно повторяем знакомый паттерн Telegram, чтобы переход был комфортным. Укажите, что ещё ожидать на экране профиля — добавим приоритетно." : {
|
||||||
|
"comment" : "Message profile footer"
|
||||||
|
},
|
||||||
"Мы свяжемся с вами по адресу %@, как только ответим." : {
|
"Мы свяжемся с вами по адресу %@, как только ответим." : {
|
||||||
"comment" : "feedback: success email",
|
"comment" : "feedback: success email",
|
||||||
"localizations" : {
|
"localizations" : {
|
||||||
@ -1072,6 +1156,12 @@
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
"На Yobble" : {
|
||||||
|
|
||||||
|
},
|
||||||
|
"На платформе с %@" : {
|
||||||
|
"comment" : "Message profile joined format"
|
||||||
|
},
|
||||||
"Назад" : {
|
"Назад" : {
|
||||||
|
|
||||||
},
|
},
|
||||||
@ -1174,6 +1264,9 @@
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
"Настройки чата" : {
|
||||||
|
"comment" : "Message profile chat settings title"
|
||||||
|
},
|
||||||
"Начальная настройка" : {
|
"Начальная настройка" : {
|
||||||
|
|
||||||
},
|
},
|
||||||
@ -1520,6 +1613,9 @@
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
"О пользователе" : {
|
||||||
|
"comment" : "Message profile about title"
|
||||||
|
},
|
||||||
"О приложении" : {
|
"О приложении" : {
|
||||||
"localizations" : {
|
"localizations" : {
|
||||||
"en" : {
|
"en" : {
|
||||||
@ -1599,6 +1695,9 @@
|
|||||||
},
|
},
|
||||||
"Отображаемое имя" : {
|
"Отображаемое имя" : {
|
||||||
|
|
||||||
|
},
|
||||||
|
"Отправим ссылку или QR — как в Telegram." : {
|
||||||
|
"comment" : "Message profile share contact subtitle"
|
||||||
},
|
},
|
||||||
"Отправить код ещё раз" : {
|
"Отправить код ещё раз" : {
|
||||||
|
|
||||||
@ -1661,12 +1760,18 @@
|
|||||||
},
|
},
|
||||||
"Очистить всё" : {
|
"Очистить всё" : {
|
||||||
|
|
||||||
|
},
|
||||||
|
"Очистить историю" : {
|
||||||
|
"comment" : "Message profile clear history title"
|
||||||
},
|
},
|
||||||
"Очистить кэш (кроме текущего)" : {
|
"Очистить кэш (кроме текущего)" : {
|
||||||
|
|
||||||
},
|
},
|
||||||
"Очистить кэш текущего пользователя" : {
|
"Очистить кэш текущего пользователя" : {
|
||||||
|
|
||||||
|
},
|
||||||
|
"Очистка истории" : {
|
||||||
|
"comment" : "Message profile clear history alert title"
|
||||||
},
|
},
|
||||||
"Ошибка" : {
|
"Ошибка" : {
|
||||||
"comment" : "Common error title\nContacts load error title\nProfile update error title\nЗаголовок сообщения об ошибке",
|
"comment" : "Common error title\nContacts load error title\nProfile update error title\nЗаголовок сообщения об ошибке",
|
||||||
@ -1844,6 +1949,12 @@
|
|||||||
"Перейдите в раздел \"Настройки > Сменить пароль\" и следуйте инструкциям." : {
|
"Перейдите в раздел \"Настройки > Сменить пароль\" и следуйте инструкциям." : {
|
||||||
"comment" : "FAQ answer: reset password"
|
"comment" : "FAQ answer: reset password"
|
||||||
},
|
},
|
||||||
|
"Перестанет появляться в чате и не сможет писать." : {
|
||||||
|
"comment" : "Message profile block subtitle"
|
||||||
|
},
|
||||||
|
"Плитки как в Telegram — скоро здесь появятся вложения из чата." : {
|
||||||
|
"comment" : "Message profile media description"
|
||||||
|
},
|
||||||
"По умолчанию это полноценная соцсеть с лентой, историями и подписками. Если нужно только общение без лишнего контента, переключитесь на режим “Только чаты”. Переключить режим можно в любой момент." : {
|
"По умолчанию это полноценная соцсеть с лентой, историями и подписками. Если нужно только общение без лишнего контента, переключитесь на режим “Только чаты”. Переключить режим можно в любой момент." : {
|
||||||
|
|
||||||
},
|
},
|
||||||
@ -1895,6 +2006,12 @@
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
"Поделиться" : {
|
||||||
|
"comment" : "Message profile share alert title"
|
||||||
|
},
|
||||||
|
"Поделиться профилем" : {
|
||||||
|
"comment" : "Message profile share contact title"
|
||||||
|
},
|
||||||
"Подключение" : {
|
"Подключение" : {
|
||||||
|
|
||||||
},
|
},
|
||||||
@ -1918,6 +2035,12 @@
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
"Подтверждённый профиль" : {
|
||||||
|
"comment" : "Message profile verified tag"
|
||||||
|
},
|
||||||
|
"Пожаловаться" : {
|
||||||
|
"comment" : "Message profile report title"
|
||||||
|
},
|
||||||
"Пожалуйста, введите корректный e-mail." : {
|
"Пожалуйста, введите корректный e-mail." : {
|
||||||
"comment" : "feedback: email error",
|
"comment" : "feedback: email error",
|
||||||
"localizations" : {
|
"localizations" : {
|
||||||
@ -1933,7 +2056,7 @@
|
|||||||
|
|
||||||
},
|
},
|
||||||
"Поиск" : {
|
"Поиск" : {
|
||||||
|
"comment" : "Message profile search action"
|
||||||
},
|
},
|
||||||
"Поиск отменён." : {
|
"Поиск отменён." : {
|
||||||
"comment" : "Search cancelled"
|
"comment" : "Search cancelled"
|
||||||
@ -2011,6 +2134,12 @@
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
"Пользователь снова сможет писать вам." : {
|
||||||
|
"comment" : "Message profile unblock subtitle"
|
||||||
|
},
|
||||||
|
"Пользователь удалён" : {
|
||||||
|
"comment" : "Message profile deleted user status"
|
||||||
|
},
|
||||||
"Помощь" : {
|
"Помощь" : {
|
||||||
"comment" : "Help Center",
|
"comment" : "Help Center",
|
||||||
"localizations" : {
|
"localizations" : {
|
||||||
@ -2023,13 +2152,16 @@
|
|||||||
}
|
}
|
||||||
},
|
},
|
||||||
"Понятно" : {
|
"Понятно" : {
|
||||||
"comment" : "Chat creation error acknowledgment"
|
"comment" : "Chat creation error acknowledgment\nPlaceholder alert dismiss"
|
||||||
},
|
},
|
||||||
"Попробовать снова можно через %d сек" : {
|
"Попробовать снова можно через %d сек" : {
|
||||||
|
|
||||||
},
|
},
|
||||||
"Попробуйте изменить запрос поиска." : {
|
"Попробуйте изменить запрос поиска." : {
|
||||||
|
|
||||||
|
},
|
||||||
|
"Последний визит" : {
|
||||||
|
|
||||||
},
|
},
|
||||||
"Последний вход: %@" : {
|
"Последний вход: %@" : {
|
||||||
"comment" : "Дата последнего входа в сессию"
|
"comment" : "Дата последнего входа в сессию"
|
||||||
@ -2045,6 +2177,12 @@
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
"Появится мутация на 1 час, 1 день или навсегда." : {
|
||||||
|
"comment" : "Message profile mute action description"
|
||||||
|
},
|
||||||
|
"Появится отдельная запись в адресной книге Yobble." : {
|
||||||
|
"comment" : "Message profile add to contacts subtitle"
|
||||||
|
},
|
||||||
"Правила сервиса" : {
|
"Правила сервиса" : {
|
||||||
|
|
||||||
},
|
},
|
||||||
@ -2083,6 +2221,9 @@
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
"Приватный диалог" : {
|
||||||
|
"comment" : "Message profile default chat type"
|
||||||
|
},
|
||||||
"Приглашение достигло лимита использования." : {
|
"Приглашение достигло лимита использования." : {
|
||||||
"localizations" : {
|
"localizations" : {
|
||||||
"en" : {
|
"en" : {
|
||||||
@ -2228,7 +2369,7 @@
|
|||||||
"comment" : "Contacts placeholder message"
|
"comment" : "Contacts placeholder message"
|
||||||
},
|
},
|
||||||
"Профиль" : {
|
"Профиль" : {
|
||||||
"comment" : "Message profile placeholder nav title",
|
"comment" : "Message profile navigation title",
|
||||||
"localizations" : {
|
"localizations" : {
|
||||||
"en" : {
|
"en" : {
|
||||||
"stringUnit" : {
|
"stringUnit" : {
|
||||||
@ -2241,9 +2382,6 @@
|
|||||||
"Профиль в разработке" : {
|
"Профиль в разработке" : {
|
||||||
"comment" : "Search placeholder title"
|
"comment" : "Search placeholder title"
|
||||||
},
|
},
|
||||||
"Профиль для сообщений пока в разработке." : {
|
|
||||||
"comment" : "Message profile placeholder title"
|
|
||||||
},
|
|
||||||
"Профиль и поиск" : {
|
"Профиль и поиск" : {
|
||||||
"localizations" : {
|
"localizations" : {
|
||||||
"en" : {
|
"en" : {
|
||||||
@ -2264,7 +2402,7 @@
|
|||||||
|
|
||||||
},
|
},
|
||||||
"Разблокировать" : {
|
"Разблокировать" : {
|
||||||
"comment" : "Unblock confirmation action"
|
"comment" : "Message profile unblock alert title\nMessage profile unblock title\nUnblock confirmation action"
|
||||||
},
|
},
|
||||||
"Разрешить пересылку сообщений" : {
|
"Разрешить пересылку сообщений" : {
|
||||||
"localizations" : {
|
"localizations" : {
|
||||||
@ -2379,8 +2517,14 @@
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
"Редактор контактов скоро появится. Мы сохраним имя, телефон и заметку." : {
|
||||||
|
"comment" : "Message profile add contact alert message"
|
||||||
|
},
|
||||||
"Режим" : {
|
"Режим" : {
|
||||||
|
|
||||||
|
},
|
||||||
|
"Режим автоудаления появится чуть позже. Мы добавим пресеты на 24 часа, 7 дней и 1 месяц — совсем как в Telegram." : {
|
||||||
|
"comment" : "Message profile auto delete alert message"
|
||||||
},
|
},
|
||||||
"Режим мессенжера" : {
|
"Режим мессенжера" : {
|
||||||
|
|
||||||
@ -2501,12 +2645,18 @@
|
|||||||
"Скоро" : {
|
"Скоро" : {
|
||||||
"comment" : "Add blocked user placeholder title\nContacts placeholder title\nЗаголовок заглушки"
|
"comment" : "Add blocked user placeholder title\nContacts placeholder title\nЗаголовок заглушки"
|
||||||
},
|
},
|
||||||
"Скоро здесь появится информация о собеседнике, статусе и дополнительных действиях." : {
|
"Скоро можно будет искать сообщения, ссылки и файлы в этом чате." : {
|
||||||
"comment" : "Message profile placeholder description"
|
"comment" : "Message profile search action description"
|
||||||
|
},
|
||||||
|
"Скоро можно будет очистить сообщения выборочно или целиком. Пока подготовим дизайн." : {
|
||||||
|
"comment" : "Message profile clear history alert message"
|
||||||
},
|
},
|
||||||
"Скоро появится мини-игра, где можно заработать очки для кастомизации профиля. Следите за обновлениями!" : {
|
"Скоро появится мини-игра, где можно заработать очки для кастомизации профиля. Следите за обновлениями!" : {
|
||||||
"comment" : "Concept tab placeholder description"
|
"comment" : "Concept tab placeholder description"
|
||||||
},
|
},
|
||||||
|
"Скоро появится разблокировка с подтверждением и синхронизацией." : {
|
||||||
|
"comment" : "Message profile unblock alert message"
|
||||||
|
},
|
||||||
"Слишком много запросов." : {
|
"Слишком много запросов." : {
|
||||||
"localizations" : {
|
"localizations" : {
|
||||||
"en" : {
|
"en" : {
|
||||||
@ -2557,6 +2707,9 @@
|
|||||||
},
|
},
|
||||||
"Сообщение слишком длинное." : {
|
"Сообщение слишком длинное." : {
|
||||||
|
|
||||||
|
},
|
||||||
|
"Сообщения пока сохраняются навсегда." : {
|
||||||
|
"comment" : "Message profile auto delete subtitle"
|
||||||
},
|
},
|
||||||
"Сообщите о материалах" : {
|
"Сообщите о материалах" : {
|
||||||
"comment" : "feedback category subtitle: content",
|
"comment" : "feedback category subtitle: content",
|
||||||
@ -2569,6 +2722,9 @@
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
"Сообщите о спаме или нарушении правил." : {
|
||||||
|
"comment" : "Message profile report subtitle"
|
||||||
|
},
|
||||||
"Сохранение..." : {
|
"Сохранение..." : {
|
||||||
|
|
||||||
},
|
},
|
||||||
@ -2693,8 +2849,20 @@
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
"Тип диалога" : {
|
||||||
|
|
||||||
|
},
|
||||||
|
"Тишина" : {
|
||||||
|
"comment" : "Message profile mute action"
|
||||||
|
},
|
||||||
|
"Тишина включена. Чат не тревожит до включения сигнала." : {
|
||||||
|
"comment" : "Message profile notifications subtitle off"
|
||||||
|
},
|
||||||
"Только чаты (готово 60%)" : {
|
"Только чаты (готово 60%)" : {
|
||||||
|
|
||||||
|
},
|
||||||
|
"Тонкие настройки диалога по образцу профиля Telegram." : {
|
||||||
|
"comment" : "Message profile chat settings description"
|
||||||
},
|
},
|
||||||
"Ты шо ебанутый? А ниче тот факт что новый пароль должен отличаться от старого." : {
|
"Ты шо ебанутый? А ниче тот факт что новый пароль должен отличаться от старого." : {
|
||||||
"localizations" : {
|
"localizations" : {
|
||||||
@ -2721,6 +2889,7 @@
|
|||||||
}
|
}
|
||||||
},
|
},
|
||||||
"Уведомления" : {
|
"Уведомления" : {
|
||||||
|
"comment" : "Message profile notifications title",
|
||||||
"localizations" : {
|
"localizations" : {
|
||||||
"en" : {
|
"en" : {
|
||||||
"stringUnit" : {
|
"stringUnit" : {
|
||||||
@ -2742,6 +2911,9 @@
|
|||||||
"Удалить контакт" : {
|
"Удалить контакт" : {
|
||||||
"comment" : "Contacts context action delete"
|
"comment" : "Contacts context action delete"
|
||||||
},
|
},
|
||||||
|
"Удалить переписку только для себя." : {
|
||||||
|
"comment" : "Message profile clear history subtitle"
|
||||||
|
},
|
||||||
"Удалить фото" : {
|
"Удалить фото" : {
|
||||||
"comment" : "Avatar delete"
|
"comment" : "Avatar delete"
|
||||||
},
|
},
|
||||||
@ -2765,6 +2937,12 @@
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
"Управление карточкой собеседника." : {
|
||||||
|
"comment" : "Message profile contact section description"
|
||||||
|
},
|
||||||
|
"Форма жалобы появится чуть позже — добавим прикрепление скриншотов и тип нарушения." : {
|
||||||
|
"comment" : "Message profile report alert message"
|
||||||
|
},
|
||||||
"Функция пока недоступна." : {
|
"Функция пока недоступна." : {
|
||||||
"comment" : "Сообщение заглушки"
|
"comment" : "Сообщение заглушки"
|
||||||
},
|
},
|
||||||
@ -2892,6 +3070,9 @@
|
|||||||
},
|
},
|
||||||
"Этот аккаунт недоступен." : {
|
"Этот аккаунт недоступен." : {
|
||||||
|
|
||||||
|
},
|
||||||
|
"Юзернейм" : {
|
||||||
|
|
||||||
},
|
},
|
||||||
"Я ознакомился и принимаю правила сервиса" : {
|
"Я ознакомился и принимаю правила сервиса" : {
|
||||||
|
|
||||||
|
|||||||
@ -6,103 +6,685 @@
|
|||||||
//
|
//
|
||||||
import SwiftUI
|
import SwiftUI
|
||||||
|
|
||||||
|
|
||||||
struct MessageProfileView: View {
|
struct MessageProfileView: View {
|
||||||
let chat: PrivateChatListItem
|
let chat: PrivateChatListItem
|
||||||
let currentUserId: String?
|
let currentUserId: String?
|
||||||
private let avatarSize: CGFloat = 96
|
private let avatarSize: CGFloat = 96
|
||||||
|
|
||||||
var body: some View {
|
@State private var areNotificationsEnabled: Bool = true
|
||||||
ScrollView {
|
@State private var placeholderAlert: PlaceholderAlert?
|
||||||
VStack(spacing: 24) {
|
|
||||||
profileAvatar
|
|
||||||
|
|
||||||
VStack(spacing: 4) {
|
var body: some View {
|
||||||
|
ScrollView(showsIndicators: false) {
|
||||||
|
VStack(spacing: 24) {
|
||||||
|
headerCard
|
||||||
|
quickActionsSection
|
||||||
|
aboutSection
|
||||||
|
mediaPreviewSection
|
||||||
|
chatSettingsSection
|
||||||
|
contactActionsSection
|
||||||
|
safetySection
|
||||||
|
footerHint
|
||||||
|
}
|
||||||
|
.padding(.horizontal, 20)
|
||||||
|
.padding(.vertical, 24)
|
||||||
|
}
|
||||||
|
.background(Color(UIColor.systemGroupedBackground).ignoresSafeArea())
|
||||||
|
.navigationTitle(NSLocalizedString("Профиль", comment: "Message profile navigation title"))
|
||||||
|
.navigationBarTitleDisplayMode(.inline)
|
||||||
|
.alert(item: $placeholderAlert) { alert in
|
||||||
|
Alert(
|
||||||
|
title: Text(alert.title),
|
||||||
|
message: Text(alert.message),
|
||||||
|
dismissButton: .default(Text(NSLocalizedString("Понятно", comment: "Placeholder alert dismiss")))
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// MARK: - Header
|
||||||
|
|
||||||
|
private var headerCard: some View {
|
||||||
|
VStack(spacing: 16) {
|
||||||
|
profileAvatar
|
||||||
|
.overlay(alignment: .bottomTrailing) {
|
||||||
|
officialBadge
|
||||||
|
}
|
||||||
|
|
||||||
|
VStack(spacing: 6) {
|
||||||
|
HStack(spacing: 6) {
|
||||||
Text(displayName)
|
Text(displayName)
|
||||||
.font(.title3)
|
.font(.title2)
|
||||||
.fontWeight(.semibold)
|
.fontWeight(.semibold)
|
||||||
.multilineTextAlignment(.center)
|
.multilineTextAlignment(.center)
|
||||||
.frame(maxWidth: .infinity)
|
.frame(maxWidth: .infinity)
|
||||||
|
}
|
||||||
|
|
||||||
|
if let login = loginDisplay {
|
||||||
|
Text(login)
|
||||||
|
.font(.subheadline)
|
||||||
|
.foregroundColor(.secondary)
|
||||||
|
}
|
||||||
|
|
||||||
|
if let status = presenceStatus {
|
||||||
|
HStack(spacing: 6) {
|
||||||
|
Circle()
|
||||||
|
.fill(status.isOnline ? Color.green : Color.gray.opacity(0.4))
|
||||||
|
.frame(width: 8, height: 8)
|
||||||
|
Text(status.text)
|
||||||
|
.font(.footnote)
|
||||||
|
.foregroundColor(.secondary)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if let bio = profileBio {
|
||||||
|
Text(bio)
|
||||||
|
.font(.body)
|
||||||
|
.multilineTextAlignment(.center)
|
||||||
|
}
|
||||||
|
|
||||||
|
if !statusTags.isEmpty {
|
||||||
|
LazyVGrid(columns: [GridItem(.adaptive(minimum: 120), spacing: 8)], spacing: 8) {
|
||||||
|
ForEach(statusTags) { tag in
|
||||||
|
HStack(spacing: 6) {
|
||||||
|
Image(systemName: tag.icon)
|
||||||
|
.font(.system(size: 12, weight: .semibold))
|
||||||
|
Text(tag.text)
|
||||||
|
.font(.caption)
|
||||||
|
.fontWeight(.medium)
|
||||||
|
}
|
||||||
|
.padding(.vertical, 6)
|
||||||
|
.padding(.horizontal, 10)
|
||||||
|
.foregroundColor(tag.tint)
|
||||||
|
.background(tag.background)
|
||||||
|
.clipShape(Capsule())
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
.padding(24)
|
||||||
|
.frame(maxWidth: .infinity)
|
||||||
|
.background(
|
||||||
|
RoundedRectangle(cornerRadius: 32, style: .continuous)
|
||||||
|
.fill(headerGradient)
|
||||||
|
)
|
||||||
|
.overlay(
|
||||||
|
RoundedRectangle(cornerRadius: 32, style: .continuous)
|
||||||
|
.stroke(Color.white.opacity(0.08), lineWidth: 1)
|
||||||
|
)
|
||||||
|
.shadow(color: Color.black.opacity(0.08), radius: 20, x: 0, y: 10)
|
||||||
|
}
|
||||||
|
|
||||||
|
@ViewBuilder
|
||||||
|
private var officialBadge: some View {
|
||||||
|
if isOfficial {
|
||||||
|
Image(systemName: "checkmark.seal.fill")
|
||||||
|
.font(.system(size: 18, weight: .semibold))
|
||||||
|
.foregroundColor(.white)
|
||||||
|
.padding(6)
|
||||||
|
.background(Circle().fill(Color.accentColor))
|
||||||
|
.offset(x: 6, y: 6)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private var headerGradient: LinearGradient {
|
||||||
|
let first = isOfficial ? Color.accentColor : Color.accentColor.opacity(0.6)
|
||||||
|
let second = Color.accentColor.opacity(isOfficial ? 0.6 : 0.3)
|
||||||
|
let third = Color(UIColor.secondarySystemBackground)
|
||||||
|
return LinearGradient(colors: [first, second, third], startPoint: .topLeading, endPoint: .bottomTrailing)
|
||||||
|
}
|
||||||
|
|
||||||
|
// MARK: - Sections
|
||||||
|
|
||||||
|
private var quickActionsSection: some View {
|
||||||
|
section(
|
||||||
|
title: NSLocalizedString("Действия в чате", comment: "Message profile quick actions title"),
|
||||||
|
description: NSLocalizedString("Как в Telegram — главные кнопки всегда под рукой.", comment: "Message profile quick actions description")
|
||||||
|
) {
|
||||||
|
card {
|
||||||
|
HStack(spacing: 12) {
|
||||||
|
ForEach(quickActionItems) { action in
|
||||||
|
quickActionButton(action)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private func quickActionButton(_ action: ProfileQuickAction) -> some View {
|
||||||
|
Button(action: {
|
||||||
|
showPlaceholderAction(title: action.title, message: action.description)
|
||||||
|
}) {
|
||||||
|
VStack(spacing: 10) {
|
||||||
|
Circle()
|
||||||
|
.fill(action.tint.opacity(0.15))
|
||||||
|
.frame(width: 64, height: 64)
|
||||||
|
.overlay(
|
||||||
|
Image(systemName: action.icon)
|
||||||
|
.font(.system(size: 22, weight: .semibold))
|
||||||
|
.foregroundColor(action.tint)
|
||||||
|
)
|
||||||
|
Text(action.title)
|
||||||
|
.font(.footnote)
|
||||||
|
.multilineTextAlignment(.center)
|
||||||
|
.foregroundColor(.primary)
|
||||||
|
}
|
||||||
|
.frame(maxWidth: .infinity)
|
||||||
|
}
|
||||||
|
.buttonStyle(.plain)
|
||||||
|
}
|
||||||
|
|
||||||
|
private var aboutSection: some View {
|
||||||
|
section(
|
||||||
|
title: NSLocalizedString("О пользователе", comment: "Message profile about title"),
|
||||||
|
description: NSLocalizedString("Имя, логин и статус — как в профиле Telegram.", comment: "Message profile about description")
|
||||||
|
) {
|
||||||
|
card {
|
||||||
|
VStack(spacing: 0) {
|
||||||
|
infoRow(
|
||||||
|
icon: "person.text.rectangle",
|
||||||
|
title: NSLocalizedString("Имя в чате", comment: ""),
|
||||||
|
value: displayName
|
||||||
|
)
|
||||||
|
|
||||||
if let login = loginDisplay {
|
if let login = loginDisplay {
|
||||||
Text(login)
|
rowDivider
|
||||||
.font(.subheadline)
|
infoRow(
|
||||||
|
icon: "at",
|
||||||
|
title: NSLocalizedString("Юзернейм", comment: ""),
|
||||||
|
value: login
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
if let membership = membershipDescription {
|
||||||
|
rowDivider
|
||||||
|
infoRow(
|
||||||
|
icon: "calendar",
|
||||||
|
title: NSLocalizedString("На Yobble", comment: ""),
|
||||||
|
value: membership
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
if let status = presenceStatus, !status.isOnline {
|
||||||
|
rowDivider
|
||||||
|
infoRow(
|
||||||
|
icon: "clock",
|
||||||
|
title: NSLocalizedString("Последний визит", comment: ""),
|
||||||
|
value: status.text
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
rowDivider
|
||||||
|
infoRow(
|
||||||
|
icon: "lock.shield",
|
||||||
|
title: NSLocalizedString("Тип диалога", comment: ""),
|
||||||
|
value: chatTypeDescription
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private var mediaPreviewSection: some View {
|
||||||
|
section(
|
||||||
|
title: NSLocalizedString("Медиа, ссылки и файлы", comment: "Message profile media title"),
|
||||||
|
description: NSLocalizedString("Плитки как в Telegram — скоро здесь появятся вложения из чата.", comment: "Message profile media description")
|
||||||
|
) {
|
||||||
|
card {
|
||||||
|
LazyVGrid(columns: Array(repeating: GridItem(.flexible(), spacing: 8), count: 3), spacing: 8) {
|
||||||
|
ForEach(Array(sharedMediaPlaceholderIcons.enumerated()), id: \.offset) { index, icon in
|
||||||
|
RoundedRectangle(cornerRadius: 16, style: .continuous)
|
||||||
|
.fill(sharedMediaPlaceholderColors[index % sharedMediaPlaceholderColors.count])
|
||||||
|
.frame(height: 72)
|
||||||
|
.overlay(
|
||||||
|
Image(systemName: icon)
|
||||||
|
.font(.system(size: 20, weight: .semibold))
|
||||||
|
.foregroundColor(.white.opacity(0.9))
|
||||||
|
)
|
||||||
|
.overlay(
|
||||||
|
RoundedRectangle(cornerRadius: 16, style: .continuous)
|
||||||
|
.stroke(Color.white.opacity(0.18), lineWidth: 1)
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
.frame(maxWidth: .infinity)
|
||||||
|
|
||||||
|
Text(NSLocalizedString("История медиа синхронизируется. Как только появятся первые вложения, они покажутся здесь списком превью.", comment: "Message profile media footer"))
|
||||||
|
.font(.caption)
|
||||||
|
.foregroundColor(.secondary)
|
||||||
|
.padding(.top, 12)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private var chatSettingsSection: some View {
|
||||||
|
section(
|
||||||
|
title: NSLocalizedString("Настройки чата", comment: "Message profile chat settings title"),
|
||||||
|
description: NSLocalizedString("Тонкие настройки диалога по образцу профиля Telegram.", comment: "Message profile chat settings description")
|
||||||
|
) {
|
||||||
|
card {
|
||||||
|
VStack(spacing: 0) {
|
||||||
|
Toggle(isOn: $areNotificationsEnabled) {
|
||||||
|
VStack(alignment: .leading, spacing: 2) {
|
||||||
|
Text(NSLocalizedString("Уведомления", comment: "Message profile notifications title"))
|
||||||
|
Text(
|
||||||
|
areNotificationsEnabled
|
||||||
|
? NSLocalizedString("Push включены — приходят все новые сообщения.", comment: "Message profile notifications subtitle on")
|
||||||
|
: NSLocalizedString("Тишина включена. Чат не тревожит до включения сигнала.", comment: "Message profile notifications subtitle off")
|
||||||
|
)
|
||||||
|
.font(.caption)
|
||||||
|
.foregroundColor(.secondary)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
.toggleStyle(SwitchToggleStyle(tint: .accentColor))
|
||||||
|
.padding(.vertical, 4)
|
||||||
|
|
||||||
|
rowDivider
|
||||||
|
|
||||||
|
buttonRow(
|
||||||
|
icon: "timer",
|
||||||
|
title: NSLocalizedString("Автоудаление", comment: "Message profile auto delete title"),
|
||||||
|
subtitle: NSLocalizedString("Сообщения пока сохраняются навсегда.", comment: "Message profile auto delete subtitle"),
|
||||||
|
iconTint: .orange
|
||||||
|
) {
|
||||||
|
showPlaceholderAction(
|
||||||
|
title: NSLocalizedString("Автоудаление", comment: "Message profile auto delete alert title"),
|
||||||
|
message: NSLocalizedString("Режим автоудаления появится чуть позже. Мы добавим пресеты на 24 часа, 7 дней и 1 месяц — совсем как в Telegram.", comment: "Message profile auto delete alert message")
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
rowDivider
|
||||||
|
|
||||||
|
buttonRow(
|
||||||
|
icon: "text.bubble",
|
||||||
|
title: NSLocalizedString("Очистить историю", comment: "Message profile clear history title"),
|
||||||
|
subtitle: NSLocalizedString("Удалить переписку только для себя.", comment: "Message profile clear history subtitle"),
|
||||||
|
iconTint: .blue
|
||||||
|
) {
|
||||||
|
showPlaceholderAction(
|
||||||
|
title: NSLocalizedString("Очистка истории", comment: "Message profile clear history alert title"),
|
||||||
|
message: NSLocalizedString("Скоро можно будет очистить сообщения выборочно или целиком. Пока подготовим дизайн.", comment: "Message profile clear history alert message")
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private var contactActionsSection: some View {
|
||||||
|
section(
|
||||||
|
title: NSLocalizedString("Контакт", comment: "Message profile contact section title"),
|
||||||
|
description: NSLocalizedString("Управление карточкой собеседника.", comment: "Message profile contact section description")
|
||||||
|
) {
|
||||||
|
card {
|
||||||
|
VStack(spacing: 0) {
|
||||||
|
buttonRow(
|
||||||
|
icon: "person.badge.plus",
|
||||||
|
title: NSLocalizedString("Добавить в контакты", comment: "Message profile add to contacts title"),
|
||||||
|
subtitle: NSLocalizedString("Появится отдельная запись в адресной книге Yobble.", comment: "Message profile add to contacts subtitle"),
|
||||||
|
iconTint: .accentColor
|
||||||
|
) {
|
||||||
|
showPlaceholderAction(
|
||||||
|
title: NSLocalizedString("Добавить контакт", comment: "Message profile add contact alert title"),
|
||||||
|
message: NSLocalizedString("Редактор контактов скоро появится. Мы сохраним имя, телефон и заметку.", comment: "Message profile add contact alert message")
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
rowDivider
|
||||||
|
|
||||||
|
buttonRow(
|
||||||
|
icon: "paperplane.fill",
|
||||||
|
title: NSLocalizedString("Поделиться профилем", comment: "Message profile share contact title"),
|
||||||
|
subtitle: NSLocalizedString("Отправим ссылку или QR — как в Telegram.", comment: "Message profile share contact subtitle"),
|
||||||
|
iconTint: .purple
|
||||||
|
) {
|
||||||
|
showPlaceholderAction(
|
||||||
|
title: NSLocalizedString("Поделиться", comment: "Message profile share alert title"),
|
||||||
|
message: NSLocalizedString("Кнопка поделиться соберёт ссылку, QR и кнопку пересылки контакта.", comment: "Message profile share alert message")
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private var safetySection: some View {
|
||||||
|
section(
|
||||||
|
title: NSLocalizedString("Безопасность", comment: "Message profile safety section title"),
|
||||||
|
description: NSLocalizedString("Блокировка и жалобы доступны из профиля, как в Telegram.", comment: "Message profile safety section description")
|
||||||
|
) {
|
||||||
|
card {
|
||||||
|
VStack(spacing: 0) {
|
||||||
|
buttonRow(
|
||||||
|
icon: "hand.raised.slash.fill",
|
||||||
|
title: isBlockedByCurrentUser
|
||||||
|
? NSLocalizedString("Разблокировать", comment: "Message profile unblock title")
|
||||||
|
: NSLocalizedString("Заблокировать", comment: "Message profile block title"),
|
||||||
|
subtitle: isBlockedByCurrentUser
|
||||||
|
? NSLocalizedString("Пользователь снова сможет писать вам.", comment: "Message profile unblock subtitle")
|
||||||
|
: NSLocalizedString("Перестанет появляться в чате и не сможет писать.", comment: "Message profile block subtitle"),
|
||||||
|
iconTint: .red,
|
||||||
|
destructive: true
|
||||||
|
) {
|
||||||
|
let message = isBlockedByCurrentUser
|
||||||
|
? NSLocalizedString("Скоро появится разблокировка с подтверждением и синхронизацией.", comment: "Message profile unblock alert message")
|
||||||
|
: NSLocalizedString("Блокировка чата пока в дизайне. Готовим отдельный экран со статусом и жалобой.", comment: "Message profile block alert message")
|
||||||
|
showPlaceholderAction(
|
||||||
|
title: isBlockedByCurrentUser
|
||||||
|
? NSLocalizedString("Разблокировать", comment: "Message profile unblock alert title")
|
||||||
|
: NSLocalizedString("Заблокировать", comment: "Message profile block alert title"),
|
||||||
|
message: message
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
rowDivider
|
||||||
|
|
||||||
|
buttonRow(
|
||||||
|
icon: "exclamationmark.bubble.fill",
|
||||||
|
title: NSLocalizedString("Пожаловаться", comment: "Message profile report title"),
|
||||||
|
subtitle: NSLocalizedString("Сообщите о спаме или нарушении правил.", comment: "Message profile report subtitle"),
|
||||||
|
iconTint: .orange,
|
||||||
|
destructive: true
|
||||||
|
) {
|
||||||
|
showPlaceholderAction(
|
||||||
|
title: NSLocalizedString("Жалоба", comment: "Message profile report alert title"),
|
||||||
|
message: NSLocalizedString("Форма жалобы появится чуть позже — добавим прикрепление скриншотов и тип нарушения.", comment: "Message profile report alert message")
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private var footerHint: some View {
|
||||||
|
Text(NSLocalizedString("Мы постепенно повторяем знакомый паттерн Telegram, чтобы переход был комфортным. Укажите, что ещё ожидать на экране профиля — добавим приоритетно.", comment: "Message profile footer"))
|
||||||
|
.font(.footnote)
|
||||||
|
.foregroundColor(.secondary)
|
||||||
|
.multilineTextAlignment(.center)
|
||||||
|
.padding(.horizontal, 12)
|
||||||
|
.padding(.bottom, 24)
|
||||||
|
}
|
||||||
|
|
||||||
|
// MARK: - Helper Builders
|
||||||
|
|
||||||
|
private func section<Content: View>(title: String, description: String? = nil, @ViewBuilder content: () -> Content) -> some View {
|
||||||
|
VStack(alignment: .leading, spacing: 12) {
|
||||||
|
VStack(alignment: .leading, spacing: 4) {
|
||||||
|
Text(title)
|
||||||
|
.font(.headline)
|
||||||
|
if let description {
|
||||||
|
Text(description)
|
||||||
|
.font(.subheadline)
|
||||||
|
.foregroundColor(.secondary)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
content()
|
||||||
|
}
|
||||||
|
.frame(maxWidth: .infinity, alignment: .leading)
|
||||||
|
}
|
||||||
|
|
||||||
|
private func card<Content: View>(@ViewBuilder content: () -> Content) -> some View {
|
||||||
|
VStack(alignment: .leading, spacing: 16) {
|
||||||
|
content()
|
||||||
|
}
|
||||||
|
.padding(20)
|
||||||
|
.frame(maxWidth: .infinity, alignment: .leading)
|
||||||
|
.background(
|
||||||
|
RoundedRectangle(cornerRadius: 28, style: .continuous)
|
||||||
|
.fill(Color(UIColor.secondarySystemGroupedBackground))
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
private func infoRow(icon: String, title: String, value: String) -> some View {
|
||||||
|
HStack(alignment: .top, spacing: 12) {
|
||||||
|
iconBackground(color: .accentColor.opacity(0.18)) {
|
||||||
|
Image(systemName: icon)
|
||||||
|
.font(.system(size: 17, weight: .semibold))
|
||||||
|
.foregroundColor(.accentColor)
|
||||||
|
}
|
||||||
|
|
||||||
|
VStack(alignment: .leading, spacing: 4) {
|
||||||
|
Text(title)
|
||||||
|
.font(.caption)
|
||||||
|
.foregroundColor(.secondary)
|
||||||
|
Text(value)
|
||||||
|
.font(.body)
|
||||||
|
}
|
||||||
|
|
||||||
|
Spacer()
|
||||||
|
}
|
||||||
|
.padding(.vertical, 4)
|
||||||
|
}
|
||||||
|
|
||||||
|
private func buttonRow(
|
||||||
|
icon: String,
|
||||||
|
title: String,
|
||||||
|
subtitle: String? = nil,
|
||||||
|
iconTint: Color,
|
||||||
|
destructive: Bool = false,
|
||||||
|
action: @escaping () -> Void
|
||||||
|
) -> some View {
|
||||||
|
Button(action: action) {
|
||||||
|
HStack(spacing: 12) {
|
||||||
|
iconBackground(color: iconTint.opacity(0.15)) {
|
||||||
|
Image(systemName: icon)
|
||||||
|
.font(.system(size: 17, weight: .semibold))
|
||||||
|
.foregroundColor(iconTint)
|
||||||
|
}
|
||||||
|
|
||||||
|
VStack(alignment: .leading, spacing: 4) {
|
||||||
|
Text(title)
|
||||||
|
.font(.body)
|
||||||
|
.foregroundColor(destructive ? .red : .primary)
|
||||||
|
if let subtitle {
|
||||||
|
Text(subtitle)
|
||||||
|
.font(.caption)
|
||||||
.foregroundColor(.secondary)
|
.foregroundColor(.secondary)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
Text(NSLocalizedString("Профиль для сообщений пока в разработке.", comment: "Message profile placeholder title"))
|
Spacer()
|
||||||
.font(.body)
|
|
||||||
.multilineTextAlignment(.center)
|
|
||||||
|
|
||||||
Text(NSLocalizedString("Скоро здесь появится информация о собеседнике, статусе и дополнительных действиях.", comment: "Message profile placeholder description"))
|
Image(systemName: "chevron.right")
|
||||||
.font(.footnote)
|
.font(.system(size: 13, weight: .semibold))
|
||||||
.foregroundColor(.secondary)
|
.foregroundColor(Color(UIColor.tertiaryLabel))
|
||||||
.multilineTextAlignment(.center)
|
|
||||||
}
|
}
|
||||||
.padding(.horizontal, 24)
|
.padding(.vertical, 2)
|
||||||
.padding(.top, 60)
|
|
||||||
.frame(maxWidth: .infinity)
|
|
||||||
}
|
}
|
||||||
.background(Color(UIColor.systemBackground))
|
.buttonStyle(.plain)
|
||||||
.navigationTitle(NSLocalizedString("Профиль", comment: "Message profile placeholder nav title"))
|
|
||||||
.navigationBarTitleDisplayMode(.inline)
|
|
||||||
}
|
}
|
||||||
|
|
||||||
private var displayName: String {
|
private func iconBackground<Content: View>(color: Color, @ViewBuilder content: () -> Content) -> some View {
|
||||||
if let custom = trimmed(chat.chatData?.customName) {
|
RoundedRectangle(cornerRadius: 14, style: .continuous)
|
||||||
return custom
|
.fill(color)
|
||||||
|
.frame(width: 44, height: 44)
|
||||||
|
.overlay(content())
|
||||||
|
}
|
||||||
|
|
||||||
|
private var rowDivider: some View {
|
||||||
|
Rectangle()
|
||||||
|
.fill(Color(UIColor.separator).opacity(0.4))
|
||||||
|
.frame(height: 1)
|
||||||
|
.padding(.vertical, 8)
|
||||||
|
}
|
||||||
|
|
||||||
|
private func showPlaceholderAction(title: String, message: String) {
|
||||||
|
placeholderAlert = PlaceholderAlert(title: title, message: message)
|
||||||
|
}
|
||||||
|
|
||||||
|
// MARK: - Derived Data
|
||||||
|
|
||||||
|
private var profileBio: String? {
|
||||||
|
trimmed(chat.chatData?.bio)
|
||||||
|
}
|
||||||
|
|
||||||
|
private var membershipDescription: String? {
|
||||||
|
guard let createdAt = chat.chatData?.createdAt else { return nil }
|
||||||
|
let formatted = MessageProfileView.joinedFormatter.string(from: createdAt)
|
||||||
|
return String(
|
||||||
|
format: NSLocalizedString("На платформе с %@", comment: "Message profile joined format"),
|
||||||
|
formatted
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
private var chatTypeDescription: String {
|
||||||
|
switch chat.chatType {
|
||||||
|
case .self:
|
||||||
|
return NSLocalizedString("Избранное", comment: "Message profile self chat type")
|
||||||
|
case .privateChat:
|
||||||
|
return NSLocalizedString("Личный чат", comment: "Message profile private chat type")
|
||||||
|
case .unknown:
|
||||||
|
fallthrough
|
||||||
|
default:
|
||||||
|
return NSLocalizedString("Приватный диалог", comment: "Message profile default chat type")
|
||||||
}
|
}
|
||||||
if let full = trimmed(chat.chatData?.fullName) {
|
|
||||||
return full
|
|
||||||
}
|
|
||||||
if let login = trimmed(chat.chatData?.login) {
|
|
||||||
return "@\(login)"
|
|
||||||
}
|
|
||||||
return NSLocalizedString("Неизвестный пользователь", comment: "Message profile fallback title")
|
|
||||||
}
|
}
|
||||||
|
|
||||||
private var loginDisplay: String? {
|
private var presenceStatus: PresenceStatus? {
|
||||||
guard let login = trimmed(chat.chatData?.login) else { return nil }
|
|
||||||
return "@\(login)"
|
|
||||||
}
|
|
||||||
|
|
||||||
private var isDeletedUser: Bool {
|
|
||||||
trimmed(chat.chatData?.login) == nil
|
|
||||||
}
|
|
||||||
|
|
||||||
private var isOfficial: Bool {
|
|
||||||
chat.chatData?.isOfficial ?? false
|
|
||||||
}
|
|
||||||
|
|
||||||
private var avatarBackgroundColor: Color {
|
|
||||||
if isDeletedUser {
|
if isDeletedUser {
|
||||||
return Color(.systemGray5)
|
return PresenceStatus(
|
||||||
|
text: NSLocalizedString("Пользователь удалён", comment: "Message profile deleted user status"),
|
||||||
|
isOnline: false
|
||||||
|
)
|
||||||
}
|
}
|
||||||
return isOfficial ? Color.accentColor.opacity(0.85) : Color.accentColor.opacity(0.15)
|
|
||||||
|
guard let lastSeen = chat.chatData?.lastSeen else { return nil }
|
||||||
|
let lastSeenDate = Date(timeIntervalSince1970: TimeInterval(lastSeen))
|
||||||
|
let interval = Date().timeIntervalSince(lastSeenDate)
|
||||||
|
|
||||||
|
if interval < 5 * 60 {
|
||||||
|
return PresenceStatus(
|
||||||
|
text: NSLocalizedString("в сети", comment: "Message profile online status"),
|
||||||
|
isOnline: true
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
let relative = MessageProfileView.relativeFormatter.localizedString(for: lastSeenDate, relativeTo: Date())
|
||||||
|
return PresenceStatus(
|
||||||
|
text: String(
|
||||||
|
format: NSLocalizedString("был(а) %@", comment: "Message profile last seen relative format"),
|
||||||
|
relative
|
||||||
|
),
|
||||||
|
isOnline: false
|
||||||
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
private var avatarTextColor: Color {
|
private var statusTags: [StatusTag] {
|
||||||
if isDeletedUser {
|
var tags: [StatusTag] = []
|
||||||
return Color.accentColor
|
|
||||||
}
|
|
||||||
return isOfficial ? Color.white : Color.accentColor
|
|
||||||
}
|
|
||||||
|
|
||||||
private var avatarInitial: String {
|
if isOfficial {
|
||||||
if let name = trimmed(chat.chatData?.customName) ?? trimmed(chat.chatData?.fullName) {
|
tags.append(
|
||||||
let components = name.split(separator: " ")
|
StatusTag(
|
||||||
let initials = components.prefix(2).compactMap { $0.first }
|
icon: "checkmark.seal.fill",
|
||||||
if !initials.isEmpty {
|
text: NSLocalizedString("Подтверждённый профиль", comment: "Message profile verified tag"),
|
||||||
return initials.map { String($0) }.joined().uppercased()
|
background: Color.white.opacity(0.18),
|
||||||
|
tint: .white
|
||||||
|
)
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
if let relationship = chat.chatData?.relationship {
|
||||||
|
if relationship.isCurrentUserInContactsOfTarget {
|
||||||
|
tags.append(
|
||||||
|
StatusTag(
|
||||||
|
icon: "person.2.fill",
|
||||||
|
text: NSLocalizedString("Вы в его контактах", comment: "Message profile contact tag"),
|
||||||
|
background: Color.white.opacity(0.14),
|
||||||
|
tint: .white
|
||||||
|
)
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
if relationship.isCurrentUserInBlacklistOfTarget {
|
||||||
|
tags.append(
|
||||||
|
StatusTag(
|
||||||
|
icon: "hand.thumbsdown.fill",
|
||||||
|
text: NSLocalizedString("Вы в его чёрном списке", comment: "Message profile blacklist tag"),
|
||||||
|
background: Color.red.opacity(0.4),
|
||||||
|
tint: .white
|
||||||
|
)
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
if relationship.isTargetUserBlockedByCurrentUser {
|
||||||
|
tags.append(
|
||||||
|
StatusTag(
|
||||||
|
icon: "hand.raised.slash.fill",
|
||||||
|
text: NSLocalizedString("Заблокирован", comment: "Message profile blocked tag"),
|
||||||
|
background: Color.orange.opacity(0.4),
|
||||||
|
tint: .white
|
||||||
|
)
|
||||||
|
)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
if let login = trimmed(chat.chatData?.login) {
|
if isDeletedUser {
|
||||||
return String(login.prefix(1)).uppercased()
|
tags.append(
|
||||||
|
StatusTag(
|
||||||
|
icon: "person.crop.circle.badge.xmark",
|
||||||
|
text: NSLocalizedString("Аккаунт удалён", comment: "Message profile deleted tag"),
|
||||||
|
background: Color.white.opacity(0.12),
|
||||||
|
tint: .white
|
||||||
|
)
|
||||||
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
return "?"
|
return tags
|
||||||
|
}
|
||||||
|
|
||||||
|
private var sharedMediaPlaceholderIcons: [String] {
|
||||||
|
[
|
||||||
|
"photo.on.rectangle",
|
||||||
|
"doc.text.fill",
|
||||||
|
"link",
|
||||||
|
"paperclip",
|
||||||
|
"music.note",
|
||||||
|
"video.fill"
|
||||||
|
]
|
||||||
|
}
|
||||||
|
|
||||||
|
private var sharedMediaPlaceholderColors: [Color] {
|
||||||
|
[
|
||||||
|
Color.accentColor.opacity(0.8),
|
||||||
|
Color.purple.opacity(0.8),
|
||||||
|
Color.blue.opacity(0.7),
|
||||||
|
Color.orange.opacity(0.8),
|
||||||
|
Color.green.opacity(0.7),
|
||||||
|
Color.pink.opacity(0.8)
|
||||||
|
]
|
||||||
|
}
|
||||||
|
|
||||||
|
private var quickActionItems: [ProfileQuickAction] {
|
||||||
|
[
|
||||||
|
ProfileQuickAction(
|
||||||
|
icon: "phone.fill",
|
||||||
|
title: NSLocalizedString("Звонок", comment: "Message profile call action"),
|
||||||
|
description: NSLocalizedString("Голосовые звонки пока недоступны. Как только включим WebRTC, кнопка оживёт.", comment: "Message profile call action description"),
|
||||||
|
tint: .green
|
||||||
|
),
|
||||||
|
ProfileQuickAction(
|
||||||
|
icon: "video.fill",
|
||||||
|
title: NSLocalizedString("Видео", comment: "Message profile video action"),
|
||||||
|
description: NSLocalizedString("Видео созвоны появятся вместе с звонками. Интерфейс повторит Telegram.", comment: "Message profile video action description"),
|
||||||
|
tint: .purple
|
||||||
|
),
|
||||||
|
ProfileQuickAction(
|
||||||
|
icon: "magnifyingglass",
|
||||||
|
title: NSLocalizedString("Поиск", comment: "Message profile search action"),
|
||||||
|
description: NSLocalizedString("Скоро можно будет искать сообщения, ссылки и файлы в этом чате.", comment: "Message profile search action description"),
|
||||||
|
tint: .blue
|
||||||
|
),
|
||||||
|
ProfileQuickAction(
|
||||||
|
icon: "bell.slash.fill",
|
||||||
|
title: NSLocalizedString("Тишина", comment: "Message profile mute action"),
|
||||||
|
description: NSLocalizedString("Появится мутация на 1 час, 1 день или навсегда.", comment: "Message profile mute action description"),
|
||||||
|
tint: .orange
|
||||||
|
)
|
||||||
|
]
|
||||||
|
}
|
||||||
|
|
||||||
|
private var isBlockedByCurrentUser: Bool {
|
||||||
|
chat.chatData?.relationship?.isTargetUserBlockedByCurrentUser ?? false
|
||||||
}
|
}
|
||||||
|
|
||||||
private var avatarUrl: URL? {
|
private var avatarUrl: URL? {
|
||||||
@ -150,10 +732,108 @@ struct MessageProfileView: View {
|
|||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private var avatarBackgroundColor: Color {
|
||||||
|
if isDeletedUser {
|
||||||
|
return Color(.systemGray5)
|
||||||
|
}
|
||||||
|
return isOfficial ? Color.accentColor.opacity(0.85) : Color.accentColor.opacity(0.15)
|
||||||
|
}
|
||||||
|
|
||||||
|
private var avatarTextColor: Color {
|
||||||
|
if isDeletedUser {
|
||||||
|
return Color.accentColor
|
||||||
|
}
|
||||||
|
return isOfficial ? Color.white : Color.accentColor
|
||||||
|
}
|
||||||
|
|
||||||
|
private var avatarInitial: String {
|
||||||
|
if let name = trimmed(chat.chatData?.customName) ?? trimmed(chat.chatData?.fullName) {
|
||||||
|
let components = name.split(separator: " ")
|
||||||
|
let initials = components.prefix(2).compactMap { $0.first }
|
||||||
|
if !initials.isEmpty {
|
||||||
|
return initials.map { String($0) }.joined().uppercased()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if let login = trimmed(chat.chatData?.login) {
|
||||||
|
return String(login.prefix(1)).uppercased()
|
||||||
|
}
|
||||||
|
|
||||||
|
return "?"
|
||||||
|
}
|
||||||
|
|
||||||
private func trimmed(_ text: String?) -> String? {
|
private func trimmed(_ text: String?) -> String? {
|
||||||
guard let text = text?.trimmingCharacters(in: .whitespacesAndNewlines), !text.isEmpty else {
|
guard let text = text?.trimmingCharacters(in: .whitespacesAndNewlines), !text.isEmpty else {
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
return text
|
return text
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private var displayName: String {
|
||||||
|
if let custom = trimmed(chat.chatData?.customName) {
|
||||||
|
return custom
|
||||||
|
}
|
||||||
|
if let full = trimmed(chat.chatData?.fullName) {
|
||||||
|
return full
|
||||||
|
}
|
||||||
|
if let login = trimmed(chat.chatData?.login) {
|
||||||
|
return "@\(login)"
|
||||||
|
}
|
||||||
|
return NSLocalizedString("Неизвестный пользователь", comment: "Message profile fallback title")
|
||||||
|
}
|
||||||
|
|
||||||
|
private var loginDisplay: String? {
|
||||||
|
guard let login = trimmed(chat.chatData?.login) else { return nil }
|
||||||
|
return "@\(login)"
|
||||||
|
}
|
||||||
|
|
||||||
|
private var isDeletedUser: Bool {
|
||||||
|
trimmed(chat.chatData?.login) == nil
|
||||||
|
}
|
||||||
|
|
||||||
|
private var isOfficial: Bool {
|
||||||
|
chat.chatData?.isOfficial ?? false
|
||||||
|
}
|
||||||
|
|
||||||
|
// MARK: - Formatters & Models
|
||||||
|
|
||||||
|
private static let relativeFormatter: RelativeDateTimeFormatter = {
|
||||||
|
let formatter = RelativeDateTimeFormatter()
|
||||||
|
formatter.unitsStyle = .full
|
||||||
|
return formatter
|
||||||
|
}()
|
||||||
|
|
||||||
|
private static let joinedFormatter: DateFormatter = {
|
||||||
|
let formatter = DateFormatter()
|
||||||
|
formatter.dateStyle = .long
|
||||||
|
formatter.timeStyle = .none
|
||||||
|
return formatter
|
||||||
|
}()
|
||||||
|
}
|
||||||
|
|
||||||
|
private struct PresenceStatus {
|
||||||
|
let text: String
|
||||||
|
let isOnline: Bool
|
||||||
|
}
|
||||||
|
|
||||||
|
private struct StatusTag: Identifiable {
|
||||||
|
let id = UUID()
|
||||||
|
let icon: String
|
||||||
|
let text: String
|
||||||
|
let background: Color
|
||||||
|
let tint: Color
|
||||||
|
}
|
||||||
|
|
||||||
|
private struct ProfileQuickAction: Identifiable {
|
||||||
|
let id = UUID()
|
||||||
|
let icon: String
|
||||||
|
let title: String
|
||||||
|
let description: String
|
||||||
|
let tint: Color
|
||||||
|
}
|
||||||
|
|
||||||
|
private struct PlaceholderAlert: Identifiable {
|
||||||
|
let id = UUID()
|
||||||
|
let title: String
|
||||||
|
let message: String
|
||||||
}
|
}
|
||||||
|
|||||||
Loading…
x
Reference in New Issue
Block a user