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

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

View File

@ -5,18 +5,29 @@ import { stickerService } from '../services/stickerService';
import apiService from '../services/api';
import { GeneratedImage } from '../types/api';
import { getCurrentUserId } from '../constants/user';
import EmojiPickerModal from '../components/shared/EmojiPickerModal';
import ValidationModal from '../components/shared/ValidationModal';
const AddStickerToPackScreen: React.FC = () => {
const navigate = useNavigate();
const { packName } = useParams<{ packName: string }>();
const [selectedImage, setSelectedImage] = useState<GeneratedImage | null>(null);
const [emoji, setEmoji] = useState('😊');
const [selectedImages, setSelectedImages] = useState<GeneratedImage[]>([]);
const [emojis, setEmojis] = useState<string[]>([]);
const [availableImages, setAvailableImages] = useState<GeneratedImage[]>([]);
const [loading, setLoading] = useState(true);
const [adding, setAdding] = useState(false);
const [error, setError] = useState<string | null>(null);
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(() => {
const fetchData = async () => {
@ -45,25 +56,74 @@ const AddStickerToPackScreen: React.FC = () => {
fetchData();
}, [packName]);
// Обработчик выбора изображения
const handleImageSelect = (image: GeneratedImage) => {
setSelectedImage(image);
// Функция для открытия пикера эмодзи
const openEmojiPicker = (index: number, e: React.MouseEvent) => {
e.stopPropagation();
setActiveEmojiIndex(index);
setIsEmojiPickerVisible(true);
};
// Обработчик добавления стикера
const handleAddSticker = async () => {
if (!selectedImage) {
setError('Выберите изображение для стикера');
return;
}
// Функция для закрытия пикера эмодзи без выбора
const handleCancelEmojiPicker = () => {
setIsEmojiPickerVisible(false);
setActiveEmojiIndex(null);
};
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;
}
if (!packName) {
setError('Не указано имя стикерпака');
setValidationTitle('Ошибка');
setValidationMessage('Не указано имя стикерпака.');
setIsValidationModalVisible(true);
return;
}
@ -71,27 +131,34 @@ const AddStickerToPackScreen: React.FC = () => {
setAdding(true);
setError(null);
// Проверяем, что link существует и является строкой
if (typeof selectedImage.link !== 'string') {
console.error('Некорректный формат link:', selectedImage.link);
setError('Некорректный формат изображения');
return;
}
// Добавляем каждый стикер по очереди
for (let i = 0; i < selectedImages.length; i++) {
const image = selectedImages[i];
const emoji = emojis[i] || '😊';
// Добавляем стикер в стикерпак, используя file_id изображения
await stickerService.addStickerToPack(
packName,
getCurrentUserId().toString(),
selectedImage.link,
emoji,
packTitle // Передаем заголовок стикерпака
);
// Проверяем, что link существует и является строкой
if (typeof image.link !== 'string') {
console.error('Некорректный формат link:', image.link);
continue;
}
// Добавляем стикер в стикерпак
await stickerService.addStickerToPack(
packName,
getCurrentUserId().toString(),
image.link,
emoji,
packTitle
);
}
// Возвращаемся на страницу стикерпаков
navigate('/packs');
} catch (err) {
console.error('Ошибка при добавлении стикера:', err);
setError('Не удалось добавить стикер');
console.error('Ошибка при добавлении стикеров:', err);
setValidationTitle('Ошибка');
setValidationMessage('Не удалось добавить стикеры в стикерпак. Пожалуйста, попробуйте еще раз.');
setIsValidationModalVisible(true);
} finally {
setAdding(false);
}
@ -107,7 +174,7 @@ const AddStickerToPackScreen: React.FC = () => {
Назад
</button>
<h1 className={styles.title}>
Добавление стикера в "{packTitle || packName}"
Добавление стикеров в "{packTitle || packName}"
</h1>
</div>
@ -127,7 +194,7 @@ const AddStickerToPackScreen: React.FC = () => {
{!loading && (
<>
<div className={styles.section}>
<h2 className={styles.sectionTitle}>1. Выберите изображение</h2>
<h2 className={styles.sectionTitle}>Выберите изображения для стикеров</h2>
{availableImages.length === 0 ? (
<p className={styles.noImages}>
@ -136,52 +203,67 @@ const AddStickerToPackScreen: React.FC = () => {
</p>
) : (
<div className={styles.imagesGrid}>
{availableImages.map((image, index) => (
<div
key={index}
className={`${styles.imageItem} ${selectedImage?.id === image.id ? styles.selected : ''}`}
onClick={() => handleImageSelect(image)}
>
<img
src={image.url || ''}
alt={`Изображение ${index + 1}`}
className={styles.image}
/>
</div>
))}
{availableImages.map((image, index) => {
const isSelected = selectedImages.some(img => img.id === image.id);
const selectedIndex = selectedImages.findIndex(img => img.id === image.id);
return (
<div
key={index}
className={`${styles.imageItem} ${isSelected ? styles.selected : ''}`}
onClick={() => handleImageSelect(image)}
>
<img
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 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 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>
);
};