лучшен экран добавления стикеров: добавлена возможность выбора нескольких стикеров, выбор эмодзи прямо на стикере, кнопка добавления прикреплена к футеру

This commit is contained in:
kazachilo 2025-03-17 16:41:17 +03:00
parent f691b6e3de
commit 12737caa1c
2 changed files with 201 additions and 95 deletions

View File

@ -5,6 +5,7 @@
padding: calc(3rem + var(--spacing-small)) var(--spacing-medium) var(--spacing-large); padding: calc(3rem + var(--spacing-small)) var(--spacing-medium) var(--spacing-large);
width: 100%; width: 100%;
box-sizing: border-box; box-sizing: border-box;
position: relative; /* Добавляем для позиционирования фиксированной кнопки */
} }
.header { .header {
@ -37,12 +38,15 @@
} }
.content { .content {
position: relative;
display: flex; display: flex;
flex-direction: column; flex-direction: column;
gap: var(--spacing-large); gap: var(--spacing-large);
background-color: var(--color-surface); background-color: var(--color-surface);
border-radius: var(--border-radius); border-radius: var(--border-radius);
padding: var(--spacing-large); padding: var(--spacing-large);
min-height: 100vh; /* Обеспечиваем, чтобы контент занимал как минимум всю высоту экрана */
padding-bottom: calc(7rem + 80px + var(--safe-area-inset-bottom)); /* Отступ для кнопки */
} }
.section { .section {
@ -74,6 +78,7 @@
transition: transform 0.2s, border-color 0.2s; transition: transform 0.2s, border-color 0.2s;
} }
.imageItem:hover { .imageItem:hover {
transform: translateY(-2px); transform: translateY(-2px);
} }
@ -89,46 +94,65 @@
} }
.emojiSelector { .emojiSelector {
display: flex; position: absolute;
flex-direction: column; bottom: var(--spacing-small);
align-items: center; right: var(--spacing-small);
gap: var(--spacing-small); background-color: rgba(0, 0, 0, 0.7);
padding: var(--spacing-medium);
background-color: var(--color-background);
border-radius: var(--border-radius); border-radius: var(--border-radius);
padding: 2px;
} }
.emojiInput { .emojiButton {
width: 5rem; width: 2.5rem;
height: 3rem; height: 2.5rem;
background: none; background: none;
border: 1px solid var(--color-border); border: none;
border-radius: var(--border-radius); color: white;
color: var(--color-text); font-size: 1.2rem;
font-size: 2rem;
text-align: center; text-align: center;
cursor: pointer;
} }
.emojiHelp { @supports (-webkit-touch-callout: none) {
font-size: 0.9rem; /* Стили только для iOS устройств */
color: var(--color-text); .emojiSelector {
opacity: 0.7; display: flex;
text-align: center; justify-content: center;
align-items: center;
}
.emojiButton {
display: flex;
justify-content: center;
align-items: center;
padding: 0;
line-height: 1; /* Важно для вертикального центрирования текста на iOS */
-webkit-appearance: none;
-webkit-tap-highlight-color: transparent;
}
} }
.actions { /* Стили для контейнера кнопки */
display: flex; .addButtonContainer {
justify-content: center; position: fixed;
margin-top: var(--spacing-medium); bottom: calc(6rem + var(--safe-area-inset-bottom)); /* Отступ от навигации */
left: var(--spacing-medium);
right: var(--spacing-medium);
width: auto;
max-width: calc(28rem - 2 * var(--spacing-medium));
margin: 0 auto;
z-index: 90;
} }
.addButton { .addButton {
padding: var(--spacing-small) var(--spacing-large); width: 100%;
padding: calc(var(--spacing-small) * 1.5) var(--spacing-large); /* Увеличенный вертикальный padding для толщины */
background-color: var(--color-primary); background-color: var(--color-primary);
color: white; color: white;
border: none; border: none;
border-radius: var(--border-radius); border-radius: var(--border-radius);
font-weight: 500; font-weight: 500;
font-size: 1.1rem; /* Увеличенный размер шрифта */
cursor: pointer; cursor: pointer;
transition: transform 0.2s; transition: transform 0.2s;
} }

View File

