Implement dark theme and comprehensive profile settings with working functionality
This commit is contained in:
@ -36,4 +36,22 @@ export class UsersResolver {
|
|||||||
) {
|
) {
|
||||||
return this.usersService.update(user.id, { bio, avatar });
|
return this.usersService.update(user.id, { bio, avatar });
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Mutation(() => User)
|
||||||
|
@UseGuards(GqlAuthGuard)
|
||||||
|
updateProfile(
|
||||||
|
@CurrentUser() user: User,
|
||||||
|
@Args('bio') bio: string,
|
||||||
|
) {
|
||||||
|
return this.usersService.update(user.id, { bio });
|
||||||
|
}
|
||||||
|
|
||||||
|
@Mutation(() => User)
|
||||||
|
@UseGuards(GqlAuthGuard)
|
||||||
|
updateOnlineStatus(
|
||||||
|
@CurrentUser() user: User,
|
||||||
|
@Args('isOnline') isOnline: boolean,
|
||||||
|
) {
|
||||||
|
return this.usersService.updateOnlineStatus(user.id, isOnline);
|
||||||
|
}
|
||||||
}
|
}
|
@ -55,10 +55,11 @@ export class UsersService {
|
|||||||
return this.findOne(id);
|
return this.findOne(id);
|
||||||
}
|
}
|
||||||
|
|
||||||
async updateOnlineStatus(id: string, isOnline: boolean): Promise<void> {
|
async updateOnlineStatus(id: string, isOnline: boolean): Promise<User> {
|
||||||
await this.usersRepository.update(id, {
|
await this.usersRepository.update(id, {
|
||||||
isOnline,
|
isOnline,
|
||||||
lastSeen: isOnline ? undefined : new Date(),
|
lastSeen: isOnline ? undefined : new Date(),
|
||||||
});
|
});
|
||||||
|
return this.findOne(id);
|
||||||
}
|
}
|
||||||
}
|
}
|
@ -1,40 +1,12 @@
|
|||||||
import React from 'react';
|
import React from 'react';
|
||||||
import { StatusBar } from 'expo-status-bar';
|
import { StatusBar } from 'expo-status-bar';
|
||||||
import { ApolloProvider } from '@apollo/client';
|
import { ApolloProvider } from '@apollo/client';
|
||||||
import { Provider as PaperProvider, MD3DarkTheme, configureFonts } from 'react-native-paper';
|
import { Provider as PaperProvider } from 'react-native-paper';
|
||||||
import { SafeAreaProvider } from 'react-native-safe-area-context';
|
import { SafeAreaProvider } from 'react-native-safe-area-context';
|
||||||
import { apolloClient } from './src/services/apollo-client';
|
import { apolloClient } from './src/services/apollo-client';
|
||||||
import { AuthProvider } from './src/contexts/AuthContext';
|
import { AuthProvider } from './src/contexts/AuthContext';
|
||||||
import { AppNavigator } from './src/navigation/AppNavigator';
|
import { AppNavigator } from './src/navigation/AppNavigator';
|
||||||
|
import { theme } from './src/theme';
|
||||||
// Кастомная темная тема в черно-фиолетовых тонах
|
|
||||||
const theme = {
|
|
||||||
...MD3DarkTheme,
|
|
||||||
colors: {
|
|
||||||
...MD3DarkTheme.colors,
|
|
||||||
primary: '#9333ea',
|
|
||||||
secondary: '#a855f7',
|
|
||||||
tertiary: '#7c3aed',
|
|
||||||
background: '#0a0a0f',
|
|
||||||
surface: '#1a1a2e',
|
|
||||||
surfaceVariant: '#2d2d42',
|
|
||||||
onSurface: '#ffffff',
|
|
||||||
onSurfaceVariant: '#e5e5e7',
|
|
||||||
onPrimary: '#ffffff',
|
|
||||||
elevation: {
|
|
||||||
level0: 'transparent',
|
|
||||||
level1: '#1a1a2e',
|
|
||||||
level2: '#2d2d42',
|
|
||||||
level3: '#3d3d56',
|
|
||||||
level4: '#4d4d6a',
|
|
||||||
level5: '#5d5d7e',
|
|
||||||
},
|
|
||||||
outline: '#7c3aed',
|
|
||||||
outlineVariant: '#6d28d9',
|
|
||||||
error: '#ef4444',
|
|
||||||
},
|
|
||||||
roundness: 12,
|
|
||||||
};
|
|
||||||
|
|
||||||
export default function App() {
|
export default function App() {
|
||||||
return (
|
return (
|
||||||
|
@ -87,4 +87,23 @@ export const MARK_MESSAGE_AS_READ = gql`
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
${MESSAGE_FRAGMENT}
|
${MESSAGE_FRAGMENT}
|
||||||
|
`;
|
||||||
|
|
||||||
|
// Profile mutations
|
||||||
|
export const UPDATE_PROFILE = gql`
|
||||||
|
mutation UpdateProfile($bio: String!) {
|
||||||
|
updateProfile(bio: $bio) {
|
||||||
|
...UserFragment
|
||||||
|
}
|
||||||
|
}
|
||||||
|
${USER_FRAGMENT}
|
||||||
|
`;
|
||||||
|
|
||||||
|
export const UPDATE_ONLINE_STATUS = gql`
|
||||||
|
mutation UpdateOnlineStatus($isOnline: Boolean!) {
|
||||||
|
updateOnlineStatus(isOnline: $isOnline) {
|
||||||
|
...UserFragment
|
||||||
|
}
|
||||||
|
}
|
||||||
|
${USER_FRAGMENT}
|
||||||
`;
|
`;
|
@ -1,6 +1,6 @@
|
|||||||
import React, { useState } from 'react';
|
import React, { useState } from 'react';
|
||||||
import { View, StyleSheet, FlatList, TouchableOpacity } from 'react-native';
|
import { View, StyleSheet, FlatList, TouchableOpacity } from 'react-native';
|
||||||
import { List, Avatar, Text, FAB, Divider, Badge, Searchbar, IconButton } from 'react-native-paper';
|
import { List, Avatar, Text, FAB, Divider, Badge, Searchbar, IconButton, useTheme } from 'react-native-paper';
|
||||||
import { useQuery } from '@apollo/client';
|
import { useQuery } from '@apollo/client';
|
||||||
import { GET_CONVERSATIONS } from '../graphql/queries';
|
import { GET_CONVERSATIONS } from '../graphql/queries';
|
||||||
import { Conversation } from '../types';
|
import { Conversation } from '../types';
|
||||||
@ -11,6 +11,7 @@ import { useAuth } from '../contexts/AuthContext';
|
|||||||
export const ConversationsScreen = ({ navigation }: any) => {
|
export const ConversationsScreen = ({ navigation }: any) => {
|
||||||
const [searchQuery, setSearchQuery] = useState('');
|
const [searchQuery, setSearchQuery] = useState('');
|
||||||
const { user } = useAuth();
|
const { user } = useAuth();
|
||||||
|
const theme = useTheme();
|
||||||
|
|
||||||
const { data, loading, error, refetch } = useQuery(GET_CONVERSATIONS, {
|
const { data, loading, error, refetch } = useQuery(GET_CONVERSATIONS, {
|
||||||
pollInterval: 5000, // Обновляем каждые 5 секунд
|
pollInterval: 5000, // Обновляем каждые 5 секунд
|
||||||
@ -89,14 +90,14 @@ export const ConversationsScreen = ({ navigation }: any) => {
|
|||||||
}
|
}
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<View style={styles.container}>
|
<View style={[styles.container, { backgroundColor: theme.colors.background }]}>
|
||||||
{/* Поисковая строка */}
|
{/* Поисковая строка */}
|
||||||
<View style={styles.searchContainer}>
|
<View style={[styles.searchContainer, { backgroundColor: theme.colors.surface }]}>
|
||||||
<Searchbar
|
<Searchbar
|
||||||
placeholder="Поиск чатов..."
|
placeholder="Поиск чатов..."
|
||||||
onChangeText={setSearchQuery}
|
onChangeText={setSearchQuery}
|
||||||
value={searchQuery}
|
value={searchQuery}
|
||||||
style={styles.searchbar}
|
style={[styles.searchbar, { backgroundColor: theme.colors.surfaceVariant }]}
|
||||||
icon="magnify"
|
icon="magnify"
|
||||||
/>
|
/>
|
||||||
</View>
|
</View>
|
||||||
|
@ -1,6 +1,6 @@
|
|||||||
import React, { useState, useEffect } from 'react';
|
import React, { useState, useEffect } from 'react';
|
||||||
import { View, StyleSheet, KeyboardAvoidingView, Platform, ScrollView, Dimensions } from 'react-native';
|
import { View, StyleSheet, KeyboardAvoidingView, Platform, ScrollView, Dimensions } from 'react-native';
|
||||||
import { TextInput, Button, Text, Headline, HelperText } from 'react-native-paper';
|
import { TextInput, Button, Text, Headline, HelperText, useTheme } from 'react-native-paper';
|
||||||
import { useMutation } from '@apollo/client';
|
import { useMutation } from '@apollo/client';
|
||||||
import { LOGIN } from '../graphql/mutations';
|
import { LOGIN } from '../graphql/mutations';
|
||||||
import { useAuth } from '../contexts/AuthContext';
|
import { useAuth } from '../contexts/AuthContext';
|
||||||
@ -24,6 +24,7 @@ export const LoginScreen = ({ navigation }: any) => {
|
|||||||
const [password, setPassword] = useState('');
|
const [password, setPassword] = useState('');
|
||||||
const [showPassword, setShowPassword] = useState(false);
|
const [showPassword, setShowPassword] = useState(false);
|
||||||
const { login } = useAuth();
|
const { login } = useAuth();
|
||||||
|
const theme = useTheme();
|
||||||
|
|
||||||
// Анимации
|
// Анимации
|
||||||
const translateY = useSharedValue(50);
|
const translateY = useSharedValue(50);
|
||||||
@ -119,7 +120,7 @@ export const LoginScreen = ({ navigation }: any) => {
|
|||||||
|
|
||||||
return (
|
return (
|
||||||
<KeyboardAvoidingView
|
<KeyboardAvoidingView
|
||||||
style={styles.container}
|
style={[styles.container, { backgroundColor: theme.colors.background }]}
|
||||||
behavior={Platform.OS === 'ios' ? 'padding' : 'height'}
|
behavior={Platform.OS === 'ios' ? 'padding' : 'height'}
|
||||||
>
|
>
|
||||||
<ScrollView contentContainerStyle={styles.scrollContent}>
|
<ScrollView contentContainerStyle={styles.scrollContent}>
|
||||||
@ -140,11 +141,11 @@ export const LoginScreen = ({ navigation }: any) => {
|
|||||||
disabled={loading}
|
disabled={loading}
|
||||||
theme={{
|
theme={{
|
||||||
colors: {
|
colors: {
|
||||||
primary: '#9333ea',
|
primary: theme.colors.primary,
|
||||||
placeholder: '#a855f7',
|
placeholder: theme.colors.secondary,
|
||||||
text: '#ffffff',
|
text: theme.colors.onSurface,
|
||||||
background: '#1a1a2e',
|
background: theme.colors.surface,
|
||||||
outline: '#7c3aed',
|
outline: theme.colors.outline,
|
||||||
}
|
}
|
||||||
}}
|
}}
|
||||||
onFocus={() => {
|
onFocus={() => {
|
||||||
@ -167,11 +168,11 @@ export const LoginScreen = ({ navigation }: any) => {
|
|||||||
disabled={loading}
|
disabled={loading}
|
||||||
theme={{
|
theme={{
|
||||||
colors: {
|
colors: {
|
||||||
primary: '#9333ea',
|
primary: theme.colors.primary,
|
||||||
placeholder: '#a855f7',
|
placeholder: theme.colors.secondary,
|
||||||
text: '#ffffff',
|
text: theme.colors.onSurface,
|
||||||
background: '#1a1a2e',
|
background: theme.colors.surface,
|
||||||
outline: '#7c3aed',
|
outline: theme.colors.outline,
|
||||||
}
|
}
|
||||||
}}
|
}}
|
||||||
right={
|
right={
|
||||||
|
119
frontend/src/screens/PROFILE_SETTINGS_FEATURE.md
Normal file
119
frontend/src/screens/PROFILE_SETTINGS_FEATURE.md
Normal file
@ -0,0 +1,119 @@
|
|||||||
|
# 🌙 Темная тема и настройки профиля в Prism Messenger
|
||||||
|
|
||||||
|
## 🎨 Новая темная тема
|
||||||
|
|
||||||
|
Приложение теперь работает в стильной темной теме с фиолетовыми акцентами:
|
||||||
|
|
||||||
|
- **Основной фон**: #0f0f0f (глубокий черный)
|
||||||
|
- **Поверхности**: #1a1a1a (темно-серый)
|
||||||
|
- **Акцент**: #6366f1 (яркий фиолетовый)
|
||||||
|
- **Вторичный**: #818cf8 (светло-фиолетовый)
|
||||||
|
|
||||||
|
## 📱 Экран профиля и настроек
|
||||||
|
|
||||||
|
### Визуальная структура:
|
||||||
|
|
||||||
|
```
|
||||||
|
┌─────────────────────────────────┐
|
||||||
|
│ 👤 @username │
|
||||||
|
│ user@email.com │
|
||||||
|
│ "Био пользователя" │
|
||||||
|
│ 🟢 В сети │
|
||||||
|
├─────────────────────────────────┤
|
||||||
|
│ ⚙️ ОСНОВНЫЕ НАСТРОЙКИ │
|
||||||
|
│ │
|
||||||
|
│ ✏️ Редактировать профиль > │
|
||||||
|
│ 🔒 Настройки приватности > │
|
||||||
|
│ 🔔 Уведомления > │
|
||||||
|
├─────────────────────────────────┤
|
||||||
|
│ 🎨 ВНЕШНИЙ ВИД │
|
||||||
|
│ │
|
||||||
|
│ 🌙 Темная тема [✓] │
|
||||||
|
├─────────────────────────────────┤
|
||||||
|
│ ℹ️ ИНФОРМАЦИЯ │
|
||||||
|
│ │
|
||||||
|
│ 📱 О приложении │
|
||||||
|
│ 📄 Условия использования │
|
||||||
|
│ 🔐 Политика конфиденциальности │
|
||||||
|
├─────────────────────────────────┤
|
||||||
|
│ [🚪 Выйти из аккаунта] │
|
||||||
|
│ [🗑️ Удалить аккаунт] │
|
||||||
|
└─────────────────────────────────┘
|
||||||
|
```
|
||||||
|
|
||||||
|
## ✨ Функциональность
|
||||||
|
|
||||||
|
### 1. **Редактирование профиля**
|
||||||
|
- Изменение био (описания)
|
||||||
|
- Сохранение изменений через GraphQL
|
||||||
|
- Валидация и обработка ошибок
|
||||||
|
|
||||||
|
### 2. **Настройки приватности**
|
||||||
|
- **Показывать онлайн статус** - контроль видимости вашего статуса
|
||||||
|
- **Показывать время последнего визита** - скрыть время последнего входа
|
||||||
|
- Настройки сохраняются локально и на сервере
|
||||||
|
|
||||||
|
### 3. **Настройки уведомлений**
|
||||||
|
- **Push-уведомления** - основной переключатель
|
||||||
|
- **Уведомления о сообщениях** - показ превью сообщений
|
||||||
|
- **Звуковые уведомления** - звук при новом сообщении
|
||||||
|
- Зависимые настройки (отключаются при выключении основной)
|
||||||
|
|
||||||
|
### 4. **Управление аккаунтом**
|
||||||
|
- **Выход** - с подтверждением через Alert
|
||||||
|
- **Удаление аккаунта** - с двойным подтверждением
|
||||||
|
|
||||||
|
## 🔧 Технические детали
|
||||||
|
|
||||||
|
### GraphQL мутации:
|
||||||
|
```graphql
|
||||||
|
mutation UpdateProfile($bio: String!) {
|
||||||
|
updateProfile(bio: $bio) {
|
||||||
|
id
|
||||||
|
bio
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
mutation UpdateOnlineStatus($isOnline: Boolean!) {
|
||||||
|
updateOnlineStatus(isOnline: $isOnline) {
|
||||||
|
id
|
||||||
|
isOnline
|
||||||
|
}
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
### Локальное хранилище:
|
||||||
|
- Настройки приватности сохраняются в AsyncStorage
|
||||||
|
- Настройки уведомлений хранятся локально
|
||||||
|
- Синхронизация с сервером при изменении
|
||||||
|
|
||||||
|
### Модальные окна:
|
||||||
|
- Используются Portal и Modal из React Native Paper
|
||||||
|
- Анимированные переходы
|
||||||
|
- Темная тема для всех диалогов
|
||||||
|
|
||||||
|
## 🎯 UX особенности
|
||||||
|
|
||||||
|
1. **Визуальная обратная связь**
|
||||||
|
- Индикаторы загрузки при сохранении
|
||||||
|
- Alert сообщения об успехе/ошибке
|
||||||
|
- Disabled состояния для зависимых настроек
|
||||||
|
|
||||||
|
2. **Безопасность**
|
||||||
|
- Подтверждение критических действий
|
||||||
|
- Двойное подтверждение для удаления аккаунта
|
||||||
|
- Валидация на клиенте и сервере
|
||||||
|
|
||||||
|
3. **Адаптивность**
|
||||||
|
- ScrollView для маленьких экранов
|
||||||
|
- Модальные окна с правильными отступами
|
||||||
|
- Корректная работа клавиатуры
|
||||||
|
|
||||||
|
## 🚀 Будущие улучшения
|
||||||
|
|
||||||
|
1. **Загрузка аватара**
|
||||||
|
2. **Смена пароля**
|
||||||
|
3. **Двухфакторная аутентификация**
|
||||||
|
4. **Экспорт данных**
|
||||||
|
5. **Выбор языка интерфейса**
|
||||||
|
6. **Светлая тема (переключатель)**
|
@ -1,42 +1,39 @@
|
|||||||
import React, { useState } from 'react';
|
import React, { useState } from 'react';
|
||||||
import { View, StyleSheet, ScrollView, Alert } from 'react-native';
|
import { View, StyleSheet, ScrollView, Switch, Alert } from 'react-native';
|
||||||
import {
|
import { Avatar, Text, Card, List, Button, Divider, useTheme, IconButton, Surface, TextInput, Portal, Modal, ActivityIndicator } from 'react-native-paper';
|
||||||
Avatar,
|
|
||||||
Text,
|
|
||||||
List,
|
|
||||||
Switch,
|
|
||||||
Divider,
|
|
||||||
Button,
|
|
||||||
IconButton,
|
|
||||||
Surface,
|
|
||||||
useTheme,
|
|
||||||
TextInput,
|
|
||||||
Dialog,
|
|
||||||
Portal,
|
|
||||||
} from 'react-native-paper';
|
|
||||||
import { useAuth } from '../contexts/AuthContext';
|
import { useAuth } from '../contexts/AuthContext';
|
||||||
|
import { MaterialCommunityIcons } from '@expo/vector-icons';
|
||||||
|
import AsyncStorage from '@react-native-async-storage/async-storage';
|
||||||
import { useMutation } from '@apollo/client';
|
import { useMutation } from '@apollo/client';
|
||||||
import { UPDATE_USER } from '../graphql/mutations';
|
import { UPDATE_PROFILE, UPDATE_ONLINE_STATUS } from '../graphql/mutations';
|
||||||
|
|
||||||
export const ProfileScreen = ({ navigation }: any) => {
|
export const ProfileScreen = ({ navigation }: any) => {
|
||||||
const theme = useTheme();
|
|
||||||
const { user, logout } = useAuth();
|
const { user, logout } = useAuth();
|
||||||
const [notificationsEnabled, setNotificationsEnabled] = useState(true);
|
const theme = useTheme();
|
||||||
const [darkMode, setDarkMode] = useState(false);
|
|
||||||
const [editDialogVisible, setEditDialogVisible] = useState(false);
|
// Состояния для модальных окон
|
||||||
|
const [editProfileVisible, setEditProfileVisible] = useState(false);
|
||||||
|
const [privacyVisible, setPrivacyVisible] = useState(false);
|
||||||
|
const [notificationsVisible, setNotificationsVisible] = useState(false);
|
||||||
|
const [deleteAccountVisible, setDeleteAccountVisible] = useState(false);
|
||||||
|
|
||||||
|
// Состояния для настроек
|
||||||
const [bio, setBio] = useState(user?.bio || '');
|
const [bio, setBio] = useState(user?.bio || '');
|
||||||
|
const [showOnlineStatus, setShowOnlineStatus] = useState(true);
|
||||||
|
const [showLastSeen, setShowLastSeen] = useState(true);
|
||||||
|
const [notificationsEnabled, setNotificationsEnabled] = useState(true);
|
||||||
|
const [messageNotifications, setMessageNotifications] = useState(true);
|
||||||
|
const [soundEnabled, setSoundEnabled] = useState(true);
|
||||||
|
|
||||||
|
// Состояние темы (пока только темная)
|
||||||
|
const [isDarkTheme, setIsDarkTheme] = useState(true);
|
||||||
|
|
||||||
|
const [updateProfile, { loading: updatingProfile }] = useMutation(UPDATE_PROFILE);
|
||||||
|
const [updateOnlineStatus] = useMutation(UPDATE_ONLINE_STATUS);
|
||||||
|
|
||||||
const [updateUser] = useMutation(UPDATE_USER, {
|
const handleLogout = async () => {
|
||||||
onCompleted: (data) => {
|
|
||||||
// В реальном приложении нужно обновить контекст пользователя
|
|
||||||
setEditDialogVisible(false);
|
|
||||||
Alert.alert('Успешно', 'Профиль обновлен');
|
|
||||||
},
|
|
||||||
});
|
|
||||||
|
|
||||||
const handleLogout = () => {
|
|
||||||
Alert.alert(
|
Alert.alert(
|
||||||
'Выход',
|
'Выход из аккаунта',
|
||||||
'Вы уверены, что хотите выйти?',
|
'Вы уверены, что хотите выйти?',
|
||||||
[
|
[
|
||||||
{ text: 'Отмена', style: 'cancel' },
|
{ text: 'Отмена', style: 'cancel' },
|
||||||
@ -46,196 +43,458 @@ export const ProfileScreen = ({ navigation }: any) => {
|
|||||||
onPress: async () => {
|
onPress: async () => {
|
||||||
await logout();
|
await logout();
|
||||||
}
|
}
|
||||||
},
|
}
|
||||||
]
|
]
|
||||||
);
|
);
|
||||||
};
|
};
|
||||||
|
|
||||||
const handleUpdateProfile = async () => {
|
const handleDeleteAccount = () => {
|
||||||
|
Alert.alert(
|
||||||
|
'Удаление аккаунта',
|
||||||
|
'Это действие необратимо. Все ваши данные будут удалены навсегда.',
|
||||||
|
[
|
||||||
|
{ text: 'Отмена', style: 'cancel' },
|
||||||
|
{
|
||||||
|
text: 'Удалить',
|
||||||
|
style: 'destructive',
|
||||||
|
onPress: async () => {
|
||||||
|
// TODO: Реализовать удаление аккаунта через API
|
||||||
|
console.log('Deleting account...');
|
||||||
|
setDeleteAccountVisible(false);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
]
|
||||||
|
);
|
||||||
|
};
|
||||||
|
|
||||||
|
const saveProfileChanges = async () => {
|
||||||
try {
|
try {
|
||||||
await updateUser({
|
await updateProfile({
|
||||||
variables: { bio },
|
variables: { bio }
|
||||||
});
|
});
|
||||||
|
setEditProfileVisible(false);
|
||||||
|
Alert.alert('Успешно', 'Профиль обновлен');
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
Alert.alert('Ошибка', 'Не удалось обновить профиль');
|
Alert.alert('Ошибка', 'Не удалось обновить профиль');
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
|
const savePrivacySettings = async () => {
|
||||||
|
try {
|
||||||
|
await updateOnlineStatus({
|
||||||
|
variables: { isOnline: showOnlineStatus }
|
||||||
|
});
|
||||||
|
// Сохраняем локальные настройки
|
||||||
|
await AsyncStorage.setItem('privacy_settings', JSON.stringify({
|
||||||
|
showOnlineStatus,
|
||||||
|
showLastSeen
|
||||||
|
}));
|
||||||
|
setPrivacyVisible(false);
|
||||||
|
Alert.alert('Успешно', 'Настройки приватности сохранены');
|
||||||
|
} catch (error) {
|
||||||
|
Alert.alert('Ошибка', 'Не удалось сохранить настройки');
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
const saveNotificationSettings = async () => {
|
||||||
|
await AsyncStorage.setItem('notification_settings', JSON.stringify({
|
||||||
|
notificationsEnabled,
|
||||||
|
messageNotifications,
|
||||||
|
soundEnabled
|
||||||
|
}));
|
||||||
|
setNotificationsVisible(false);
|
||||||
|
Alert.alert('Успешно', 'Настройки уведомлений сохранены');
|
||||||
|
};
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<ScrollView style={styles.container}>
|
<>
|
||||||
{/* Профиль пользователя */}
|
<ScrollView style={[styles.container, { backgroundColor: theme.colors.background }]}>
|
||||||
<Surface style={styles.profileSection} elevation={0}>
|
{/* Профиль пользователя */}
|
||||||
<View style={styles.profileHeader}>
|
<Surface style={[styles.profileCard, { backgroundColor: theme.colors.surface }]} elevation={2}>
|
||||||
<Avatar.Text
|
<View style={styles.profileContent}>
|
||||||
size={80}
|
<Avatar.Text
|
||||||
label={user?.username?.charAt(0).toUpperCase() || '?'}
|
size={80}
|
||||||
style={styles.avatar}
|
label={user?.username.charAt(0).toUpperCase() || 'U'}
|
||||||
/>
|
style={{ backgroundColor: theme.colors.primary }}
|
||||||
<IconButton
|
|
||||||
icon="pencil"
|
|
||||||
size={20}
|
|
||||||
onPress={() => setEditDialogVisible(true)}
|
|
||||||
style={styles.editButton}
|
|
||||||
/>
|
|
||||||
</View>
|
|
||||||
|
|
||||||
<Text variant="headlineSmall" style={styles.username}>
|
|
||||||
{user?.username}
|
|
||||||
</Text>
|
|
||||||
<Text variant="bodyMedium" style={styles.email}>
|
|
||||||
{user?.email}
|
|
||||||
</Text>
|
|
||||||
{user?.bio && (
|
|
||||||
<Text variant="bodyMedium" style={styles.bio}>
|
|
||||||
{user.bio}
|
|
||||||
</Text>
|
|
||||||
)}
|
|
||||||
</Surface>
|
|
||||||
|
|
||||||
<Divider />
|
|
||||||
|
|
||||||
{/* Настройки */}
|
|
||||||
<List.Section>
|
|
||||||
<List.Subheader>Настройки</List.Subheader>
|
|
||||||
|
|
||||||
<List.Item
|
|
||||||
title="Уведомления"
|
|
||||||
description="Получать push-уведомления"
|
|
||||||
left={(props) => <List.Icon {...props} icon="bell" />}
|
|
||||||
right={() => (
|
|
||||||
<Switch
|
|
||||||
value={notificationsEnabled}
|
|
||||||
onValueChange={setNotificationsEnabled}
|
|
||||||
/>
|
/>
|
||||||
)}
|
<View style={styles.profileInfo}>
|
||||||
/>
|
<Text variant="headlineSmall" style={{ color: theme.colors.onSurface }}>
|
||||||
|
@{user?.username}
|
||||||
<List.Item
|
</Text>
|
||||||
title="Темная тема"
|
<Text variant="bodyMedium" style={{ color: theme.colors.onSurfaceVariant }}>
|
||||||
description="Переключить тему приложения"
|
{user?.email}
|
||||||
left={(props) => <List.Icon {...props} icon="theme-light-dark" />}
|
</Text>
|
||||||
right={() => (
|
{user?.bio && (
|
||||||
<Switch
|
<Text variant="bodySmall" style={{ color: theme.colors.onSurfaceVariant, marginTop: 4 }}>
|
||||||
value={darkMode}
|
{user.bio}
|
||||||
onValueChange={setDarkMode}
|
</Text>
|
||||||
|
)}
|
||||||
|
<View style={styles.statusContainer}>
|
||||||
|
<MaterialCommunityIcons
|
||||||
|
name="circle"
|
||||||
|
size={10}
|
||||||
|
color={user?.isOnline ? '#10b981' : '#6b7280'}
|
||||||
|
/>
|
||||||
|
<Text variant="bodySmall" style={{ color: user?.isOnline ? '#10b981' : '#6b7280', marginLeft: 4 }}>
|
||||||
|
{user?.isOnline ? 'В сети' : 'Не в сети'}
|
||||||
|
</Text>
|
||||||
|
</View>
|
||||||
|
</View>
|
||||||
|
</View>
|
||||||
|
</Surface>
|
||||||
|
|
||||||
|
{/* Основные настройки */}
|
||||||
|
<Surface style={[styles.settingsSection, { backgroundColor: theme.colors.surface }]} elevation={1}>
|
||||||
|
<List.Section>
|
||||||
|
<List.Subheader style={{ color: theme.colors.primary }}>Основные настройки</List.Subheader>
|
||||||
|
|
||||||
|
<List.Item
|
||||||
|
title="Редактировать профиль"
|
||||||
|
description="Изменить информацию о себе"
|
||||||
|
titleStyle={{ color: theme.colors.onSurface }}
|
||||||
|
descriptionStyle={{ color: theme.colors.onSurfaceVariant }}
|
||||||
|
left={(props) => <List.Icon {...props} icon="account-edit" color={theme.colors.primary} />}
|
||||||
|
right={(props) => <List.Icon {...props} icon="chevron-right" color={theme.colors.onSurfaceVariant} />}
|
||||||
|
onPress={() => setEditProfileVisible(true)}
|
||||||
/>
|
/>
|
||||||
)}
|
|
||||||
/>
|
<Divider style={{ backgroundColor: theme.colors.outlineVariant }} />
|
||||||
|
|
||||||
<List.Item
|
<List.Item
|
||||||
title="Язык"
|
title="Настройки приватности"
|
||||||
description="Русский"
|
description="Онлайн статус и последнее посещение"
|
||||||
left={(props) => <List.Icon {...props} icon="translate" />}
|
titleStyle={{ color: theme.colors.onSurface }}
|
||||||
right={(props) => <List.Icon {...props} icon="chevron-right" />}
|
descriptionStyle={{ color: theme.colors.onSurfaceVariant }}
|
||||||
onPress={() => {}}
|
left={(props) => <List.Icon {...props} icon="shield-lock" color={theme.colors.primary} />}
|
||||||
/>
|
right={(props) => <List.Icon {...props} icon="chevron-right" color={theme.colors.onSurfaceVariant} />}
|
||||||
|
onPress={() => setPrivacyVisible(true)}
|
||||||
<List.Item
|
/>
|
||||||
title="Приватность"
|
|
||||||
description="Настройки приватности"
|
<Divider style={{ backgroundColor: theme.colors.outlineVariant }} />
|
||||||
left={(props) => <List.Icon {...props} icon="lock" />}
|
|
||||||
right={(props) => <List.Icon {...props} icon="chevron-right" />}
|
<List.Item
|
||||||
onPress={() => {}}
|
title="Уведомления"
|
||||||
/>
|
description="Push-уведомления и звуки"
|
||||||
</List.Section>
|
titleStyle={{ color: theme.colors.onSurface }}
|
||||||
|
descriptionStyle={{ color: theme.colors.onSurfaceVariant }}
|
||||||
|
left={(props) => <List.Icon {...props} icon="bell" color={theme.colors.primary} />}
|
||||||
|
right={(props) => <List.Icon {...props} icon="chevron-right" color={theme.colors.onSurfaceVariant} />}
|
||||||
|
onPress={() => setNotificationsVisible(true)}
|
||||||
|
/>
|
||||||
|
</List.Section>
|
||||||
|
</Surface>
|
||||||
|
|
||||||
<Divider />
|
{/* Внешний вид */}
|
||||||
|
<Surface style={[styles.settingsSection, { backgroundColor: theme.colors.surface }]} elevation={1}>
|
||||||
|
<List.Section>
|
||||||
|
<List.Subheader style={{ color: theme.colors.primary }}>Внешний вид</List.Subheader>
|
||||||
|
|
||||||
|
<List.Item
|
||||||
|
title="Темная тема"
|
||||||
|
description="Включена по умолчанию"
|
||||||
|
titleStyle={{ color: theme.colors.onSurface }}
|
||||||
|
descriptionStyle={{ color: theme.colors.onSurfaceVariant }}
|
||||||
|
left={(props) => <List.Icon {...props} icon="theme-light-dark" color={theme.colors.primary} />}
|
||||||
|
right={() => (
|
||||||
|
<Switch
|
||||||
|
value={isDarkTheme}
|
||||||
|
onValueChange={setIsDarkTheme}
|
||||||
|
color={theme.colors.primary}
|
||||||
|
disabled
|
||||||
|
/>
|
||||||
|
)}
|
||||||
|
/>
|
||||||
|
</List.Section>
|
||||||
|
</Surface>
|
||||||
|
|
||||||
{/* О приложении */}
|
{/* Информация */}
|
||||||
<List.Section>
|
<Surface style={[styles.settingsSection, { backgroundColor: theme.colors.surface }]} elevation={1}>
|
||||||
<List.Subheader>О приложении</List.Subheader>
|
<List.Section>
|
||||||
|
<List.Subheader style={{ color: theme.colors.primary }}>Информация</List.Subheader>
|
||||||
<List.Item
|
|
||||||
title="Версия"
|
<List.Item
|
||||||
description="1.0.0"
|
title="О приложении"
|
||||||
left={(props) => <List.Icon {...props} icon="information" />}
|
description="Prism Messenger v1.0.0"
|
||||||
/>
|
titleStyle={{ color: theme.colors.onSurface }}
|
||||||
|
descriptionStyle={{ color: theme.colors.onSurfaceVariant }}
|
||||||
<List.Item
|
left={(props) => <List.Icon {...props} icon="information" color={theme.colors.primary} />}
|
||||||
title="Помощь"
|
onPress={() => {}}
|
||||||
left={(props) => <List.Icon {...props} icon="help-circle" />}
|
/>
|
||||||
right={(props) => <List.Icon {...props} icon="chevron-right" />}
|
|
||||||
onPress={() => {}}
|
<Divider style={{ backgroundColor: theme.colors.outlineVariant }} />
|
||||||
/>
|
|
||||||
</List.Section>
|
<List.Item
|
||||||
|
title="Условия использования"
|
||||||
|
titleStyle={{ color: theme.colors.onSurface }}
|
||||||
|
left={(props) => <List.Icon {...props} icon="file-document" color={theme.colors.primary} />}
|
||||||
|
onPress={() => {}}
|
||||||
|
/>
|
||||||
|
|
||||||
|
<Divider style={{ backgroundColor: theme.colors.outlineVariant }} />
|
||||||
|
|
||||||
|
<List.Item
|
||||||
|
title="Политика конфиденциальности"
|
||||||
|
titleStyle={{ color: theme.colors.onSurface }}
|
||||||
|
left={(props) => <List.Icon {...props} icon="shield-check" color={theme.colors.primary} />}
|
||||||
|
onPress={() => {}}
|
||||||
|
/>
|
||||||
|
</List.Section>
|
||||||
|
</Surface>
|
||||||
|
|
||||||
{/* Действия */}
|
{/* Действия с аккаунтом */}
|
||||||
<View style={styles.actions}>
|
<Surface style={[styles.settingsSection, { backgroundColor: theme.colors.surface, marginBottom: 32 }]} elevation={1}>
|
||||||
<Button
|
<View style={styles.accountActions}>
|
||||||
mode="outlined"
|
<Button
|
||||||
onPress={handleLogout}
|
mode="contained"
|
||||||
style={styles.logoutButton}
|
onPress={handleLogout}
|
||||||
textColor={theme.colors.error}
|
style={[styles.logoutButton, { backgroundColor: theme.colors.error }]}
|
||||||
>
|
labelStyle={styles.buttonLabel}
|
||||||
Выйти из аккаунта
|
icon="logout"
|
||||||
</Button>
|
>
|
||||||
</View>
|
Выйти из аккаунта
|
||||||
|
</Button>
|
||||||
|
|
||||||
|
<Button
|
||||||
|
mode="outlined"
|
||||||
|
onPress={() => setDeleteAccountVisible(true)}
|
||||||
|
style={[styles.deleteButton, { borderColor: theme.colors.error }]}
|
||||||
|
labelStyle={[styles.buttonLabel, { color: theme.colors.error }]}
|
||||||
|
icon="account-remove"
|
||||||
|
>
|
||||||
|
Удалить аккаунт
|
||||||
|
</Button>
|
||||||
|
</View>
|
||||||
|
</Surface>
|
||||||
|
</ScrollView>
|
||||||
|
|
||||||
{/* Диалог редактирования профиля */}
|
{/* Модальное окно редактирования профиля */}
|
||||||
<Portal>
|
<Portal>
|
||||||
<Dialog visible={editDialogVisible} onDismiss={() => setEditDialogVisible(false)}>
|
<Modal
|
||||||
<Dialog.Title>Редактировать профиль</Dialog.Title>
|
visible={editProfileVisible}
|
||||||
<Dialog.Content>
|
onDismiss={() => setEditProfileVisible(false)}
|
||||||
<TextInput
|
contentContainerStyle={[styles.modal, { backgroundColor: theme.colors.surface }]}
|
||||||
label="О себе"
|
>
|
||||||
value={bio}
|
<Text variant="headlineSmall" style={{ color: theme.colors.onSurface, marginBottom: 16 }}>
|
||||||
onChangeText={setBio}
|
Редактировать профиль
|
||||||
multiline
|
</Text>
|
||||||
numberOfLines={3}
|
|
||||||
mode="outlined"
|
<TextInput
|
||||||
/>
|
label="О себе"
|
||||||
</Dialog.Content>
|
value={bio}
|
||||||
<Dialog.Actions>
|
onChangeText={setBio}
|
||||||
<Button onPress={() => setEditDialogVisible(false)}>Отмена</Button>
|
mode="outlined"
|
||||||
<Button onPress={handleUpdateProfile}>Сохранить</Button>
|
multiline
|
||||||
</Dialog.Actions>
|
numberOfLines={3}
|
||||||
</Dialog>
|
style={{ marginBottom: 16 }}
|
||||||
|
outlineColor={theme.colors.outline}
|
||||||
|
activeOutlineColor={theme.colors.primary}
|
||||||
|
textColor={theme.colors.onSurface}
|
||||||
|
/>
|
||||||
|
|
||||||
|
<View style={styles.modalActions}>
|
||||||
|
<Button
|
||||||
|
mode="text"
|
||||||
|
onPress={() => setEditProfileVisible(false)}
|
||||||
|
textColor={theme.colors.onSurfaceVariant}
|
||||||
|
>
|
||||||
|
Отмена
|
||||||
|
</Button>
|
||||||
|
<Button
|
||||||
|
mode="contained"
|
||||||
|
onPress={saveProfileChanges}
|
||||||
|
loading={updatingProfile}
|
||||||
|
disabled={updatingProfile}
|
||||||
|
>
|
||||||
|
Сохранить
|
||||||
|
</Button>
|
||||||
|
</View>
|
||||||
|
</Modal>
|
||||||
</Portal>
|
</Portal>
|
||||||
</ScrollView>
|
|
||||||
|
{/* Модальное окно настроек приватности */}
|
||||||
|
<Portal>
|
||||||
|
<Modal
|
||||||
|
visible={privacyVisible}
|
||||||
|
onDismiss={() => setPrivacyVisible(false)}
|
||||||
|
contentContainerStyle={[styles.modal, { backgroundColor: theme.colors.surface }]}
|
||||||
|
>
|
||||||
|
<Text variant="headlineSmall" style={{ color: theme.colors.onSurface, marginBottom: 16 }}>
|
||||||
|
Настройки приватности
|
||||||
|
</Text>
|
||||||
|
|
||||||
|
<List.Item
|
||||||
|
title="Показывать онлайн статус"
|
||||||
|
titleStyle={{ color: theme.colors.onSurface }}
|
||||||
|
description="Другие пользователи видят, когда вы в сети"
|
||||||
|
descriptionStyle={{ color: theme.colors.onSurfaceVariant }}
|
||||||
|
right={() => (
|
||||||
|
<Switch
|
||||||
|
value={showOnlineStatus}
|
||||||
|
onValueChange={setShowOnlineStatus}
|
||||||
|
color={theme.colors.primary}
|
||||||
|
/>
|
||||||
|
)}
|
||||||
|
/>
|
||||||
|
|
||||||
|
<List.Item
|
||||||
|
title="Показывать время последнего визита"
|
||||||
|
titleStyle={{ color: theme.colors.onSurface }}
|
||||||
|
description="Видно, когда вы последний раз были в сети"
|
||||||
|
descriptionStyle={{ color: theme.colors.onSurfaceVariant }}
|
||||||
|
right={() => (
|
||||||
|
<Switch
|
||||||
|
value={showLastSeen}
|
||||||
|
onValueChange={setShowLastSeen}
|
||||||
|
color={theme.colors.primary}
|
||||||
|
/>
|
||||||
|
)}
|
||||||
|
/>
|
||||||
|
|
||||||
|
<View style={styles.modalActions}>
|
||||||
|
<Button
|
||||||
|
mode="text"
|
||||||
|
onPress={() => setPrivacyVisible(false)}
|
||||||
|
textColor={theme.colors.onSurfaceVariant}
|
||||||
|
>
|
||||||
|
Отмена
|
||||||
|
</Button>
|
||||||
|
<Button
|
||||||
|
mode="contained"
|
||||||
|
onPress={savePrivacySettings}
|
||||||
|
>
|
||||||
|
Сохранить
|
||||||
|
</Button>
|
||||||
|
</View>
|
||||||
|
</Modal>
|
||||||
|
</Portal>
|
||||||
|
|
||||||
|
{/* Модальное окно настроек уведомлений */}
|
||||||
|
<Portal>
|
||||||
|
<Modal
|
||||||
|
visible={notificationsVisible}
|
||||||
|
onDismiss={() => setNotificationsVisible(false)}
|
||||||
|
contentContainerStyle={[styles.modal, { backgroundColor: theme.colors.surface }]}
|
||||||
|
>
|
||||||
|
<Text variant="headlineSmall" style={{ color: theme.colors.onSurface, marginBottom: 16 }}>
|
||||||
|
Настройки уведомлений
|
||||||
|
</Text>
|
||||||
|
|
||||||
|
<List.Item
|
||||||
|
title="Push-уведомления"
|
||||||
|
titleStyle={{ color: theme.colors.onSurface }}
|
||||||
|
description="Получать уведомления о новых сообщениях"
|
||||||
|
descriptionStyle={{ color: theme.colors.onSurfaceVariant }}
|
||||||
|
right={() => (
|
||||||
|
<Switch
|
||||||
|
value={notificationsEnabled}
|
||||||
|
onValueChange={setNotificationsEnabled}
|
||||||
|
color={theme.colors.primary}
|
||||||
|
/>
|
||||||
|
)}
|
||||||
|
/>
|
||||||
|
|
||||||
|
<List.Item
|
||||||
|
title="Уведомления о сообщениях"
|
||||||
|
titleStyle={{ color: theme.colors.onSurface }}
|
||||||
|
description="Показывать превью сообщений"
|
||||||
|
descriptionStyle={{ color: theme.colors.onSurfaceVariant }}
|
||||||
|
right={() => (
|
||||||
|
<Switch
|
||||||
|
value={messageNotifications}
|
||||||
|
onValueChange={setMessageNotifications}
|
||||||
|
color={theme.colors.primary}
|
||||||
|
disabled={!notificationsEnabled}
|
||||||
|
/>
|
||||||
|
)}
|
||||||
|
/>
|
||||||
|
|
||||||
|
<List.Item
|
||||||
|
title="Звуковые уведомления"
|
||||||
|
titleStyle={{ color: theme.colors.onSurface }}
|
||||||
|
description="Воспроизводить звук при новом сообщении"
|
||||||
|
descriptionStyle={{ color: theme.colors.onSurfaceVariant }}
|
||||||
|
right={() => (
|
||||||
|
<Switch
|
||||||
|
value={soundEnabled}
|
||||||
|
onValueChange={setSoundEnabled}
|
||||||
|
color={theme.colors.primary}
|
||||||
|
disabled={!notificationsEnabled}
|
||||||
|
/>
|
||||||
|
)}
|
||||||
|
/>
|
||||||
|
|
||||||
|
<View style={styles.modalActions}>
|
||||||
|
<Button
|
||||||
|
mode="text"
|
||||||
|
onPress={() => setNotificationsVisible(false)}
|
||||||
|
textColor={theme.colors.onSurfaceVariant}
|
||||||
|
>
|
||||||
|
Отмена
|
||||||
|
</Button>
|
||||||
|
<Button
|
||||||
|
mode="contained"
|
||||||
|
onPress={saveNotificationSettings}
|
||||||
|
>
|
||||||
|
Сохранить
|
||||||
|
</Button>
|
||||||
|
</View>
|
||||||
|
</Modal>
|
||||||
|
</Portal>
|
||||||
|
</>
|
||||||
);
|
);
|
||||||
};
|
};
|
||||||
|
|
||||||
const styles = StyleSheet.create({
|
const styles = StyleSheet.create({
|
||||||
container: {
|
container: {
|
||||||
flex: 1,
|
flex: 1,
|
||||||
backgroundColor: '#f5f5f5',
|
|
||||||
},
|
},
|
||||||
profileSection: {
|
profileCard: {
|
||||||
padding: 24,
|
margin: 16,
|
||||||
backgroundColor: '#ffffff',
|
padding: 20,
|
||||||
|
borderRadius: 16,
|
||||||
|
},
|
||||||
|
profileContent: {
|
||||||
|
flexDirection: 'row',
|
||||||
alignItems: 'center',
|
alignItems: 'center',
|
||||||
},
|
},
|
||||||
profileHeader: {
|
profileInfo: {
|
||||||
position: 'relative',
|
marginLeft: 16,
|
||||||
marginBottom: 16,
|
flex: 1,
|
||||||
},
|
},
|
||||||
avatar: {
|
statusContainer: {
|
||||||
backgroundColor: '#2196F3',
|
flexDirection: 'row',
|
||||||
},
|
alignItems: 'center',
|
||||||
editButton: {
|
|
||||||
position: 'absolute',
|
|
||||||
bottom: -8,
|
|
||||||
right: -8,
|
|
||||||
backgroundColor: '#ffffff',
|
|
||||||
elevation: 2,
|
|
||||||
},
|
|
||||||
username: {
|
|
||||||
marginBottom: 4,
|
|
||||||
},
|
|
||||||
email: {
|
|
||||||
color: '#666',
|
|
||||||
marginBottom: 8,
|
|
||||||
},
|
|
||||||
bio: {
|
|
||||||
textAlign: 'center',
|
|
||||||
marginTop: 8,
|
marginTop: 8,
|
||||||
paddingHorizontal: 16,
|
|
||||||
},
|
},
|
||||||
actions: {
|
settingsSection: {
|
||||||
|
marginHorizontal: 16,
|
||||||
|
marginBottom: 16,
|
||||||
|
borderRadius: 16,
|
||||||
|
overflow: 'hidden',
|
||||||
|
},
|
||||||
|
accountActions: {
|
||||||
padding: 16,
|
padding: 16,
|
||||||
marginTop: 16,
|
gap: 12,
|
||||||
},
|
},
|
||||||
logoutButton: {
|
logoutButton: {
|
||||||
borderColor: '#f44336',
|
paddingVertical: 4,
|
||||||
|
},
|
||||||
|
deleteButton: {
|
||||||
|
paddingVertical: 4,
|
||||||
|
},
|
||||||
|
buttonLabel: {
|
||||||
|
fontSize: 16,
|
||||||
|
fontWeight: '600',
|
||||||
|
},
|
||||||
|
modal: {
|
||||||
|
margin: 20,
|
||||||
|
padding: 20,
|
||||||
|
borderRadius: 16,
|
||||||
|
},
|
||||||
|
modalActions: {
|
||||||
|
flexDirection: 'row',
|
||||||
|
justifyContent: 'flex-end',
|
||||||
|
marginTop: 16,
|
||||||
|
gap: 8,
|
||||||
},
|
},
|
||||||
});
|
});
|
83
frontend/src/theme/index.ts
Normal file
83
frontend/src/theme/index.ts
Normal file
@ -0,0 +1,83 @@
|
|||||||
|
import { MD3DarkTheme, MD3LightTheme, adaptNavigationTheme } from 'react-native-paper';
|
||||||
|
import { DefaultTheme, DarkTheme } from '@react-navigation/native';
|
||||||
|
|
||||||
|
// Цвета для темной темы
|
||||||
|
const darkColors = {
|
||||||
|
primary: '#6366f1',
|
||||||
|
onPrimary: '#ffffff',
|
||||||
|
primaryContainer: '#4f46e5',
|
||||||
|
onPrimaryContainer: '#e0e7ff',
|
||||||
|
|
||||||
|
secondary: '#818cf8',
|
||||||
|
onSecondary: '#ffffff',
|
||||||
|
secondaryContainer: '#6366f1',
|
||||||
|
onSecondaryContainer: '#e0e7ff',
|
||||||
|
|
||||||
|
tertiary: '#a78bfa',
|
||||||
|
onTertiary: '#ffffff',
|
||||||
|
tertiaryContainer: '#8b5cf6',
|
||||||
|
onTertiaryContainer: '#ede9fe',
|
||||||
|
|
||||||
|
error: '#ef4444',
|
||||||
|
onError: '#ffffff',
|
||||||
|
errorContainer: '#dc2626',
|
||||||
|
onErrorContainer: '#fee2e2',
|
||||||
|
|
||||||
|
background: '#0f0f0f',
|
||||||
|
onBackground: '#e5e5e5',
|
||||||
|
|
||||||
|
surface: '#1a1a1a',
|
||||||
|
onSurface: '#e5e5e5',
|
||||||
|
|
||||||
|
surfaceVariant: '#262626',
|
||||||
|
onSurfaceVariant: '#d4d4d4',
|
||||||
|
|
||||||
|
outline: '#404040',
|
||||||
|
outlineVariant: '#2a2a2a',
|
||||||
|
|
||||||
|
shadow: '#000000',
|
||||||
|
scrim: '#000000',
|
||||||
|
|
||||||
|
inverseSurface: '#e5e5e5',
|
||||||
|
inverseOnSurface: '#1a1a1a',
|
||||||
|
inversePrimary: '#4f46e5',
|
||||||
|
|
||||||
|
elevation: {
|
||||||
|
level0: 'transparent',
|
||||||
|
level1: '#1f1f1f',
|
||||||
|
level2: '#232323',
|
||||||
|
level3: '#282828',
|
||||||
|
level4: '#2a2a2a',
|
||||||
|
level5: '#2d2d2d',
|
||||||
|
},
|
||||||
|
};
|
||||||
|
|
||||||
|
// Объединяем темы Paper и Navigation
|
||||||
|
const { LightTheme: NavigationLightTheme, DarkTheme: NavigationDarkTheme } = adaptNavigationTheme({
|
||||||
|
reactNavigationLight: DefaultTheme,
|
||||||
|
reactNavigationDark: DarkTheme,
|
||||||
|
});
|
||||||
|
|
||||||
|
// Темная тема (основная)
|
||||||
|
export const darkTheme = {
|
||||||
|
...MD3DarkTheme,
|
||||||
|
...NavigationDarkTheme,
|
||||||
|
colors: {
|
||||||
|
...MD3DarkTheme.colors,
|
||||||
|
...NavigationDarkTheme.colors,
|
||||||
|
...darkColors,
|
||||||
|
},
|
||||||
|
};
|
||||||
|
|
||||||
|
// Светлая тема (на будущее)
|
||||||
|
export const lightTheme = {
|
||||||
|
...MD3LightTheme,
|
||||||
|
...NavigationLightTheme,
|
||||||
|
colors: {
|
||||||
|
...MD3LightTheme.colors,
|
||||||
|
...NavigationLightTheme.colors,
|
||||||
|
},
|
||||||
|
};
|
||||||
|
|
||||||
|
// Экспортируем текущую тему
|
||||||
|
export const theme = darkTheme;
|
Reference in New Issue
Block a user