Implement custom dark theme for the app and enhance login/register screens with animations. Update dependencies and fix package versions in package.json and package-lock.json.
This commit is contained in:
@ -1,20 +1,49 @@
|
||||
import React from 'react';
|
||||
import { StatusBar } from 'expo-status-bar';
|
||||
import { ApolloProvider } from '@apollo/client';
|
||||
import { Provider as PaperProvider } from 'react-native-paper';
|
||||
import { Provider as PaperProvider, MD3DarkTheme, configureFonts } from 'react-native-paper';
|
||||
import { SafeAreaProvider } from 'react-native-safe-area-context';
|
||||
import { apolloClient } from './src/services/apollo-client';
|
||||
import { AuthProvider } from './src/contexts/AuthContext';
|
||||
import { AppNavigator } from './src/navigation/AppNavigator';
|
||||
|
||||
// Кастомная темная тема в черно-фиолетовых тонах
|
||||
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() {
|
||||
return (
|
||||
<SafeAreaProvider>
|
||||
<ApolloProvider client={apolloClient}>
|
||||
<PaperProvider>
|
||||
<PaperProvider theme={theme}>
|
||||
<AuthProvider>
|
||||
<AppNavigator />
|
||||
<StatusBar style="auto" />
|
||||
<StatusBar style="light" />
|
||||
</AuthProvider>
|
||||
</PaperProvider>
|
||||
</ApolloProvider>
|
||||
|
297
frontend/package-lock.json
generated
297
frontend/package-lock.json
generated
@ -9,7 +9,8 @@
|
||||
"version": "1.0.0",
|
||||
"dependencies": {
|
||||
"@apollo/client": "^3.13.9",
|
||||
"@react-native-async-storage/async-storage": "^2.2.0",
|
||||
"@expo/metro-runtime": "~5.0.4",
|
||||
"@react-native-async-storage/async-storage": "2.1.2",
|
||||
"@react-navigation/bottom-tabs": "^7.4.5",
|
||||
"@react-navigation/native": "^7.1.17",
|
||||
"@react-navigation/native-stack": "^7.3.24",
|
||||
@ -18,14 +19,16 @@
|
||||
"expo-status-bar": "~2.2.3",
|
||||
"graphql": "^16.11.0",
|
||||
"react": "19.0.0",
|
||||
"react-dom": "19.0.0",
|
||||
"react-native": "0.79.5",
|
||||
"react-native-gesture-handler": "^2.27.2",
|
||||
"react-native-gesture-handler": "~2.24.0",
|
||||
"react-native-keyboard-aware-scroll-view": "^0.9.5",
|
||||
"react-native-paper": "^5.14.5",
|
||||
"react-native-reanimated": "^4.0.1",
|
||||
"react-native-safe-area-context": "^5.5.2",
|
||||
"react-native-screens": "^4.13.1",
|
||||
"react-native-vector-icons": "^10.3.0"
|
||||
"react-native-reanimated": "~3.17.4",
|
||||
"react-native-safe-area-context": "5.4.0",
|
||||
"react-native-screens": "~4.11.1",
|
||||
"react-native-vector-icons": "^10.3.0",
|
||||
"react-native-web": "^0.20.0"
|
||||
},
|
||||
"devDependencies": {
|
||||
"@babel/core": "^7.25.2",
|
||||
@ -1406,7 +1409,6 @@
|
||||
"resolved": "https://registry.npmjs.org/@babel/plugin-transform-template-literals/-/plugin-transform-template-literals-7.27.1.tgz",
|
||||
"integrity": "sha512-fBJKiV7F2DxZUkg5EtHKXQdbsbURW3DZKQUWphDum0uRP6eHGGa/He9mc0mypL680pb+e/lDIthRohlv8NCHkg==",
|
||||
"license": "MIT",
|
||||
"peer": true,
|
||||
"dependencies": {
|
||||
"@babel/helper-plugin-utils": "^7.27.1"
|
||||
},
|
||||
@ -1917,6 +1919,15 @@
|
||||
"resolve-from": "^5.0.0"
|
||||
}
|
||||
},
|
||||
"node_modules/@expo/metro-runtime": {
|
||||
"version": "5.0.4",
|
||||
"resolved": "https://registry.npmjs.org/@expo/metro-runtime/-/metro-runtime-5.0.4.tgz",
|
||||
"integrity": "sha512-r694MeO+7Vi8IwOsDIDzH/Q5RPMt1kUDYbiTJwnO15nIqiDwlE8HU55UlRhffKZy6s5FmxQsZ8HA+T8DqUW8cQ==",
|
||||
"license": "MIT",
|
||||
"peerDependencies": {
|
||||
"react-native": "*"
|
||||
}
|
||||
},
|
||||
"node_modules/@expo/osascript": {
|
||||
"version": "2.2.5",
|
||||
"resolved": "https://registry.npmjs.org/@expo/osascript/-/osascript-2.2.5.tgz",
|
||||
@ -2380,9 +2391,9 @@
|
||||
}
|
||||
},
|
||||
"node_modules/@react-native-async-storage/async-storage": {
|
||||
"version": "2.2.0",
|
||||
"resolved": "https://registry.npmjs.org/@react-native-async-storage/async-storage/-/async-storage-2.2.0.tgz",
|
||||
"integrity": "sha512-gvRvjR5JAaUZF8tv2Kcq/Gbt3JHwbKFYfmb445rhOj6NUMx3qPLixmDx5pZAyb9at1bYvJ4/eTUipU5aki45xw==",
|
||||
"version": "2.1.2",
|
||||
"resolved": "https://registry.npmjs.org/@react-native-async-storage/async-storage/-/async-storage-2.1.2.tgz",
|
||||
"integrity": "sha512-dvlNq4AlGWC+ehtH12p65+17V0Dx7IecOWl6WanF2ja38O1Dcjjvn7jVzkUHJ5oWkQBlyASurTPlTHgKXyYiow==",
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"merge-options": "^3.0.4"
|
||||
@ -3945,6 +3956,15 @@
|
||||
"js-yaml": "bin/js-yaml.js"
|
||||
}
|
||||
},
|
||||
"node_modules/cross-fetch": {
|
||||
"version": "3.2.0",
|
||||
"resolved": "https://registry.npmjs.org/cross-fetch/-/cross-fetch-3.2.0.tgz",
|
||||
"integrity": "sha512-Q+xVJLoGOeIMXZmbUK4HYk+69cQH6LudR0Vu/pRm2YlU/hDV9CiS0gKUMaWY5f2NeUH9C1nV3bsTlCo0FsTV1Q==",
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"node-fetch": "^2.7.0"
|
||||
}
|
||||
},
|
||||
"node_modules/cross-spawn": {
|
||||
"version": "7.0.6",
|
||||
"resolved": "https://registry.npmjs.org/cross-spawn/-/cross-spawn-7.0.6.tgz",
|
||||
@ -3968,6 +3988,15 @@
|
||||
"node": ">=8"
|
||||
}
|
||||
},
|
||||
"node_modules/css-in-js-utils": {
|
||||
"version": "3.1.0",
|
||||
"resolved": "https://registry.npmjs.org/css-in-js-utils/-/css-in-js-utils-3.1.0.tgz",
|
||||
"integrity": "sha512-fJAcud6B3rRu+KHYk+Bwf+WFL2MDCJJ1XG9x137tJQ0xYxor7XziQtuGFbWNdqrvF4Tk26O3H73nfVqXt/fW1A==",
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"hyphenate-style-name": "^1.0.3"
|
||||
}
|
||||
},
|
||||
"node_modules/csstype": {
|
||||
"version": "3.1.3",
|
||||
"resolved": "https://registry.npmjs.org/csstype/-/csstype-3.1.3.tgz",
|
||||
@ -4410,6 +4439,36 @@
|
||||
"bser": "2.1.1"
|
||||
}
|
||||
},
|
||||
"node_modules/fbjs": {
|
||||
"version": "3.0.5",
|
||||
"resolved": "https://registry.npmjs.org/fbjs/-/fbjs-3.0.5.tgz",
|
||||
"integrity": "sha512-ztsSx77JBtkuMrEypfhgc3cI0+0h+svqeie7xHbh1k/IKdcydnvadp/mUaGgjAOXQmQSxsqgaRhS3q9fy+1kxg==",
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"cross-fetch": "^3.1.5",
|
||||
"fbjs-css-vars": "^1.0.0",
|
||||
"loose-envify": "^1.0.0",
|
||||
"object-assign": "^4.1.0",
|
||||
"promise": "^7.1.1",
|
||||
"setimmediate": "^1.0.5",
|
||||
"ua-parser-js": "^1.0.35"
|
||||
}
|
||||
},
|
||||
"node_modules/fbjs-css-vars": {
|
||||
"version": "1.0.2",
|
||||
"resolved": "https://registry.npmjs.org/fbjs-css-vars/-/fbjs-css-vars-1.0.2.tgz",
|
||||
"integrity": "sha512-b2XGFAFdWZWg0phtAWLHCk836A1Xann+I+Dgd3Gk64MHKZO44FfoD1KxyvbSh0qZsIoXQGGlVztIY+oitJPpRQ==",
|
||||
"license": "MIT"
|
||||
},
|
||||
"node_modules/fbjs/node_modules/promise": {
|
||||
"version": "7.3.1",
|
||||
"resolved": "https://registry.npmjs.org/promise/-/promise-7.3.1.tgz",
|
||||
"integrity": "sha512-nolQXZ/4L+bP/UGlkfaIujX9BKxGwmQ9OT4mOt5yvy8iK1h3wqTEJCijzGANTCCl9nWjY41juyAn2K3Q1hLLTg==",
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"asap": "~2.0.3"
|
||||
}
|
||||
},
|
||||
"node_modules/fill-range": {
|
||||
"version": "7.1.1",
|
||||
"resolved": "https://registry.npmjs.org/fill-range/-/fill-range-7.1.1.tgz",
|
||||
@ -4748,6 +4807,12 @@
|
||||
"node": ">= 14"
|
||||
}
|
||||
},
|
||||
"node_modules/hyphenate-style-name": {
|
||||
"version": "1.1.0",
|
||||
"resolved": "https://registry.npmjs.org/hyphenate-style-name/-/hyphenate-style-name-1.1.0.tgz",
|
||||
"integrity": "sha512-WDC/ui2VVRrz3jOVi+XtjqkDjiVjTtFaAGiW37k6b+ohyQ5wYDOGkvCZa8+H0nx3gyvv0+BST9xuOgIyGQ00gw==",
|
||||
"license": "BSD-3-Clause"
|
||||
},
|
||||
"node_modules/ieee754": {
|
||||
"version": "1.2.1",
|
||||
"resolved": "https://registry.npmjs.org/ieee754/-/ieee754-1.2.1.tgz",
|
||||
@ -4846,6 +4911,15 @@
|
||||
"integrity": "sha512-JV/yugV2uzW5iMRSiZAyDtQd+nxtUnjeLt0acNdw98kKLrvuRVyB80tsREOE7yvGVgalhZ6RNXCmEHkUKBKxew==",
|
||||
"license": "ISC"
|
||||
},
|
||||
"node_modules/inline-style-prefixer": {
|
||||
"version": "7.0.1",
|
||||
"resolved": "https://registry.npmjs.org/inline-style-prefixer/-/inline-style-prefixer-7.0.1.tgz",
|
||||
"integrity": "sha512-lhYo5qNTQp3EvSSp3sRvXMbVQTLrvGV6DycRMJ5dm2BLMiJ30wpXKdDdgX+GmJZ5uQMucwRKHamXSst3Sj/Giw==",
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"css-in-js-utils": "^3.1.0"
|
||||
}
|
||||
},
|
||||
"node_modules/invariant": {
|
||||
"version": "2.2.4",
|
||||
"resolved": "https://registry.npmjs.org/invariant/-/invariant-2.2.4.tgz",
|
||||
@ -6192,6 +6266,26 @@
|
||||
"integrity": "sha512-SrQrok4CATudVzBS7coSz26QRSmlK9TzzoFbeKfcPBUFPjcQM9Rqvr/DlJkOrwI/0KcgvMub1n1g5Jt9EgRn4A==",
|
||||
"license": "MIT"
|
||||
},
|
||||
"node_modules/node-fetch": {
|
||||
"version": "2.7.0",
|
||||
"resolved": "https://registry.npmjs.org/node-fetch/-/node-fetch-2.7.0.tgz",
|
||||
"integrity": "sha512-c4FRfUm/dbcWZ7U+1Wq0AwCyFL+3nt2bEw05wfxSz+DWpWsitgmSgYmy2dQdWyKC1694ELPqMs/YzUSNozLt8A==",
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"whatwg-url": "^5.0.0"
|
||||
},
|
||||
"engines": {
|
||||
"node": "4.x || >=6.0.0"
|
||||
},
|
||||
"peerDependencies": {
|
||||
"encoding": "^0.1.0"
|
||||
},
|
||||
"peerDependenciesMeta": {
|
||||
"encoding": {
|
||||
"optional": true
|
||||
}
|
||||
}
|
||||
},
|
||||
"node_modules/node-forge": {
|
||||
"version": "1.3.1",
|
||||
"resolved": "https://registry.npmjs.org/node-forge/-/node-forge-1.3.1.tgz",
|
||||
@ -6667,6 +6761,12 @@
|
||||
"node": "^10 || ^12 || >=14"
|
||||
}
|
||||
},
|
||||
"node_modules/postcss-value-parser": {
|
||||
"version": "4.2.0",
|
||||
"resolved": "https://registry.npmjs.org/postcss-value-parser/-/postcss-value-parser-4.2.0.tgz",
|
||||
"integrity": "sha512-1NNCs6uurfkVbeXG4S8JFT9t19m45ICnif8zWLd5oPSZ50QnwMfK+H3jv408d4jw/7Bttv5axS5IiHoLaVNHeQ==",
|
||||
"license": "MIT"
|
||||
},
|
||||
"node_modules/pretty-bytes": {
|
||||
"version": "5.6.0",
|
||||
"resolved": "https://registry.npmjs.org/pretty-bytes/-/pretty-bytes-5.6.0.tgz",
|
||||
@ -6870,6 +6970,18 @@
|
||||
}
|
||||
}
|
||||
},
|
||||
"node_modules/react-dom": {
|
||||
"version": "19.0.0",
|
||||
"resolved": "https://registry.npmjs.org/react-dom/-/react-dom-19.0.0.tgz",
|
||||
"integrity": "sha512-4GV5sHFG0e/0AD4X+ySy6UJd3jVl1iNsNHdpad0qhABJ11twS3TTBnseqsKurKcsNqCEFeGL3uLpVChpIO3QfQ==",
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"scheduler": "^0.25.0"
|
||||
},
|
||||
"peerDependencies": {
|
||||
"react": "^19.0.0"
|
||||
}
|
||||
},
|
||||
"node_modules/react-freeze": {
|
||||
"version": "1.0.4",
|
||||
"resolved": "https://registry.npmjs.org/react-freeze/-/react-freeze-1.0.4.tgz",
|
||||
@ -6958,9 +7070,9 @@
|
||||
}
|
||||
},
|
||||
"node_modules/react-native-gesture-handler": {
|
||||
"version": "2.27.2",
|
||||
"resolved": "https://registry.npmjs.org/react-native-gesture-handler/-/react-native-gesture-handler-2.27.2.tgz",
|
||||
"integrity": "sha512-+kNaY2m7uQu5+5ls8os6z92DTk9expsEAYsaPv30n08mrqX2r64G8iVGDwNWzZcId54+P7RlDnhyszTql0sQ0w==",
|
||||
"version": "2.24.0",
|
||||
"resolved": "https://registry.npmjs.org/react-native-gesture-handler/-/react-native-gesture-handler-2.24.0.tgz",
|
||||
"integrity": "sha512-ZdWyOd1C8axKJHIfYxjJKCcxjWEpUtUWgTOVY2wynbiveSQDm8X/PDyAKXSer/GOtIpjudUbACOndZXCN3vHsw==",
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"@egjs/hammerjs": "^2.0.17",
|
||||
@ -7050,37 +7162,44 @@
|
||||
"license": "MIT"
|
||||
},
|
||||
"node_modules/react-native-reanimated": {
|
||||
"version": "4.0.1",
|
||||
"resolved": "https://registry.npmjs.org/react-native-reanimated/-/react-native-reanimated-4.0.1.tgz",
|
||||
"integrity": "sha512-SZmIpxVd1yijV1MA8KB9S9TUj6JpdU4THjVB0WCkfV9p6F8oR3YxO4e+GRKbNci3mODp7plW095LhjaCB9bqZQ==",
|
||||
"version": "3.17.5",
|
||||
"resolved": "https://registry.npmjs.org/react-native-reanimated/-/react-native-reanimated-3.17.5.tgz",
|
||||
"integrity": "sha512-SxBK7wQfJ4UoWoJqQnmIC7ZjuNgVb9rcY5Xc67upXAFKftWg0rnkknTw6vgwnjRcvYThrjzUVti66XoZdDJGtw==",
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"react-native-is-edge-to-edge": "^1.2.1",
|
||||
"semver": "7.7.2"
|
||||
"@babel/plugin-transform-arrow-functions": "^7.0.0-0",
|
||||
"@babel/plugin-transform-class-properties": "^7.0.0-0",
|
||||
"@babel/plugin-transform-classes": "^7.0.0-0",
|
||||
"@babel/plugin-transform-nullish-coalescing-operator": "^7.0.0-0",
|
||||
"@babel/plugin-transform-optional-chaining": "^7.0.0-0",
|
||||
"@babel/plugin-transform-shorthand-properties": "^7.0.0-0",
|
||||
"@babel/plugin-transform-template-literals": "^7.0.0-0",
|
||||
"@babel/plugin-transform-unicode-regex": "^7.0.0-0",
|
||||
"@babel/preset-typescript": "^7.16.7",
|
||||
"convert-source-map": "^2.0.0",
|
||||
"invariant": "^2.2.4",
|
||||
"react-native-is-edge-to-edge": "1.1.7"
|
||||
},
|
||||
"peerDependencies": {
|
||||
"@babel/core": "^7.0.0-0",
|
||||
"react": "*",
|
||||
"react-native": "*",
|
||||
"react-native-worklets": ">=0.3.0"
|
||||
"react-native": "*"
|
||||
}
|
||||
},
|
||||
"node_modules/react-native-reanimated/node_modules/semver": {
|
||||
"version": "7.7.2",
|
||||
"resolved": "https://registry.npmjs.org/semver/-/semver-7.7.2.tgz",
|
||||
"integrity": "sha512-RF0Fw+rO5AMf9MAyaRXI4AV0Ulj5lMHqVxxdSgiVbixSCXoEmmX/jk0CuJw4+3SqroYO9VoUh+HcuJivvtJemA==",
|
||||
"license": "ISC",
|
||||
"bin": {
|
||||
"semver": "bin/semver.js"
|
||||
},
|
||||
"engines": {
|
||||
"node": ">=10"
|
||||
"node_modules/react-native-reanimated/node_modules/react-native-is-edge-to-edge": {
|
||||
"version": "1.1.7",
|
||||
"resolved": "https://registry.npmjs.org/react-native-is-edge-to-edge/-/react-native-is-edge-to-edge-1.1.7.tgz",
|
||||
"integrity": "sha512-EH6i7E8epJGIcu7KpfXYXiV2JFIYITtq+rVS8uEb+92naMRBdxhTuS8Wn2Q7j9sqyO0B+Xbaaf9VdipIAmGW4w==",
|
||||
"license": "MIT",
|
||||
"peerDependencies": {
|
||||
"react": "*",
|
||||
"react-native": "*"
|
||||
}
|
||||
},
|
||||
"node_modules/react-native-safe-area-context": {
|
||||
"version": "5.5.2",
|
||||
"resolved": "https://registry.npmjs.org/react-native-safe-area-context/-/react-native-safe-area-context-5.5.2.tgz",
|
||||
"integrity": "sha512-t4YVbHa9uAGf+pHMabGrb0uHrD5ogAusSu842oikJ3YKXcYp6iB4PTGl0EZNkUIR3pCnw/CXKn42OCfhsS0JIw==",
|
||||
"version": "5.4.0",
|
||||
"resolved": "https://registry.npmjs.org/react-native-safe-area-context/-/react-native-safe-area-context-5.4.0.tgz",
|
||||
"integrity": "sha512-JaEThVyJcLhA+vU0NU8bZ0a1ih6GiF4faZ+ArZLqpYbL6j7R3caRqj+mE3lEtKCuHgwjLg3bCxLL1GPUJZVqUA==",
|
||||
"license": "MIT",
|
||||
"peerDependencies": {
|
||||
"react": "*",
|
||||
@ -7088,13 +7207,13 @@
|
||||
}
|
||||
},
|
||||
"node_modules/react-native-screens": {
|
||||
"version": "4.13.1",
|
||||
"resolved": "https://registry.npmjs.org/react-native-screens/-/react-native-screens-4.13.1.tgz",
|
||||
"integrity": "sha512-EESsMAtyzYcL3gpAI2NKKiIo+Ew0fnX4P4b3Zy/+MTc6SJIo3foJbZwdIWd/SUBswOf7IYCvWBppg+D8tbwnsw==",
|
||||
"version": "4.11.1",
|
||||
"resolved": "https://registry.npmjs.org/react-native-screens/-/react-native-screens-4.11.1.tgz",
|
||||
"integrity": "sha512-F0zOzRVa3ptZfLpD0J8ROdo+y1fEPw+VBFq1MTY/iyDu08al7qFUO5hLMd+EYMda5VXGaTFCa8q7bOppUszhJw==",
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"react-freeze": "^1.0.0",
|
||||
"react-native-is-edge-to-edge": "^1.2.1",
|
||||
"react-native-is-edge-to-edge": "^1.1.7",
|
||||
"warn-once": "^0.1.0"
|
||||
},
|
||||
"peerDependencies": {
|
||||
@ -7189,30 +7308,38 @@
|
||||
"node": ">=10"
|
||||
}
|
||||
},
|
||||
"node_modules/react-native-worklets": {
|
||||
"version": "0.4.1",
|
||||
"resolved": "https://registry.npmjs.org/react-native-worklets/-/react-native-worklets-0.4.1.tgz",
|
||||
"integrity": "sha512-QXAMZ8jz0sLEoNrc3ej050z6Sd+UJ/Gef4SACeMuoLRinwHIy4uel7XtMPJZMqKhFerkwXZ7Ips5vIjnNyPDBA==",
|
||||
"node_modules/react-native-web": {
|
||||
"version": "0.20.0",
|
||||
"resolved": "https://registry.npmjs.org/react-native-web/-/react-native-web-0.20.0.tgz",
|
||||
"integrity": "sha512-OOSgrw+aON6R3hRosCau/xVxdLzbjEcsLysYedka0ZON4ZZe6n9xgeN9ZkoejhARM36oTlUgHIQqxGutEJ9Wxg==",
|
||||
"license": "MIT",
|
||||
"peer": true,
|
||||
"dependencies": {
|
||||
"@babel/plugin-transform-arrow-functions": "^7.0.0-0",
|
||||
"@babel/plugin-transform-class-properties": "^7.0.0-0",
|
||||
"@babel/plugin-transform-classes": "^7.0.0-0",
|
||||
"@babel/plugin-transform-nullish-coalescing-operator": "^7.0.0-0",
|
||||
"@babel/plugin-transform-optional-chaining": "^7.0.0-0",
|
||||
"@babel/plugin-transform-shorthand-properties": "^7.0.0-0",
|
||||
"@babel/plugin-transform-template-literals": "^7.0.0-0",
|
||||
"@babel/plugin-transform-unicode-regex": "^7.0.0-0",
|
||||
"@babel/preset-typescript": "^7.16.7",
|
||||
"convert-source-map": "^2.0.0"
|
||||
"@babel/runtime": "^7.18.6",
|
||||
"@react-native/normalize-colors": "^0.74.1",
|
||||
"fbjs": "^3.0.4",
|
||||
"inline-style-prefixer": "^7.0.1",
|
||||
"memoize-one": "^6.0.0",
|
||||
"nullthrows": "^1.1.1",
|
||||
"postcss-value-parser": "^4.2.0",
|
||||
"styleq": "^0.1.3"
|
||||
},
|
||||
"peerDependencies": {
|
||||
"@babel/core": "^7.0.0-0",
|
||||
"react": "*",
|
||||
"react-native": "*"
|
||||
"react": "^18.0.0 || ^19.0.0",
|
||||
"react-dom": "^18.0.0 || ^19.0.0"
|
||||
}
|
||||
},
|
||||
"node_modules/react-native-web/node_modules/@react-native/normalize-colors": {
|
||||
"version": "0.74.89",
|
||||
"resolved": "https://registry.npmjs.org/@react-native/normalize-colors/-/normalize-colors-0.74.89.tgz",
|
||||
"integrity": "sha512-qoMMXddVKVhZ8PA1AbUCk83trpd6N+1nF2A6k1i6LsQObyS92fELuk8kU/lQs6M7BsMHwqyLCpQJ1uFgNvIQXg==",
|
||||
"license": "MIT"
|
||||
},
|
||||
"node_modules/react-native-web/node_modules/memoize-one": {
|
||||
"version": "6.0.0",
|
||||
"resolved": "https://registry.npmjs.org/memoize-one/-/memoize-one-6.0.0.tgz",
|
||||
"integrity": "sha512-rkpe71W0N0c0Xz6QD0eJETuWAJGnJ9afsl1srmwPrI+yBCkge5EycXXbYRyvL29zZVUWQCY7InPRCv3GDXuZNw==",
|
||||
"license": "MIT"
|
||||
},
|
||||
"node_modules/react-native/node_modules/@react-native/virtualized-lists": {
|
||||
"version": "0.79.5",
|
||||
"resolved": "https://registry.npmjs.org/@react-native/virtualized-lists/-/virtualized-lists-0.79.5.tgz",
|
||||
@ -7781,6 +7908,12 @@
|
||||
"node": ">= 0.8"
|
||||
}
|
||||
},
|
||||
"node_modules/setimmediate": {
|
||||
"version": "1.0.5",
|
||||
"resolved": "https://registry.npmjs.org/setimmediate/-/setimmediate-1.0.5.tgz",
|
||||
"integrity": "sha512-MATJdZp8sLqDl/68LfQmbP8zKPLQNV6BIZoIgrscFDQ+RsvK/BxeDQOgyxKKoh0y/8h3BqVFnCqQ/gd+reiIXA==",
|
||||
"license": "MIT"
|
||||
},
|
||||
"node_modules/setprototypeof": {
|
||||
"version": "1.2.0",
|
||||
"resolved": "https://registry.npmjs.org/setprototypeof/-/setprototypeof-1.2.0.tgz",
|
||||
@ -8117,6 +8250,12 @@
|
||||
"integrity": "sha512-0MP/Cxx5SzeeZ10p/bZI0S6MpgD+yxAhi1BOQ34jgnMXsCq3j1t6tQnZu+KdlL7dvJTLT3g9xN8tl10TqgFMcg==",
|
||||
"license": "MIT"
|
||||
},
|
||||
"node_modules/styleq": {
|
||||
"version": "0.1.3",
|
||||
"resolved": "https://registry.npmjs.org/styleq/-/styleq-0.1.3.tgz",
|
||||
"integrity": "sha512-3ZUifmCDCQanjeej1f6kyl/BeP/Vae5EYkQ9iJfUm/QwZvlgnZzyflqAsAWYURdtea8Vkvswu2GrC57h3qffcA==",
|
||||
"license": "MIT"
|
||||
},
|
||||
"node_modules/sucrase": {
|
||||
"version": "3.35.0",
|
||||
"resolved": "https://registry.npmjs.org/sucrase/-/sucrase-3.35.0.tgz",
|
||||
@ -8395,6 +8534,12 @@
|
||||
"node": ">=0.6"
|
||||
}
|
||||
},
|
||||
"node_modules/tr46": {
|
||||
"version": "0.0.3",
|
||||
"resolved": "https://registry.npmjs.org/tr46/-/tr46-0.0.3.tgz",
|
||||
"integrity": "sha512-N3WMsuqV66lT30CrXNbEjx4GEwlow3v6rr4mCcv6prnfwhS01rkgyFdjPNBYd9br7LpXV1+Emh01fHnq2Gdgrw==",
|
||||
"license": "MIT"
|
||||
},
|
||||
"node_modules/ts-interface-checker": {
|
||||
"version": "0.1.13",
|
||||
"resolved": "https://registry.npmjs.org/ts-interface-checker/-/ts-interface-checker-0.1.13.tgz",
|
||||
@ -8451,6 +8596,32 @@
|
||||
"node": ">=14.17"
|
||||
}
|
||||
},
|
||||
"node_modules/ua-parser-js": {
|
||||
"version": "1.0.40",
|
||||
"resolved": "https://registry.npmjs.org/ua-parser-js/-/ua-parser-js-1.0.40.tgz",
|
||||
"integrity": "sha512-z6PJ8Lml+v3ichVojCiB8toQJBuwR42ySM4ezjXIqXK3M0HczmKQ3LF4rhU55PfD99KEEXQG6yb7iOMyvYuHew==",
|
||||
"funding": [
|
||||
{
|
||||
"type": "opencollective",
|
||||
"url": "https://opencollective.com/ua-parser-js"
|
||||
},
|
||||
{
|
||||
"type": "paypal",
|
||||
"url": "https://paypal.me/faisalman"
|
||||
},
|
||||
{
|
||||
"type": "github",
|
||||
"url": "https://github.com/sponsors/faisalman"
|
||||
}
|
||||
],
|
||||
"license": "MIT",
|
||||
"bin": {
|
||||
"ua-parser-js": "script/cli.js"
|
||||
},
|
||||
"engines": {
|
||||
"node": "*"
|
||||
}
|
||||
},
|
||||
"node_modules/undici": {
|
||||
"version": "6.21.3",
|
||||
"resolved": "https://registry.npmjs.org/undici/-/undici-6.21.3.tgz",
|
||||
@ -8656,6 +8827,16 @@
|
||||
"integrity": "sha512-EqhiFU6daOA8kpjOWTL0olhVOF3i7OrFzSYiGsEMB8GcXS+RrzauAERX65xMeNWVqxA6HXH2m69Z9LaKKdisfg==",
|
||||
"license": "MIT"
|
||||
},
|
||||
"node_modules/whatwg-url": {
|
||||
"version": "5.0.0",
|
||||
"resolved": "https://registry.npmjs.org/whatwg-url/-/whatwg-url-5.0.0.tgz",
|
||||
"integrity": "sha512-saE57nupxk6v3HY35+jzBwYa0rKSy0XR8JSxZPwgLr7ys0IBzhGviA1/TUGJLmSVqs8pb9AnvICXEuOHLprYTw==",
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"tr46": "~0.0.3",
|
||||
"webidl-conversions": "^3.0.0"
|
||||
}
|
||||
},
|
||||
"node_modules/whatwg-url-without-unicode": {
|
||||
"version": "8.0.0-3",
|
||||
"resolved": "https://registry.npmjs.org/whatwg-url-without-unicode/-/whatwg-url-without-unicode-8.0.0-3.tgz",
|
||||
@ -8670,6 +8851,12 @@
|
||||
"node": ">=10"
|
||||
}
|
||||
},
|
||||
"node_modules/whatwg-url/node_modules/webidl-conversions": {
|
||||
"version": "3.0.1",
|
||||
"resolved": "https://registry.npmjs.org/webidl-conversions/-/webidl-conversions-3.0.1.tgz",
|
||||
"integrity": "sha512-2JAn3z8AR6rjK8Sm8orRC0h/bcl/DqL7tRPdGZ4I1CjdF+EaMLmYxBHyXuKL849eucPFhvBoxMsflfOb8kxaeQ==",
|
||||
"license": "BSD-2-Clause"
|
||||
},
|
||||
"node_modules/which": {
|
||||
"version": "2.0.2",
|
||||
"resolved": "https://registry.npmjs.org/which/-/which-2.0.2.tgz",
|
||||
|
@ -10,7 +10,7 @@
|
||||
},
|
||||
"dependencies": {
|
||||
"@apollo/client": "^3.13.9",
|
||||
"@react-native-async-storage/async-storage": "^2.2.0",
|
||||
"@react-native-async-storage/async-storage": "2.1.2",
|
||||
"@react-navigation/bottom-tabs": "^7.4.5",
|
||||
"@react-navigation/native": "^7.1.17",
|
||||
"@react-navigation/native-stack": "^7.3.24",
|
||||
@ -20,13 +20,16 @@
|
||||
"graphql": "^16.11.0",
|
||||
"react": "19.0.0",
|
||||
"react-native": "0.79.5",
|
||||
"react-native-gesture-handler": "^2.27.2",
|
||||
"react-native-gesture-handler": "~2.24.0",
|
||||
"react-native-keyboard-aware-scroll-view": "^0.9.5",
|
||||
"react-native-paper": "^5.14.5",
|
||||
"react-native-reanimated": "^4.0.1",
|
||||
"react-native-safe-area-context": "^5.5.2",
|
||||
"react-native-screens": "^4.13.1",
|
||||
"react-native-vector-icons": "^10.3.0"
|
||||
"react-native-reanimated": "~3.17.4",
|
||||
"react-native-safe-area-context": "5.4.0",
|
||||
"react-native-screens": "~4.11.1",
|
||||
"react-native-vector-icons": "^10.3.0",
|
||||
"react-dom": "19.0.0",
|
||||
"react-native-web": "^0.20.0",
|
||||
"@expo/metro-runtime": "~5.0.4"
|
||||
},
|
||||
"devDependencies": {
|
||||
"@babel/core": "^7.25.2",
|
||||
|
@ -1,9 +1,23 @@
|
||||
import React, { useState } from 'react';
|
||||
import { View, StyleSheet, KeyboardAvoidingView, Platform, ScrollView } from 'react-native';
|
||||
import React, { useState, useEffect } from 'react';
|
||||
import { View, StyleSheet, KeyboardAvoidingView, Platform, ScrollView, Dimensions } from 'react-native';
|
||||
import { TextInput, Button, Text, Headline, HelperText } from 'react-native-paper';
|
||||
import { useMutation } from '@apollo/client';
|
||||
import { LOGIN } from '../graphql/mutations';
|
||||
import { useAuth } from '../contexts/AuthContext';
|
||||
import Animated, {
|
||||
useSharedValue,
|
||||
useAnimatedStyle,
|
||||
withTiming,
|
||||
withSpring,
|
||||
withDelay,
|
||||
withSequence,
|
||||
withRepeat,
|
||||
interpolate,
|
||||
Easing,
|
||||
} from 'react-native-reanimated';
|
||||
|
||||
const { width: screenWidth } = Dimensions.get('window');
|
||||
const AnimatedView = Animated.View;
|
||||
|
||||
export const LoginScreen = ({ navigation }: any) => {
|
||||
const [username, setUsername] = useState('');
|
||||
@ -11,6 +25,32 @@ export const LoginScreen = ({ navigation }: any) => {
|
||||
const [showPassword, setShowPassword] = useState(false);
|
||||
const { login } = useAuth();
|
||||
|
||||
// Анимации
|
||||
const translateY = useSharedValue(50);
|
||||
const opacity = useSharedValue(0);
|
||||
const scale = useSharedValue(0.9);
|
||||
const glowAnimation = useSharedValue(0);
|
||||
const buttonScale = useSharedValue(1);
|
||||
const inputFocusAnimation1 = useSharedValue(0);
|
||||
const inputFocusAnimation2 = useSharedValue(0);
|
||||
|
||||
useEffect(() => {
|
||||
// Анимация появления
|
||||
translateY.value = withSpring(0, { damping: 15, stiffness: 100 });
|
||||
opacity.value = withTiming(1, { duration: 800 });
|
||||
scale.value = withSpring(1, { damping: 15, stiffness: 100 });
|
||||
|
||||
// Пульсирующее свечение
|
||||
glowAnimation.value = withRepeat(
|
||||
withSequence(
|
||||
withTiming(1, { duration: 2000, easing: Easing.inOut(Easing.ease) }),
|
||||
withTiming(0, { duration: 2000, easing: Easing.inOut(Easing.ease) })
|
||||
),
|
||||
-1,
|
||||
false
|
||||
);
|
||||
}, []);
|
||||
|
||||
const [loginMutation, { loading, error }] = useMutation(LOGIN, {
|
||||
onCompleted: async (data) => {
|
||||
await login(data.login.access_token, data.login.user);
|
||||
@ -25,66 +65,173 @@ export const LoginScreen = ({ navigation }: any) => {
|
||||
loginMutation({ variables: { username, password } });
|
||||
};
|
||||
|
||||
// Анимированные стили
|
||||
const containerAnimatedStyle = useAnimatedStyle(() => {
|
||||
return {
|
||||
transform: [
|
||||
{ translateY: translateY.value },
|
||||
{ scale: scale.value }
|
||||
],
|
||||
opacity: opacity.value,
|
||||
};
|
||||
});
|
||||
|
||||
const glowContainerStyle = useAnimatedStyle(() => {
|
||||
const glowOpacity = interpolate(glowAnimation.value, [0, 1], [0.3, 0.8]);
|
||||
const shadowRadius = interpolate(glowAnimation.value, [0, 1], [10, 30]);
|
||||
|
||||
return {
|
||||
shadowOpacity: glowOpacity,
|
||||
shadowRadius: shadowRadius,
|
||||
elevation: interpolate(glowAnimation.value, [0, 1], [5, 15]),
|
||||
};
|
||||
});
|
||||
|
||||
const buttonAnimatedStyle = useAnimatedStyle(() => {
|
||||
return {
|
||||
transform: [{ scale: buttonScale.value }],
|
||||
};
|
||||
});
|
||||
|
||||
const createInputAnimatedStyle = (focusAnimation: any) => {
|
||||
return useAnimatedStyle(() => {
|
||||
const borderWidth = interpolate(focusAnimation.value, [0, 1], [1, 2]);
|
||||
const shadowOpacity = interpolate(focusAnimation.value, [0, 1], [0, 0.6]);
|
||||
|
||||
return {
|
||||
borderWidth: borderWidth,
|
||||
shadowOpacity: shadowOpacity,
|
||||
elevation: interpolate(focusAnimation.value, [0, 1], [2, 8]),
|
||||
};
|
||||
});
|
||||
};
|
||||
|
||||
const inputStyle1 = createInputAnimatedStyle(inputFocusAnimation1);
|
||||
const inputStyle2 = createInputAnimatedStyle(inputFocusAnimation2);
|
||||
|
||||
const handleButtonPressIn = () => {
|
||||
buttonScale.value = withSpring(0.95);
|
||||
};
|
||||
|
||||
const handleButtonPressOut = () => {
|
||||
buttonScale.value = withSpring(1);
|
||||
};
|
||||
|
||||
return (
|
||||
<KeyboardAvoidingView
|
||||
style={styles.container}
|
||||
behavior={Platform.OS === 'ios' ? 'padding' : 'height'}
|
||||
>
|
||||
<ScrollView contentContainerStyle={styles.scrollContent}>
|
||||
<View style={styles.content}>
|
||||
<Headline style={styles.title}>Вход в Prism</Headline>
|
||||
<Animated.View style={[styles.content, containerAnimatedStyle]}>
|
||||
<Animated.View style={[styles.glowContainer, glowContainerStyle]}>
|
||||
<Headline style={styles.title}>Вход в Prism</Headline>
|
||||
<Text style={styles.subtitle}>Добро пожаловать обратно</Text>
|
||||
</Animated.View>
|
||||
|
||||
<TextInput
|
||||
label="Имя пользователя"
|
||||
value={username}
|
||||
onChangeText={setUsername}
|
||||
mode="outlined"
|
||||
style={styles.input}
|
||||
autoCapitalize="none"
|
||||
disabled={loading}
|
||||
/>
|
||||
<AnimatedView style={inputStyle1}>
|
||||
<TextInput
|
||||
label="Имя пользователя"
|
||||
value={username}
|
||||
onChangeText={setUsername}
|
||||
mode="outlined"
|
||||
style={styles.input}
|
||||
autoCapitalize="none"
|
||||
disabled={loading}
|
||||
theme={{
|
||||
colors: {
|
||||
primary: '#9333ea',
|
||||
placeholder: '#a855f7',
|
||||
text: '#ffffff',
|
||||
background: '#1a1a2e',
|
||||
outline: '#7c3aed',
|
||||
}
|
||||
}}
|
||||
onFocus={() => {
|
||||
inputFocusAnimation1.value = withSpring(1);
|
||||
}}
|
||||
onBlur={() => {
|
||||
inputFocusAnimation1.value = withSpring(0);
|
||||
}}
|
||||
/>
|
||||
</AnimatedView>
|
||||
|
||||
<TextInput
|
||||
label="Пароль"
|
||||
value={password}
|
||||
onChangeText={setPassword}
|
||||
mode="outlined"
|
||||
style={styles.input}
|
||||
secureTextEntry={!showPassword}
|
||||
disabled={loading}
|
||||
right={
|
||||
<TextInput.Icon
|
||||
icon={showPassword ? 'eye-off' : 'eye'}
|
||||
onPress={() => setShowPassword(!showPassword)}
|
||||
/>
|
||||
}
|
||||
/>
|
||||
<AnimatedView style={inputStyle2}>
|
||||
<TextInput
|
||||
label="Пароль"
|
||||
value={password}
|
||||
onChangeText={setPassword}
|
||||
mode="outlined"
|
||||
style={styles.input}
|
||||
secureTextEntry={!showPassword}
|
||||
disabled={loading}
|
||||
theme={{
|
||||
colors: {
|
||||
primary: '#9333ea',
|
||||
placeholder: '#a855f7',
|
||||
text: '#ffffff',
|
||||
background: '#1a1a2e',
|
||||
outline: '#7c3aed',
|
||||
}
|
||||
}}
|
||||
right={
|
||||
<TextInput.Icon
|
||||
icon={showPassword ? 'eye-off' : 'eye'}
|
||||
onPress={() => setShowPassword(!showPassword)}
|
||||
color="#a855f7"
|
||||
/>
|
||||
}
|
||||
onFocus={() => {
|
||||
inputFocusAnimation2.value = withSpring(1);
|
||||
}}
|
||||
onBlur={() => {
|
||||
inputFocusAnimation2.value = withSpring(0);
|
||||
}}
|
||||
/>
|
||||
</AnimatedView>
|
||||
|
||||
{error && (
|
||||
<HelperText type="error" visible={true}>
|
||||
<HelperText type="error" visible={true} style={styles.errorText}>
|
||||
{error.message}
|
||||
</HelperText>
|
||||
)}
|
||||
|
||||
<Button
|
||||
mode="contained"
|
||||
onPress={handleLogin}
|
||||
loading={loading}
|
||||
disabled={loading || !username || !password}
|
||||
style={styles.button}
|
||||
>
|
||||
Войти
|
||||
</Button>
|
||||
<AnimatedView style={buttonAnimatedStyle}>
|
||||
<Button
|
||||
mode="contained"
|
||||
onPress={handleLogin}
|
||||
onPressIn={handleButtonPressIn}
|
||||
onPressOut={handleButtonPressOut}
|
||||
loading={loading}
|
||||
disabled={loading || !username || !password}
|
||||
style={styles.button}
|
||||
contentStyle={styles.buttonContent}
|
||||
labelStyle={styles.buttonLabel}
|
||||
theme={{
|
||||
colors: {
|
||||
primary: '#9333ea',
|
||||
}
|
||||
}}
|
||||
>
|
||||
Войти
|
||||
</Button>
|
||||
</AnimatedView>
|
||||
|
||||
<Button
|
||||
mode="text"
|
||||
onPress={() => navigation.navigate('Register')}
|
||||
disabled={loading}
|
||||
style={styles.linkButton}
|
||||
labelStyle={styles.linkButtonLabel}
|
||||
theme={{
|
||||
colors: {
|
||||
primary: '#a855f7',
|
||||
}
|
||||
}}
|
||||
>
|
||||
Нет аккаунта? Зарегистрироваться
|
||||
</Button>
|
||||
</View>
|
||||
</Animated.View>
|
||||
</ScrollView>
|
||||
</KeyboardAvoidingView>
|
||||
);
|
||||
@ -93,11 +240,12 @@ export const LoginScreen = ({ navigation }: any) => {
|
||||
const styles = StyleSheet.create({
|
||||
container: {
|
||||
flex: 1,
|
||||
backgroundColor: '#f5f5f5',
|
||||
backgroundColor: '#0a0a0f',
|
||||
},
|
||||
scrollContent: {
|
||||
flexGrow: 1,
|
||||
justifyContent: 'center',
|
||||
paddingVertical: 40,
|
||||
},
|
||||
content: {
|
||||
padding: 20,
|
||||
@ -105,18 +253,90 @@ const styles = StyleSheet.create({
|
||||
width: '100%',
|
||||
alignSelf: 'center',
|
||||
},
|
||||
glowContainer: {
|
||||
marginBottom: 40,
|
||||
padding: 20,
|
||||
borderRadius: 20,
|
||||
backgroundColor: '#1a1a2e',
|
||||
// iOS тени
|
||||
shadowColor: '#9333ea',
|
||||
shadowOffset: {
|
||||
width: 0,
|
||||
height: 0,
|
||||
},
|
||||
shadowOpacity: 0.5,
|
||||
shadowRadius: 20,
|
||||
// Android тень
|
||||
elevation: 10,
|
||||
},
|
||||
title: {
|
||||
textAlign: 'center',
|
||||
marginBottom: 30,
|
||||
marginBottom: 10,
|
||||
fontSize: 32,
|
||||
fontWeight: 'bold',
|
||||
color: '#ffffff',
|
||||
textShadowColor: '#9333ea',
|
||||
textShadowOffset: { width: 0, height: 0 },
|
||||
textShadowRadius: 10,
|
||||
},
|
||||
subtitle: {
|
||||
textAlign: 'center',
|
||||
fontSize: 16,
|
||||
color: '#a855f7',
|
||||
fontStyle: 'italic',
|
||||
},
|
||||
input: {
|
||||
marginBottom: 15,
|
||||
marginBottom: 20,
|
||||
backgroundColor: '#1a1a2e',
|
||||
borderRadius: 12,
|
||||
// iOS тени
|
||||
shadowColor: '#7c3aed',
|
||||
shadowOffset: {
|
||||
width: 0,
|
||||
height: 4,
|
||||
},
|
||||
shadowOpacity: 0.3,
|
||||
shadowRadius: 6,
|
||||
// Android тень
|
||||
elevation: 5,
|
||||
},
|
||||
button: {
|
||||
marginTop: 10,
|
||||
marginTop: 20,
|
||||
marginBottom: 10,
|
||||
borderRadius: 12,
|
||||
backgroundColor: '#9333ea',
|
||||
// iOS тени для кнопки
|
||||
shadowColor: '#9333ea',
|
||||
shadowOffset: {
|
||||
width: 0,
|
||||
height: 8,
|
||||
},
|
||||
shadowOpacity: 0.6,
|
||||
shadowRadius: 15,
|
||||
// Android тень
|
||||
elevation: 10,
|
||||
},
|
||||
buttonContent: {
|
||||
paddingVertical: 8,
|
||||
},
|
||||
buttonLabel: {
|
||||
fontSize: 18,
|
||||
fontWeight: 'bold',
|
||||
letterSpacing: 1,
|
||||
},
|
||||
linkButton: {
|
||||
marginTop: 10,
|
||||
},
|
||||
linkButtonLabel: {
|
||||
fontSize: 16,
|
||||
color: '#a855f7',
|
||||
},
|
||||
errorText: {
|
||||
color: '#ef4444',
|
||||
textAlign: 'center',
|
||||
marginBottom: 10,
|
||||
textShadowColor: '#ef4444',
|
||||
textShadowOffset: { width: 0, height: 0 },
|
||||
textShadowRadius: 5,
|
||||
},
|
||||
});
|
@ -1,9 +1,23 @@
|
||||
import React, { useState } from 'react';
|
||||
import { View, StyleSheet, KeyboardAvoidingView, Platform, ScrollView } from 'react-native';
|
||||
import React, { useState, useEffect } from 'react';
|
||||
import { View, StyleSheet, KeyboardAvoidingView, Platform, ScrollView, Dimensions } from 'react-native';
|
||||
import { TextInput, Button, Text, Headline, HelperText } from 'react-native-paper';
|
||||
import { useMutation } from '@apollo/client';
|
||||
import { REGISTER } from '../graphql/mutations';
|
||||
import { useAuth } from '../contexts/AuthContext';
|
||||
import Animated, {
|
||||
useSharedValue,
|
||||
useAnimatedStyle,
|
||||
withTiming,
|
||||
withSpring,
|
||||
withDelay,
|
||||
withSequence,
|
||||
withRepeat,
|
||||
interpolate,
|
||||
Easing,
|
||||
} from 'react-native-reanimated';
|
||||
|
||||
const { width: screenWidth } = Dimensions.get('window');
|
||||
const AnimatedView = Animated.View;
|
||||
|
||||
export const RegisterScreen = ({ navigation }: any) => {
|
||||
const [username, setUsername] = useState('');
|
||||
@ -13,6 +27,34 @@ export const RegisterScreen = ({ navigation }: any) => {
|
||||
const [showPassword, setShowPassword] = useState(false);
|
||||
const { login } = useAuth();
|
||||
|
||||
// Анимации
|
||||
const translateY = useSharedValue(50);
|
||||
const opacity = useSharedValue(0);
|
||||
const scale = useSharedValue(0.9);
|
||||
const glowAnimation = useSharedValue(0);
|
||||
const buttonScale = useSharedValue(1);
|
||||
const inputFocusAnimation1 = useSharedValue(0);
|
||||
const inputFocusAnimation2 = useSharedValue(0);
|
||||
const inputFocusAnimation3 = useSharedValue(0);
|
||||
const inputFocusAnimation4 = useSharedValue(0);
|
||||
|
||||
useEffect(() => {
|
||||
// Анимация появления
|
||||
translateY.value = withSpring(0, { damping: 15, stiffness: 100 });
|
||||
opacity.value = withTiming(1, { duration: 800 });
|
||||
scale.value = withSpring(1, { damping: 15, stiffness: 100 });
|
||||
|
||||
// Пульсирующее свечение
|
||||
glowAnimation.value = withRepeat(
|
||||
withSequence(
|
||||
withTiming(1, { duration: 2000, easing: Easing.inOut(Easing.ease) }),
|
||||
withTiming(0, { duration: 2000, easing: Easing.inOut(Easing.ease) })
|
||||
),
|
||||
-1,
|
||||
false
|
||||
);
|
||||
}, []);
|
||||
|
||||
const [registerMutation, { loading, error }] = useMutation(REGISTER, {
|
||||
onCompleted: async (data) => {
|
||||
await login(data.register.access_token, data.register.user);
|
||||
@ -29,94 +71,238 @@ export const RegisterScreen = ({ navigation }: any) => {
|
||||
|
||||
const passwordsMatch = password === confirmPassword || confirmPassword === '';
|
||||
|
||||
// Анимированные стили
|
||||
const containerAnimatedStyle = useAnimatedStyle(() => {
|
||||
return {
|
||||
transform: [
|
||||
{ translateY: translateY.value },
|
||||
{ scale: scale.value }
|
||||
],
|
||||
opacity: opacity.value,
|
||||
};
|
||||
});
|
||||
|
||||
const glowContainerStyle = useAnimatedStyle(() => {
|
||||
const glowOpacity = interpolate(glowAnimation.value, [0, 1], [0.3, 0.8]);
|
||||
const shadowRadius = interpolate(glowAnimation.value, [0, 1], [10, 30]);
|
||||
|
||||
return {
|
||||
shadowOpacity: glowOpacity,
|
||||
shadowRadius: shadowRadius,
|
||||
elevation: interpolate(glowAnimation.value, [0, 1], [5, 15]),
|
||||
};
|
||||
});
|
||||
|
||||
const buttonAnimatedStyle = useAnimatedStyle(() => {
|
||||
return {
|
||||
transform: [{ scale: buttonScale.value }],
|
||||
};
|
||||
});
|
||||
|
||||
const createInputAnimatedStyle = (focusAnimation: any) => {
|
||||
return useAnimatedStyle(() => {
|
||||
const borderWidth = interpolate(focusAnimation.value, [0, 1], [1, 2]);
|
||||
const shadowOpacity = interpolate(focusAnimation.value, [0, 1], [0, 0.6]);
|
||||
|
||||
return {
|
||||
borderWidth: borderWidth,
|
||||
shadowOpacity: shadowOpacity,
|
||||
elevation: interpolate(focusAnimation.value, [0, 1], [2, 8]),
|
||||
};
|
||||
});
|
||||
};
|
||||
|
||||
const inputStyle1 = createInputAnimatedStyle(inputFocusAnimation1);
|
||||
const inputStyle2 = createInputAnimatedStyle(inputFocusAnimation2);
|
||||
const inputStyle3 = createInputAnimatedStyle(inputFocusAnimation3);
|
||||
const inputStyle4 = createInputAnimatedStyle(inputFocusAnimation4);
|
||||
|
||||
const handleButtonPressIn = () => {
|
||||
buttonScale.value = withSpring(0.95);
|
||||
};
|
||||
|
||||
const handleButtonPressOut = () => {
|
||||
buttonScale.value = withSpring(1);
|
||||
};
|
||||
|
||||
return (
|
||||
<KeyboardAvoidingView
|
||||
style={styles.container}
|
||||
behavior={Platform.OS === 'ios' ? 'padding' : 'height'}
|
||||
>
|
||||
<ScrollView contentContainerStyle={styles.scrollContent}>
|
||||
<View style={styles.content}>
|
||||
<Headline style={styles.title}>Регистрация в Prism</Headline>
|
||||
<Animated.View style={[styles.content, containerAnimatedStyle]}>
|
||||
<Animated.View style={[styles.glowContainer, glowContainerStyle]}>
|
||||
<Headline style={styles.title}>Регистрация в Prism</Headline>
|
||||
<Text style={styles.subtitle}>Присоединяйтесь к будущему</Text>
|
||||
</Animated.View>
|
||||
|
||||
<TextInput
|
||||
label="Имя пользователя"
|
||||
value={username}
|
||||
onChangeText={setUsername}
|
||||
mode="outlined"
|
||||
style={styles.input}
|
||||
autoCapitalize="none"
|
||||
disabled={loading}
|
||||
/>
|
||||
<AnimatedView style={inputStyle1}>
|
||||
<TextInput
|
||||
label="Имя пользователя"
|
||||
value={username}
|
||||
onChangeText={setUsername}
|
||||
mode="outlined"
|
||||
style={styles.input}
|
||||
autoCapitalize="none"
|
||||
disabled={loading}
|
||||
theme={{
|
||||
colors: {
|
||||
primary: '#9333ea',
|
||||
placeholder: '#a855f7',
|
||||
text: '#ffffff',
|
||||
background: '#1a1a2e',
|
||||
outline: '#7c3aed',
|
||||
}
|
||||
}}
|
||||
onFocus={() => {
|
||||
inputFocusAnimation1.value = withSpring(1);
|
||||
}}
|
||||
onBlur={() => {
|
||||
inputFocusAnimation1.value = withSpring(0);
|
||||
}}
|
||||
/>
|
||||
</AnimatedView>
|
||||
|
||||
<TextInput
|
||||
label="Email"
|
||||
value={email}
|
||||
onChangeText={setEmail}
|
||||
mode="outlined"
|
||||
style={styles.input}
|
||||
keyboardType="email-address"
|
||||
autoCapitalize="none"
|
||||
disabled={loading}
|
||||
/>
|
||||
<AnimatedView style={inputStyle2}>
|
||||
<TextInput
|
||||
label="Email"
|
||||
value={email}
|
||||
onChangeText={setEmail}
|
||||
mode="outlined"
|
||||
style={styles.input}
|
||||
keyboardType="email-address"
|
||||
autoCapitalize="none"
|
||||
disabled={loading}
|
||||
theme={{
|
||||
colors: {
|
||||
primary: '#9333ea',
|
||||
placeholder: '#a855f7',
|
||||
text: '#ffffff',
|
||||
background: '#1a1a2e',
|
||||
outline: '#7c3aed',
|
||||
}
|
||||
}}
|
||||
onFocus={() => {
|
||||
inputFocusAnimation2.value = withSpring(1);
|
||||
}}
|
||||
onBlur={() => {
|
||||
inputFocusAnimation2.value = withSpring(0);
|
||||
}}
|
||||
/>
|
||||
</AnimatedView>
|
||||
|
||||
<TextInput
|
||||
label="Пароль"
|
||||
value={password}
|
||||
onChangeText={setPassword}
|
||||
mode="outlined"
|
||||
style={styles.input}
|
||||
secureTextEntry={!showPassword}
|
||||
disabled={loading}
|
||||
right={
|
||||
<TextInput.Icon
|
||||
icon={showPassword ? 'eye-off' : 'eye'}
|
||||
onPress={() => setShowPassword(!showPassword)}
|
||||
/>
|
||||
}
|
||||
/>
|
||||
<AnimatedView style={inputStyle3}>
|
||||
<TextInput
|
||||
label="Пароль"
|
||||
value={password}
|
||||
onChangeText={setPassword}
|
||||
mode="outlined"
|
||||
style={styles.input}
|
||||
secureTextEntry={!showPassword}
|
||||
disabled={loading}
|
||||
theme={{
|
||||
colors: {
|
||||
primary: '#9333ea',
|
||||
placeholder: '#a855f7',
|
||||
text: '#ffffff',
|
||||
background: '#1a1a2e',
|
||||
outline: '#7c3aed',
|
||||
}
|
||||
}}
|
||||
right={
|
||||
<TextInput.Icon
|
||||
icon={showPassword ? 'eye-off' : 'eye'}
|
||||
onPress={() => setShowPassword(!showPassword)}
|
||||
color="#a855f7"
|
||||
/>
|
||||
}
|
||||
onFocus={() => {
|
||||
inputFocusAnimation3.value = withSpring(1);
|
||||
}}
|
||||
onBlur={() => {
|
||||
inputFocusAnimation3.value = withSpring(0);
|
||||
}}
|
||||
/>
|
||||
</AnimatedView>
|
||||
|
||||
<TextInput
|
||||
label="Подтвердите пароль"
|
||||
value={confirmPassword}
|
||||
onChangeText={setConfirmPassword}
|
||||
mode="outlined"
|
||||
style={styles.input}
|
||||
secureTextEntry={!showPassword}
|
||||
disabled={loading}
|
||||
error={!passwordsMatch}
|
||||
/>
|
||||
<AnimatedView style={inputStyle4}>
|
||||
<TextInput
|
||||
label="Подтвердите пароль"
|
||||
value={confirmPassword}
|
||||
onChangeText={setConfirmPassword}
|
||||
mode="outlined"
|
||||
style={styles.input}
|
||||
secureTextEntry={!showPassword}
|
||||
disabled={loading}
|
||||
error={!passwordsMatch}
|
||||
theme={{
|
||||
colors: {
|
||||
primary: '#9333ea',
|
||||
placeholder: '#a855f7',
|
||||
text: '#ffffff',
|
||||
background: '#1a1a2e',
|
||||
outline: '#7c3aed',
|
||||
error: '#ef4444',
|
||||
}
|
||||
}}
|
||||
onFocus={() => {
|
||||
inputFocusAnimation4.value = withSpring(1);
|
||||
}}
|
||||
onBlur={() => {
|
||||
inputFocusAnimation4.value = withSpring(0);
|
||||
}}
|
||||
/>
|
||||
</AnimatedView>
|
||||
|
||||
{!passwordsMatch && (
|
||||
<HelperText type="error" visible={true}>
|
||||
<HelperText type="error" visible={true} style={styles.errorText}>
|
||||
Пароли не совпадают
|
||||
</HelperText>
|
||||
)}
|
||||
|
||||
{error && (
|
||||
<HelperText type="error" visible={true}>
|
||||
<HelperText type="error" visible={true} style={styles.errorText}>
|
||||
{error.message}
|
||||
</HelperText>
|
||||
)}
|
||||
|
||||
<Button
|
||||
mode="contained"
|
||||
onPress={handleRegister}
|
||||
loading={loading}
|
||||
disabled={loading || !username || !email || !password || !passwordsMatch}
|
||||
style={styles.button}
|
||||
>
|
||||
Зарегистрироваться
|
||||
</Button>
|
||||
<AnimatedView style={buttonAnimatedStyle}>
|
||||
<Button
|
||||
mode="contained"
|
||||
onPress={handleRegister}
|
||||
onPressIn={handleButtonPressIn}
|
||||
onPressOut={handleButtonPressOut}
|
||||
loading={loading}
|
||||
disabled={loading || !username || !email || !password || !passwordsMatch}
|
||||
style={styles.button}
|
||||
contentStyle={styles.buttonContent}
|
||||
labelStyle={styles.buttonLabel}
|
||||
theme={{
|
||||
colors: {
|
||||
primary: '#9333ea',
|
||||
}
|
||||
}}
|
||||
>
|
||||
Зарегистрироваться
|
||||
</Button>
|
||||
</AnimatedView>
|
||||
|
||||
<Button
|
||||
mode="text"
|
||||
onPress={() => navigation.navigate('Login')}
|
||||
disabled={loading}
|
||||
style={styles.linkButton}
|
||||
labelStyle={styles.linkButtonLabel}
|
||||
theme={{
|
||||
colors: {
|
||||
primary: '#a855f7',
|
||||
}
|
||||
}}
|
||||
>
|
||||
Уже есть аккаунт? Войти
|
||||
</Button>
|
||||
</View>
|
||||
</Animated.View>
|
||||
</ScrollView>
|
||||
</KeyboardAvoidingView>
|
||||
);
|
||||
@ -125,11 +311,12 @@ export const RegisterScreen = ({ navigation }: any) => {
|
||||
const styles = StyleSheet.create({
|
||||
container: {
|
||||
flex: 1,
|
||||
backgroundColor: '#f5f5f5',
|
||||
backgroundColor: '#0a0a0f',
|
||||
},
|
||||
scrollContent: {
|
||||
flexGrow: 1,
|
||||
justifyContent: 'center',
|
||||
paddingVertical: 40,
|
||||
},
|
||||
content: {
|
||||
padding: 20,
|
||||
@ -137,18 +324,90 @@ const styles = StyleSheet.create({
|
||||
width: '100%',
|
||||
alignSelf: 'center',
|
||||
},
|
||||
glowContainer: {
|
||||
marginBottom: 40,
|
||||
padding: 20,
|
||||
borderRadius: 20,
|
||||
backgroundColor: '#1a1a2e',
|
||||
// iOS тени
|
||||
shadowColor: '#9333ea',
|
||||
shadowOffset: {
|
||||
width: 0,
|
||||
height: 0,
|
||||
},
|
||||
shadowOpacity: 0.5,
|
||||
shadowRadius: 20,
|
||||
// Android тень
|
||||
elevation: 10,
|
||||
},
|
||||
title: {
|
||||
textAlign: 'center',
|
||||
marginBottom: 30,
|
||||
marginBottom: 10,
|
||||
fontSize: 32,
|
||||
fontWeight: 'bold',
|
||||
color: '#ffffff',
|
||||
textShadowColor: '#9333ea',
|
||||
textShadowOffset: { width: 0, height: 0 },
|
||||
textShadowRadius: 10,
|
||||
},
|
||||
subtitle: {
|
||||
textAlign: 'center',
|
||||
fontSize: 16,
|
||||
color: '#a855f7',
|
||||
fontStyle: 'italic',
|
||||
},
|
||||
input: {
|
||||
marginBottom: 15,
|
||||
marginBottom: 20,
|
||||
backgroundColor: '#1a1a2e',
|
||||
borderRadius: 12,
|
||||
// iOS тени
|
||||
shadowColor: '#7c3aed',
|
||||
shadowOffset: {
|
||||
width: 0,
|
||||
height: 4,
|
||||
},
|
||||
shadowOpacity: 0.3,
|
||||
shadowRadius: 6,
|
||||
// Android тень
|
||||
elevation: 5,
|
||||
},
|
||||
button: {
|
||||
marginTop: 10,
|
||||
marginTop: 20,
|
||||
marginBottom: 10,
|
||||
borderRadius: 12,
|
||||
backgroundColor: '#9333ea',
|
||||
// iOS тени для кнопки
|
||||
shadowColor: '#9333ea',
|
||||
shadowOffset: {
|
||||
width: 0,
|
||||
height: 8,
|
||||
},
|
||||
shadowOpacity: 0.6,
|
||||
shadowRadius: 15,
|
||||
// Android тень
|
||||
elevation: 10,
|
||||
},
|
||||
buttonContent: {
|
||||
paddingVertical: 8,
|
||||
},
|
||||
buttonLabel: {
|
||||
fontSize: 18,
|
||||
fontWeight: 'bold',
|
||||
letterSpacing: 1,
|
||||
},
|
||||
linkButton: {
|
||||
marginTop: 10,
|
||||
},
|
||||
linkButtonLabel: {
|
||||
fontSize: 16,
|
||||
color: '#a855f7',
|
||||
},
|
||||
errorText: {
|
||||
color: '#ef4444',
|
||||
textAlign: 'center',
|
||||
marginBottom: 10,
|
||||
textShadowColor: '#ef4444',
|
||||
textShadowOffset: { width: 0, height: 0 },
|
||||
textShadowRadius: 5,
|
||||
},
|
||||
});
|
Reference in New Issue
Block a user