@ -5,17 +5,28 @@ import { stickerService } from '../services/stickerService';
import apiService from '../services/api'; import apiService from '../services/api';
import { GeneratedImage } from '../types/api'; import { GeneratedImage } from '../types/api';
import { getCurrentUserId } from '../constants/user'; import { getCurrentUserId } from '../constants/user';
import EmojiPickerModal from '../components/shared/EmojiPickerModal';
import ValidationModal from '../components/shared/ValidationModal';
const AddStickerToPackScreen: React.FC = () => { const AddStickerToPackScreen: React.FC = () => {
const navigate = useNavigate(); const navigate = useNavigate();
const { packName } = useParams<{ packName: string }>(); const { packName } = useParams<{ packName: string }>();
const [selectedImage, setSelectedImage] = useState<GeneratedImage | null>(null); const [selectedImages, setSelectedImages] = useState<GeneratedImage[]>([]);
const [emoji, setEmoji] = useState('😊'); const [emojis, setEmojis] = useState<string[]>([]);
const [availableImages, setAvailableImages] = useState<GeneratedImage[]>([]); const [availableImages, setAvailableImages] = useState<GeneratedImage[]>([]);
const [loading, setLoading] = useState(true); const [loading, setLoading] = useState(true);
const [adding, setAdding] = useState(false); const [adding, setAdding] = useState(false);
const [error, setError] = useState<string | null>(null); const [error, setError] = useState<string | null>(null);
const [packTitle, setPackTitle] = useState(''); const [packTitle, setPackTitle] = useState('');
// Состояния для модального окна эмодзи
const [isEmojiPickerVisible, setIsEmojiPickerVisible] = useState(false);
const [activeEmojiIndex, setActiveEmojiIndex] = useState<number | null>(null);
// Состояния для модального окна валидации
const [validationTitle, setValidationTitle] = useState<string>('');
const [validationMessage, setValidationMessage] = useState<string>('');
const [isValidationModalVisible, setIsValidationModalVisible] = useState<boolean>(false);
// Загрузка доступных изображений и информации о стикерпаке // Загрузка доступных изображений и информации о стикерпаке
useEffect(() => { useEffect(() => {
@ -45,25 +56,74 @@ const AddStickerToPackScreen: React.FC = () => {
fetchData(); fetchData();
}, [packName]); }, [packName]);
// Обработчик выбора изображения // Функция для открытия пикера эмодзи
const handleImageSelect = (image: GeneratedImage) => { const openEmojiPicker = (index: number, e: React.MouseEvent) => {
setSelectedImage(image); e.stopPropagation();
setActiveEmojiIndex(index);
setIsEmojiPickerVisible(true);
}; };
// Обработчик добавления стикера // Функция для закрытия пикера эмодзи без выбора
const handleAddSticker = async () => { const handleCancelEmojiPicker = () => {
if (!selectedImage) { setIsEmojiPickerVisible(false);
setError('Выберите изображение для стикера'); setActiveEmojiIndex(null);
return; };
}
if (!emoji.trim()) { // Функция для выбора эмодзи
setError('Введите эмодзи для стикера'); const handleSelectEmoji = (emoji: string) => {
if (activeEmojiIndex !== null) {
handleEmojiChange(activeEmojiIndex, emoji);
}
setIsEmojiPickerVisible(false);
setActiveEmojiIndex(null);
};
// Обработчик изменения эмодзи
const handleEmojiChange = (index: number, emoji: string) => {
setEmojis(prev => {
const newEmojis = [...prev];
newEmojis[index] = emoji;
return newEmojis;
});
};
// Обработчик закрытия модального окна валидации
const handleValidationModalClose = () => {
setIsValidationModalVisible(false);
};
// Обработчик выбора изображения
const handleImageSelect = (image: GeneratedImage) => {
// Проверяем, выбрано ли уже изображение
const isSelected = selectedImages.some(img => img.id === image.id);
if (isSelected) {
// Если изображение уже выбрано, удаляем его из выбранных
setSelectedImages(prev => prev.filter(img => img.id !== image.id));
setEmojis(prev => {
const index = selectedImages.findIndex(img => img.id === image.id);
return prev.filter((_, i) => i !== index);
});
} else {
// Если изображение не выбрано, добавляем его в выбранные
setSelectedImages(prev => [...prev, image]);
setEmojis(prev => [...prev, '😊']); // Добавляем эмодзи по умолчанию
}
};
// Обработчик добавления стикеров
const handleAddStickers = async () => {
if (selectedImages.length === 0) {
setValidationTitle('Стикеры не выбраны');
setValidationMessage('Пожалуйста, выберите хотя бы одно изображение для добавления в стикерпак.');
setIsValidationModalVisible(true);
return; return;
} }
if (!packName) { if (!packName) {
setError('Не указано имя стикерпака'); setValidationTitle('Ошибка');
setValidationMessage('Не указано имя стикерпака.');
setIsValidationModalVisible(true);
return; return;
} }
@ -71,27 +131,34 @@ const AddStickerToPackScreen: React.FC = () => {
setAdding(true); setAdding(true);
setError(null); setError(null);
// Проверяем, что link существует и является строкой // Добавляем каждый стикер по очереди
if (typeof selectedImage.link !== 'string') { for (let i = 0; i < selectedImages.length; i++) {
console.error('Некорректный формат link:', selectedImage.link); const image = selectedImages[i];
setError('Некорректный формат изображения'); const emoji = emojis[i] || '😊';
return;
}
// Добавляем стикер в стикерпак, используя file_id изображения // Проверяем, что link существует и является строкой
await stickerService.addStickerToPack( if (typeof image.link !== 'string') {
packName, console.error('Некорректный формат link:', image.link);
getCurrentUserId().toString(), continue;
selectedImage.link, }
emoji,
packTitle // Передаем заголовок стикерпака // Добавляем стикер в стикерпак
); await stickerService.addStickerToPack(
packName,
getCurrentUserId().toString(),
image.link,
emoji,
packTitle
);
}
// Возвращаемся на страницу стикерпаков // Возвращаемся на страницу стикерпаков
navigate('/packs'); navigate('/packs');
} catch (err) { } catch (err) {
console.error('Ошибка при добавлении стикера:', err); console.error('Ошибка при добавлении стикеров:', err);
setError('Не удалось добавить стикер'); setValidationTitle('Ошибка');
setValidationMessage('Не удалось добавить стикеры в стикерпак. Пожалуйста, попробуйте еще раз.');
setIsValidationModalVisible(true);
} finally { } finally {
setAdding(false); setAdding(false);
} }
@ -107,7 +174,7 @@ const AddStickerToPackScreen: React.FC = () => {
Назад Назад
</button> </button>
<h1 className={styles.title}> <h1 className={styles.title}>
Добавление стикера в "{packTitle || packName}" Добавление стикеров в "{packTitle || packName}"
</h1> </h1>
</div> </div>
@ -127,7 +194,7 @@ const AddStickerToPackScreen: React.FC = () => {
{!loading && ( {!loading && (
<> <>
<div className={styles.section}> <div className={styles.section}>
<h2 className={styles.sectionTitle}>1. Выберите изображение</h2> <h2 className={styles.sectionTitle}>Выберите изображения для стикеров</h2>
{availableImages.length === 0 ? ( {availableImages.length === 0 ? (
<p className={styles.noImages}> <p className={styles.noImages}>
@ -136,52 +203,67 @@ const AddStickerToPackScreen: React.FC = () => {
</p> </p>
) : ( ) : (
<div className={styles.imagesGrid}> <div className={styles.imagesGrid}>
{availableImages.map((image, index) => ( {availableImages.map((image, index) => {
<div const isSelected = selectedImages.some(img => img.id === image.id);
key={index} const selectedIndex = selectedImages.findIndex(img => img.id === image.id);
className={`${styles.imageItem} ${selectedImage?.id === image.id ? styles.selected : ''}`}
onClick={() => handleImageSelect(image)} return (
> <div
<img key={index}
src={image.url || ''} className={`${styles.imageItem} ${isSelected ? styles.selected : ''}`}
alt={`Изображение ${index + 1}`} onClick={() => handleImageSelect(image)}
className={styles.image} >
/> <img
</div> src={image.url || ''}
))} alt={`Изображение ${index + 1}`}
className={styles.image}
/>
{isSelected && (
<div className={styles.emojiSelector}>
<button
className={styles.emojiButton}
onClick={(e) => openEmojiPicker(selectedIndex, e)}
>
{emojis[selectedIndex] || '😊'}
</button>
</div>
)}
</div>
);
})}
</div> </div>
)} )}
</div> </div>
<div className={styles.section}>
<h2 className={styles.sectionTitle}>2. Выберите эмодзи</h2>
<div className={styles.emojiSelector}>
<input
type="text"
value={emoji}
onChange={(e) => setEmoji(e.target.value)}
className={styles.emojiInput}
maxLength={2}
placeholder="😊"
/>
<p className={styles.emojiHelp}>
Введите один или два эмодзи, которые будут ассоциироваться со стикером
</p>
</div>
</div>
<div className={styles.actions}>
<button
className={styles.addButton}
onClick={handleAddSticker}
disabled={adding || !selectedImage}
>
{adding ? 'Добавление...' : 'Добавить стикер'}
</button>
</div>
</> </>
)} )}
</div> </div>
<div className={styles.addButtonContainer}>
<button
className={styles.addButton}
onClick={handleAddStickers}
disabled={adding}
>
{adding ? 'Добавление...' : 'Добавить стикеры'}
</button>
</div>
{/* Модальное окно выбора эмодзи */}
<EmojiPickerModal
isVisible={isEmojiPickerVisible && activeEmojiIndex !== null}
onCancel={handleCancelEmojiPicker}
onSelect={handleSelectEmoji}
initialEmoji={activeEmojiIndex !== null ? (emojis[activeEmojiIndex] || '😊') : '😊'}
/>
{/* Модальное окно валидации */}
<ValidationModal
isVisible={isValidationModalVisible}
title={validationTitle}
message={validationMessage}
onContinue={handleValidationModalClose}
/>
</div> </div>
); );
}; };