лучшен экран добавления стикеров: добавлена возможность выбора нескольких стикеров, выбор эмодзи прямо на стикере, кнопка добавления прикреплена к футеру
This commit is contained in:
parent
f691b6e3de
commit
12737caa1c
@ -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;
|
||||
}
|
||||
|
||||
@ -5,17 +5,28 @@ 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(() => {
|
||||
@ -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>
|
||||
);
|
||||
};
|
||||
|
||||
Loading…
Reference in New Issue
Block a user