лучшен экран добавления стикеров: добавлена возможность выбора нескольких стикеров, выбор эмодзи прямо на стикере, кнопка добавления прикреплена к футеру
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);
|
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;
|
||||||
}
|
}
|
||||||
|
|||||||
@ -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>
|
||||||
);
|
);
|
||||||
};
|
};
|
||||||
|
|||||||
Loading…
Reference in New Issue
Block a user