init 1
2
.eslintignore
Normal file
@@ -0,0 +1,2 @@
|
||||
/.expo
|
||||
node_modules
|
||||
7
.eslintrc.js
Normal file
@@ -0,0 +1,7 @@
|
||||
module.exports = {
|
||||
root: true,
|
||||
extends: ['expo', 'eslint:recommended', 'universe/native'],
|
||||
rules: {
|
||||
'react-hooks/exhaustive-deps': 'warn',
|
||||
},
|
||||
}
|
||||
89
.github/workflows/codeql.yml
vendored
Normal file
@@ -0,0 +1,89 @@
|
||||
# For most projects, this workflow file will not need changing; you simply need
|
||||
# to commit it to your repository.
|
||||
#
|
||||
# You may wish to alter this file to override the set of languages analyzed,
|
||||
# or to provide custom queries or build logic.
|
||||
#
|
||||
# ******** NOTE ********
|
||||
# We have attempted to detect the languages in your repository. Please check
|
||||
# the `language` matrix defined below to confirm you have the correct set of
|
||||
# supported CodeQL languages.
|
||||
#
|
||||
name: 'CodeQL'
|
||||
|
||||
on:
|
||||
push:
|
||||
branches: ['main']
|
||||
pull_request:
|
||||
branches: ['main']
|
||||
schedule:
|
||||
- cron: '23 19 * * 0'
|
||||
|
||||
jobs:
|
||||
analyze:
|
||||
name: Analyze (${{ matrix.language }})
|
||||
# Runner size impacts CodeQL analysis time. To learn more, please see:
|
||||
# - https://gh.io/recommended-hardware-resources-for-running-codeql
|
||||
# - https://gh.io/supported-runners-and-hardware-resources
|
||||
# - https://gh.io/using-larger-runners (GitHub.com only)
|
||||
# Consider using larger runners or machines with greater resources for possible analysis time improvements.
|
||||
runs-on: ${{ (matrix.language == 'swift' && 'macos-latest') || 'ubuntu-latest' }}
|
||||
timeout-minutes: ${{ (matrix.language == 'swift' && 120) || 360 }}
|
||||
permissions:
|
||||
# required for all workflows
|
||||
security-events: write
|
||||
|
||||
# only required for workflows in private repositories
|
||||
actions: read
|
||||
contents: read
|
||||
|
||||
strategy:
|
||||
fail-fast: false
|
||||
matrix:
|
||||
include:
|
||||
- language: javascript-typescript
|
||||
build-mode: none
|
||||
# CodeQL supports the following values keywords for 'language': 'c-cpp', 'csharp', 'go', 'java-kotlin', 'javascript-typescript', 'python', 'ruby', 'swift'
|
||||
# Use `c-cpp` to analyze code written in C, C++ or both
|
||||
# Use 'java-kotlin' to analyze code written in Java, Kotlin or both
|
||||
# Use 'javascript-typescript' to analyze code written in JavaScript, TypeScript or both
|
||||
# To learn more about changing the languages that are analyzed or customizing the build mode for your analysis,
|
||||
# see https://docs.github.com/en/code-security/code-scanning/creating-an-advanced-setup-for-code-scanning/customizing-your-advanced-setup-for-code-scanning.
|
||||
# If you are analyzing a compiled language, you can modify the 'build-mode' for that language to customize how
|
||||
# your codebase is analyzed, see https://docs.github.com/en/code-security/code-scanning/creating-an-advanced-setup-for-code-scanning/codeql-code-scanning-for-compiled-languages
|
||||
steps:
|
||||
- name: Checkout repository
|
||||
uses: actions/checkout@v4
|
||||
|
||||
# Initializes the CodeQL tools for scanning.
|
||||
- name: Initialize CodeQL
|
||||
uses: github/codeql-action/init@v3
|
||||
with:
|
||||
languages: ${{ matrix.language }}
|
||||
build-mode: ${{ matrix.build-mode }}
|
||||
# If you wish to specify custom queries, you can do so here or in a config file.
|
||||
# By default, queries listed here will override any specified in a config file.
|
||||
# Prefix the list here with "+" to use these queries and those in the config file.
|
||||
|
||||
# For more details on CodeQL's query packs, refer to: https://docs.github.com/en/code-security/code-scanning/automatically-scanning-your-code-for-vulnerabilities-and-errors/configuring-code-scanning#using-queries-in-ql-packs
|
||||
# queries: security-extended,security-and-quality
|
||||
|
||||
# If the analyze step fails for one of the languages you are analyzing with
|
||||
# "We were unable to automatically build your code", modify the matrix above
|
||||
# to set the build mode to "manual" for that language. Then modify this step
|
||||
# to build your code.
|
||||
# ℹ️ Command-line programs to run using the OS shell.
|
||||
# 📚 See https://docs.github.com/en/actions/using-workflows/workflow-syntax-for-github-actions#jobsjob_idstepsrun
|
||||
- if: matrix.build-mode == 'manual'
|
||||
run: |
|
||||
echo 'If you are using a "manual" build mode for one or more of the' \
|
||||
'languages you are analyzing, replace this with the commands to build' \
|
||||
'your code, for example:'
|
||||
echo ' make bootstrap'
|
||||
echo ' make release'
|
||||
exit 1
|
||||
|
||||
- name: Perform CodeQL Analysis
|
||||
uses: github/codeql-action/analyze@v3
|
||||
with:
|
||||
category: '/language:${{matrix.language}}'
|
||||
27
.github/workflows/eas-build.yml
vendored
Normal file
@@ -0,0 +1,27 @@
|
||||
name: EAS Build
|
||||
|
||||
on:
|
||||
workflow_dispatch:
|
||||
# push:
|
||||
# branches:
|
||||
# - main
|
||||
|
||||
jobs:
|
||||
build:
|
||||
name: Install and build
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- uses: actions/checkout@v4
|
||||
- uses: actions/setup-node@v4
|
||||
with:
|
||||
node-version: 18.x
|
||||
cache: npm
|
||||
- name: Setup Expo and EAS
|
||||
uses: expo/expo-github-action@v8
|
||||
with:
|
||||
eas-version: latest
|
||||
token: ${{ secrets.EXPO_TOKEN }}
|
||||
- name: Install dependencies
|
||||
run: npm ci
|
||||
- name: Build on EAS
|
||||
run: eas build --platform all --non-interactive --no-wait
|
||||
36
.github/workflows/eas-reviews.yml
vendored
Normal file
@@ -0,0 +1,36 @@
|
||||
name: EAS Review
|
||||
|
||||
on:
|
||||
workflow_dispatch:
|
||||
# pull_request:
|
||||
# branches:
|
||||
# - main
|
||||
|
||||
jobs:
|
||||
preview:
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- name: 🏗 Setup repo
|
||||
uses: actions/checkout@v4
|
||||
|
||||
- name: 🏗 Setup Node
|
||||
uses: actions/setup-node@v4
|
||||
with:
|
||||
node-version: 18.x
|
||||
cache: yarn
|
||||
|
||||
- name: 🏗 Setup EAS
|
||||
uses: expo/expo-github-action@v8
|
||||
with:
|
||||
eas-version: latest
|
||||
token: ${{ secrets.EXPO_TOKEN }}
|
||||
|
||||
- name: 📦 Install dependencies
|
||||
run: yarn install
|
||||
|
||||
- name: 🚀 Create preview
|
||||
uses: expo/expo-github-action/preview@v8
|
||||
with:
|
||||
# `github.event.pull_request.head.ref` is only available on `pull_request` triggers.
|
||||
# Use your own, or keep the automatically infered branch name from `--auto`, when using different triggers.
|
||||
command: eas update --auto --branch ${{ github.event.pull_request.head.ref }}
|
||||
32
.github/workflows/eas-update.yml
vendored
Normal file
@@ -0,0 +1,32 @@
|
||||
name: EAS Update
|
||||
|
||||
on:
|
||||
workflow_dispatch:
|
||||
# push:
|
||||
# branches:
|
||||
# - main
|
||||
|
||||
jobs:
|
||||
update:
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- name: 🏗 Setup repo
|
||||
uses: actions/checkout@v4
|
||||
|
||||
- name: 🏗 Setup Node
|
||||
uses: actions/setup-node@v4
|
||||
with:
|
||||
node-version: 18.x
|
||||
cache: yarn
|
||||
|
||||
- name: 🏗 Setup EAS
|
||||
uses: expo/expo-github-action@v8
|
||||
with:
|
||||
eas-version: latest
|
||||
token: ${{ secrets.EXPO_TOKEN }}
|
||||
|
||||
- name: 📦 Install dependencies
|
||||
run: yarn install
|
||||
|
||||
- name: 🚀 Create update
|
||||
run: eas update --auto --non-interactive
|
||||
44
.github/workflows/eslint.yml
vendored
Normal file
@@ -0,0 +1,44 @@
|
||||
# This workflow uses actions that are not certified by GitHub.
|
||||
# They are provided by a third-party and are governed by
|
||||
# separate terms of service, privacy policy, and support
|
||||
# documentation.
|
||||
# ESLint is a tool for identifying and reporting on patterns
|
||||
# found in ECMAScript/JavaScript code.
|
||||
# More details at https://github.com/eslint/eslint
|
||||
# and https://eslint.org
|
||||
|
||||
name: ESLint
|
||||
|
||||
on:
|
||||
push:
|
||||
branches: ['main']
|
||||
pull_request:
|
||||
# The branches below must be a subset of the branches above
|
||||
branches: ['main']
|
||||
|
||||
jobs:
|
||||
eslint:
|
||||
name: Run eslint scanning
|
||||
runs-on: ubuntu-latest
|
||||
permissions:
|
||||
contents: read
|
||||
security-events: write
|
||||
actions: read # only required for a private repository by github/codeql-action/upload-sarif to get the Action run status
|
||||
steps:
|
||||
- name: Checkout code
|
||||
uses: actions/checkout@v4
|
||||
|
||||
- name: Setup Node.js environment
|
||||
uses: actions/setup-node@v4
|
||||
with:
|
||||
node-version: 20
|
||||
|
||||
- name: Install dependencies
|
||||
run: |
|
||||
npm ci
|
||||
npm install eslint
|
||||
|
||||
- name: Run ESLint
|
||||
run: npx eslint .
|
||||
--config .eslintrc.js
|
||||
--ext .js,.jsx,.ts,.tsx
|
||||
39
.github/workflows/prettier.yml
vendored
Normal file
@@ -0,0 +1,39 @@
|
||||
# This workflow uses actions that are not certified by GitHub.
|
||||
# They are provided by a third-party and are governed by
|
||||
# separate terms of service, privacy policy, and support
|
||||
# documentation.
|
||||
# Prettier is an opinionated code formatter.
|
||||
# It enforces a consistent style by parsing your code and re-printing it with its own rules
|
||||
# that take the maximum line length into account, wrapping code when necessary.
|
||||
# More details at https://github.com/prettier/prettier
|
||||
# and https://prttier.io
|
||||
|
||||
name: Prettier
|
||||
|
||||
on:
|
||||
push:
|
||||
branches: ['main']
|
||||
pull_request:
|
||||
# The branches below must be a subset of the branches above
|
||||
branches: ['main']
|
||||
|
||||
jobs:
|
||||
eslint:
|
||||
name: Run prettier checking
|
||||
runs-on: ubuntu-latest
|
||||
permissions:
|
||||
contents: read
|
||||
steps:
|
||||
- name: Checkout code
|
||||
uses: actions/checkout@v4
|
||||
|
||||
- name: Setup Node.js environment
|
||||
uses: actions/setup-node@v4
|
||||
with:
|
||||
node-version: 20
|
||||
|
||||
- name: Install dependencies
|
||||
run: npm ci
|
||||
|
||||
- name: Run Prettier check
|
||||
run: npx prettier --check .
|
||||
76
.gitignore
vendored
Normal file
@@ -0,0 +1,76 @@
|
||||
# See https://help.github.com/articles/ignoring-files/ for more about ignoring files.
|
||||
|
||||
# dependencies
|
||||
/node_modules
|
||||
/.pnp
|
||||
.pnp.js
|
||||
.yarn/install-state.gz
|
||||
|
||||
# testing
|
||||
/coverage
|
||||
|
||||
# next.js
|
||||
/.next/
|
||||
/out/
|
||||
|
||||
# production
|
||||
/build
|
||||
|
||||
# misc
|
||||
.DS_Store
|
||||
*.pem
|
||||
|
||||
# debug
|
||||
npm-debug.log*
|
||||
yarn-debug.log*
|
||||
yarn-error.log*
|
||||
|
||||
# local env files
|
||||
.env*.local
|
||||
|
||||
# vercel
|
||||
.vercel
|
||||
|
||||
# typescript
|
||||
*.tsbuildinfo
|
||||
next-env.d.ts
|
||||
|
||||
# dependencies
|
||||
node_modules/
|
||||
|
||||
# Expo
|
||||
.expo/
|
||||
dist/
|
||||
web-build/
|
||||
|
||||
# Native
|
||||
*.orig.*
|
||||
*.jks
|
||||
*.p8
|
||||
*.p12
|
||||
*.key
|
||||
*.mobileprovision
|
||||
|
||||
# Metro
|
||||
.metro-health-check*
|
||||
|
||||
# debug
|
||||
npm-debug.*
|
||||
yarn-debug.*
|
||||
yarn-error.*
|
||||
|
||||
# macOS
|
||||
.DS_Store
|
||||
*.pem
|
||||
|
||||
# local env files
|
||||
.env*.local
|
||||
|
||||
# typescript
|
||||
*.tsbuildinfo
|
||||
|
||||
# @generated expo-cli sync-2b81b286409207a5da26e14c78851eb30d8ccbdb
|
||||
# The following patterns were generated by expo-cli
|
||||
|
||||
expo-env.d.ts
|
||||
# @end expo-cli
|
||||
8
.prettierrc
Normal file
@@ -0,0 +1,8 @@
|
||||
{
|
||||
"semi": false,
|
||||
"tabWidth": 2,
|
||||
"useTabs": false,
|
||||
"trailingComma": "all",
|
||||
"singleQuote": true,
|
||||
"endOfLine": "auto"
|
||||
}
|
||||
21
LICENSE
Normal file
@@ -0,0 +1,21 @@
|
||||
MIT License
|
||||
|
||||
Copyright (c) 2024 Yousef Abu Shanab
|
||||
|
||||
Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||
of this software and associated documentation files (the "Software"), to deal
|
||||
in the Software without restriction, including without limitation the rights
|
||||
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
||||
copies of the Software, and to permit persons to whom the Software is
|
||||
furnished to do so, subject to the following conditions:
|
||||
|
||||
The above copyright notice and this permission notice shall be included in all
|
||||
copies or substantial portions of the Software.
|
||||
|
||||
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
||||
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
||||
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
||||
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
||||
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
||||
SOFTWARE.
|
||||
105
README.md
Normal file
@@ -0,0 +1,105 @@
|
||||
# expo-react-native-paper
|
||||
|
||||
[](https://github.com/youzarsiph/expo-react-native-paper/actions/workflows/eas-build.yml)
|
||||
[](https://github.com/youzarsiph/expo-react-native-paper/actions/workflows/eas-reviews.yml)
|
||||
[](https://github.com/youzarsiph/expo-react-native-paper/actions/workflows/eas-update.yml)
|
||||
[](https://github.com/youzarsiph/expo-react-native-paper/actions/workflows/codeql.yml)
|
||||
[](https://github.com/youzarsiph/expo-react-native-paper/actions/workflows/eslint.yml)
|
||||
[](https://github.com/youzarsiph/expo-react-native-paper/actions/workflows/prettier.yml)
|
||||
|
||||
This repository is a template for React Native Expo applications. It integrates Expo Router and React Native Paper. It also demonstrates how to use Github Actions for linting and formatting.
|
||||
|
||||
## Features
|
||||
|
||||
- Expo
|
||||
- Expo Router
|
||||
- Material Design V3
|
||||
- Light & Dark modes with custom themes
|
||||
- Cross Platform
|
||||
- LTR and RTL support
|
||||
- Multi lingual (`ar`, `en` and `tr`)
|
||||
- CI/CD. For more info checkout this [page](https://github.com/expo/expo-github-action/tree/main)
|
||||
|
||||
## Platforms
|
||||
|
||||
- Web
|
||||
- IOS
|
||||
- Android
|
||||
|
||||
## Screenshots
|
||||
|
||||

|
||||

|
||||

|
||||

|
||||

|
||||

|
||||

|
||||

|
||||

|
||||

|
||||
|
||||
## Getting Started
|
||||
|
||||
These instructions will get you a copy of the project up and running on your local machine for development and testing purposes.
|
||||
|
||||
### Prerequisites
|
||||
|
||||
- Node.js
|
||||
- npm
|
||||
|
||||
### Installation
|
||||
|
||||
Clone the repo:
|
||||
|
||||
```bash
|
||||
git clone https://github.com/youzarsiph/expo-react-native-paper.git
|
||||
```
|
||||
|
||||
Open `package.json` and update the `name` field to match your app's name:
|
||||
|
||||
```jsonc
|
||||
{
|
||||
// Change the following line
|
||||
"name": "expo-react-native-paper",
|
||||
"main": "expo-router/entry",
|
||||
"version": "1.0.0",
|
||||
...
|
||||
}
|
||||
|
||||
```
|
||||
|
||||
Install dependencies:
|
||||
|
||||
```bash
|
||||
npm install
|
||||
```
|
||||
|
||||
Run the app:
|
||||
|
||||
```bash
|
||||
npm start
|
||||
```
|
||||
|
||||
## Built With
|
||||
|
||||
- TypeScript
|
||||
- React
|
||||
- React Native
|
||||
- Expo
|
||||
- Expo Router
|
||||
- React Native Paper
|
||||
|
||||
## Contributing
|
||||
|
||||
Contributions are what make the open-source community such an amazing place to learn, inspire, and create. Any contributions you make are greatly appreciated.
|
||||
|
||||
1. Fork the Project
|
||||
2. Create your Feature Branch (git checkout -b feature/AmazingFeature)
|
||||
3. Commit your Changes (git commit -m 'Add some AmazingFeature')
|
||||
4. Push to the Branch (git push origin feature/AmazingFeature)
|
||||
5. Open a Pull Request
|
||||
|
||||
## License
|
||||
|
||||
Distributed under the MIT License. See LICENSE for more information.
|
||||
43
app.json
Normal file
@@ -0,0 +1,43 @@
|
||||
{
|
||||
"expo": {
|
||||
"name": "expo-react-native-paper",
|
||||
"slug": "expo-react-native-paper",
|
||||
"version": "1.0.0",
|
||||
"orientation": "portrait",
|
||||
"icon": "./assets/images/icon.png",
|
||||
"scheme": "myapp",
|
||||
"userInterfaceStyle": "automatic",
|
||||
"splash": {
|
||||
"image": "./assets/images/splash.png",
|
||||
"resizeMode": "contain",
|
||||
"backgroundColor": "#ffffff"
|
||||
},
|
||||
"assetBundlePatterns": ["**/*"],
|
||||
"ios": {
|
||||
"supportsTablet": true
|
||||
},
|
||||
"android": {
|
||||
"adaptiveIcon": {
|
||||
"foregroundImage": "./assets/images/adaptive-icon.png",
|
||||
"backgroundColor": "#ffffff"
|
||||
}
|
||||
},
|
||||
"web": {
|
||||
"bundler": "metro",
|
||||
"output": "static",
|
||||
"favicon": "./assets/images/favicon.png"
|
||||
},
|
||||
"plugins": [
|
||||
"expo-router",
|
||||
"expo-secure-store",
|
||||
"expo-font",
|
||||
"expo-localization"
|
||||
],
|
||||
"experiments": {
|
||||
"typedRoutes": true
|
||||
},
|
||||
"extra": {
|
||||
"supportsRTL": true
|
||||
}
|
||||
}
|
||||
}
|
||||
18
app/(auth)/_layout.tsx
Normal file
@@ -0,0 +1,18 @@
|
||||
import { Stack } from 'expo-router'
|
||||
|
||||
import Locales from '@/lib/locales'
|
||||
import { StackHeader } from '@/lib/ui'
|
||||
|
||||
const Layout = () => (
|
||||
<Stack
|
||||
screenOptions={{
|
||||
animation: 'slide_from_bottom',
|
||||
header: (props) => <StackHeader navProps={props} children={undefined} />,
|
||||
}}
|
||||
>
|
||||
<Stack.Screen name="login" options={{ title: Locales.t('login') }} />
|
||||
<Stack.Screen name="signup" options={{ title: Locales.t('signup') }} />
|
||||
</Stack>
|
||||
)
|
||||
|
||||
export default Layout
|
||||
107
app/(auth)/login.tsx
Normal file
@@ -0,0 +1,107 @@
|
||||
import { Image } from 'expo-image'
|
||||
import { router } from 'expo-router'
|
||||
import { Formik } from 'formik'
|
||||
import React from 'react'
|
||||
import {
|
||||
Button,
|
||||
Surface,
|
||||
TextInput,
|
||||
HelperText,
|
||||
Text,
|
||||
} from 'react-native-paper'
|
||||
import * as Yup from 'yup'
|
||||
|
||||
import { styles } from '@/lib/ui'
|
||||
|
||||
const Login = () => (
|
||||
<Surface style={{ ...styles.screen, alignItems: undefined }}>
|
||||
<Image
|
||||
alt="Logo"
|
||||
source={require('@/assets/images/icon.png')}
|
||||
style={{
|
||||
height: 150,
|
||||
width: 150,
|
||||
borderRadius: 16,
|
||||
marginBottom: 32,
|
||||
marginHorizontal: 'auto',
|
||||
}}
|
||||
/>
|
||||
|
||||
<Text variant="headlineLarge" style={{ textAlign: 'center' }}>
|
||||
Welcome to ERNP
|
||||
</Text>
|
||||
<Text variant="bodyLarge" style={{ textAlign: 'center' }}>
|
||||
We're excited to have you back. Please log in to continue.
|
||||
</Text>
|
||||
|
||||
<Formik
|
||||
initialValues={{ username: '', password: '' }}
|
||||
onSubmit={(values) => console.log(values)}
|
||||
validationSchema={Yup.object().shape({
|
||||
username: Yup.string()
|
||||
.min(3, 'Too Short!')
|
||||
.max(64, 'Too Long!')
|
||||
.required('Please enter a username.'),
|
||||
password: Yup.string()
|
||||
.min(8, 'Too Short! must be at least 8 characters.')
|
||||
.max(64, 'Too Long!')
|
||||
.matches(
|
||||
/^(?=.*[a-z])(?=.*[A-Z])(?=.*[0-9])(?=.*[!@#$%^&*])/,
|
||||
'Must 1 uppercase, 1 lowercase, 1 number and 1 special case character',
|
||||
)
|
||||
.required('Please enter a password'),
|
||||
})}
|
||||
>
|
||||
{({ handleChange, handleBlur, handleSubmit, values, errors }) => (
|
||||
<>
|
||||
<Surface elevation={0}>
|
||||
<TextInput
|
||||
maxLength={64}
|
||||
mode="outlined"
|
||||
label="Username"
|
||||
value={values.username}
|
||||
error={!!errors.username}
|
||||
onBlur={handleBlur('username')}
|
||||
right={64 - values.username.length}
|
||||
placeholder="Enter your username..."
|
||||
onChangeText={handleChange('username')}
|
||||
/>
|
||||
<HelperText type="error" visible={!!errors.username}>
|
||||
{errors.username}
|
||||
</HelperText>
|
||||
</Surface>
|
||||
|
||||
<Surface elevation={0}>
|
||||
<TextInput
|
||||
maxLength={64}
|
||||
mode="outlined"
|
||||
label="Password"
|
||||
value={values.password}
|
||||
error={!!errors.password}
|
||||
onBlur={handleBlur('password')}
|
||||
right={64 - values.password.length}
|
||||
placeholder="Enter your password..."
|
||||
onChangeText={handleChange('password')}
|
||||
/>
|
||||
<HelperText type="error" visible={!!errors.password}>
|
||||
{errors.password}
|
||||
</HelperText>
|
||||
</Surface>
|
||||
|
||||
<Button mode="contained" onPress={() => handleSubmit()}>
|
||||
Login
|
||||
</Button>
|
||||
</>
|
||||
)}
|
||||
</Formik>
|
||||
|
||||
<Button
|
||||
mode="contained-tonal"
|
||||
onPress={() => router.push('/(auth)/signup')}
|
||||
>
|
||||
New here?
|
||||
</Button>
|
||||
</Surface>
|
||||
)
|
||||
|
||||
export default Login
|
||||
176
app/(auth)/signup.tsx
Normal file
@@ -0,0 +1,176 @@
|
||||
import { Image } from 'expo-image'
|
||||
import { router } from 'expo-router'
|
||||
import { Formik } from 'formik'
|
||||
import React from 'react'
|
||||
import { ScrollView } from 'react-native'
|
||||
import {
|
||||
Button,
|
||||
Surface,
|
||||
TextInput,
|
||||
HelperText,
|
||||
Text,
|
||||
} from 'react-native-paper'
|
||||
import * as Yup from 'yup'
|
||||
|
||||
import { styles } from '@/lib/ui'
|
||||
|
||||
const SignUp = () => (
|
||||
<ScrollView style={{ flex: 1 }}>
|
||||
<Surface style={{ ...styles.screen, alignItems: undefined }}>
|
||||
<Image
|
||||
alt="Logo"
|
||||
source={require('@/assets/images/icon.png')}
|
||||
style={{
|
||||
height: 150,
|
||||
width: 150,
|
||||
borderRadius: 16,
|
||||
marginBottom: 32,
|
||||
marginHorizontal: 'auto',
|
||||
}}
|
||||
/>
|
||||
|
||||
<Text variant="headlineLarge" style={{ textAlign: 'center' }}>
|
||||
Join ERNP Today!
|
||||
</Text>
|
||||
<Text variant="bodyLarge" style={{ textAlign: 'center' }}>
|
||||
We're thrilled to have you on board. Let's get you set up.
|
||||
</Text>
|
||||
|
||||
<Formik
|
||||
initialValues={{
|
||||
username: '',
|
||||
password: '',
|
||||
email: '',
|
||||
firstName: '',
|
||||
lastName: '',
|
||||
}}
|
||||
onSubmit={(values) => console.log(values)}
|
||||
validationSchema={Yup.object().shape({
|
||||
username: Yup.string()
|
||||
.min(3, 'Too Short!')
|
||||
.max(64, 'Too Long!')
|
||||
.required('Please enter a username.'),
|
||||
password: Yup.string()
|
||||
.min(8, 'Too Short! must be at least 8 characters.')
|
||||
.max(64, 'Too Long!')
|
||||
.matches(
|
||||
/^(?=.*[a-z])(?=.*[A-Z])(?=.*[0-9])(?=.*[!@#$%^&*])/,
|
||||
'Must 1 uppercase, 1 lowercase, 1 number and 1 special case character',
|
||||
)
|
||||
.required('Please enter a password'),
|
||||
email: Yup.string().email().required('Please enter an email.'),
|
||||
firstName: Yup.string()
|
||||
.min(3, 'Too Short!')
|
||||
.max(64, 'Too Long!')
|
||||
.required('Please enter a first name.'),
|
||||
lastName: Yup.string()
|
||||
.min(3, 'Too Short!')
|
||||
.max(64, 'Too Long!')
|
||||
.required('Please enter a last name.'),
|
||||
})}
|
||||
>
|
||||
{({ handleChange, handleBlur, handleSubmit, values, errors }) => (
|
||||
<>
|
||||
<Surface elevation={0}>
|
||||
<TextInput
|
||||
maxLength={64}
|
||||
mode="outlined"
|
||||
label="Username"
|
||||
value={values.username}
|
||||
error={!!errors.username}
|
||||
onBlur={handleBlur('username')}
|
||||
right={64 - values.username.length}
|
||||
placeholder="Enter your username..."
|
||||
onChangeText={handleChange('username')}
|
||||
/>
|
||||
<HelperText type="error" visible={!!errors.username}>
|
||||
{errors.username}
|
||||
</HelperText>
|
||||
</Surface>
|
||||
|
||||
<Surface elevation={0}>
|
||||
<TextInput
|
||||
maxLength={64}
|
||||
mode="outlined"
|
||||
label="Password"
|
||||
value={values.password}
|
||||
error={!!errors.password}
|
||||
onBlur={handleBlur('password')}
|
||||
right={64 - values.password.length}
|
||||
placeholder="Enter your password..."
|
||||
onChangeText={handleChange('password')}
|
||||
/>
|
||||
<HelperText type="error" visible={!!errors.password}>
|
||||
{errors.password}
|
||||
</HelperText>
|
||||
</Surface>
|
||||
|
||||
<Surface elevation={0}>
|
||||
<TextInput
|
||||
maxLength={64}
|
||||
mode="outlined"
|
||||
label="Email"
|
||||
value={values.email}
|
||||
error={!!errors.email}
|
||||
onBlur={handleBlur('email')}
|
||||
right={64 - values.email.length}
|
||||
placeholder="Enter your email..."
|
||||
onChangeText={handleChange('email')}
|
||||
/>
|
||||
<HelperText type="error" visible={!!errors.email}>
|
||||
{errors.email}
|
||||
</HelperText>
|
||||
</Surface>
|
||||
|
||||
<Surface elevation={0}>
|
||||
<TextInput
|
||||
maxLength={64}
|
||||
mode="outlined"
|
||||
label="First name"
|
||||
value={values.firstName}
|
||||
error={!!errors.firstName}
|
||||
onBlur={handleBlur('firstName')}
|
||||
right={64 - values.firstName.length}
|
||||
placeholder="Enter your first name..."
|
||||
onChangeText={handleChange('firstName')}
|
||||
/>
|
||||
<HelperText type="error" visible={!!errors.firstName}>
|
||||
{errors.firstName}
|
||||
</HelperText>
|
||||
</Surface>
|
||||
|
||||
<Surface elevation={0}>
|
||||
<TextInput
|
||||
maxLength={64}
|
||||
mode="outlined"
|
||||
label="Last name"
|
||||
value={values.lastName}
|
||||
error={!!errors.lastName}
|
||||
onBlur={handleBlur('lastName')}
|
||||
right={64 - values.lastName.length}
|
||||
placeholder="Enter your first name..."
|
||||
onChangeText={handleChange('lastName')}
|
||||
/>
|
||||
<HelperText type="error" visible={!!errors.lastName}>
|
||||
{errors.lastName}
|
||||
</HelperText>
|
||||
</Surface>
|
||||
|
||||
<Button mode="contained" onPress={() => handleSubmit()}>
|
||||
Sign up
|
||||
</Button>
|
||||
</>
|
||||
)}
|
||||
</Formik>
|
||||
|
||||
<Button
|
||||
mode="contained-tonal"
|
||||
onPress={() => router.push('/(auth)/login')}
|
||||
>
|
||||
Already have an account?
|
||||
</Button>
|
||||
</Surface>
|
||||
</ScrollView>
|
||||
)
|
||||
|
||||
export default SignUp
|
||||
126
app/(tabs)/_layout.tsx
Normal file
@@ -0,0 +1,126 @@
|
||||
import { MaterialCommunityIcons } from '@expo/vector-icons'
|
||||
import { Tabs, router } from 'expo-router'
|
||||
import React from 'react'
|
||||
import { Appbar, Menu, Tooltip } from 'react-native-paper'
|
||||
|
||||
import Locales from '@/lib/locales'
|
||||
import { TabBar, TabsHeader } from '@/lib/ui'
|
||||
|
||||
const TabLayout = () => {
|
||||
const [visible, setVisible] = React.useState(false)
|
||||
|
||||
return (
|
||||
<Tabs
|
||||
tabBar={(props) => <TabBar {...props} />}
|
||||
screenOptions={{
|
||||
tabBarHideOnKeyboard: true,
|
||||
header: (props) => <TabsHeader navProps={props} children={undefined} />,
|
||||
}}
|
||||
>
|
||||
<Tabs.Screen
|
||||
name="index"
|
||||
options={{
|
||||
title: Locales.t('titleHome'),
|
||||
headerRight: () => (
|
||||
<>
|
||||
<Tooltip title={Locales.t('search')}>
|
||||
<Appbar.Action
|
||||
icon="magnify"
|
||||
onPress={() => router.push('/search')}
|
||||
/>
|
||||
</Tooltip>
|
||||
<Menu
|
||||
statusBarHeight={48}
|
||||
visible={visible}
|
||||
onDismiss={() => setVisible(false)}
|
||||
anchor={
|
||||
<Tooltip title={Locales.t('options')}>
|
||||
<Appbar.Action
|
||||
icon="dots-vertical"
|
||||
onPress={() => setVisible(true)}
|
||||
/>
|
||||
</Tooltip>
|
||||
}
|
||||
>
|
||||
<Menu.Item
|
||||
title={Locales.t('titleSettings')}
|
||||
leadingIcon="cog"
|
||||
onPress={() => router.push('/(tabs)/settings')}
|
||||
/>
|
||||
<Menu.Item
|
||||
title={Locales.t('stackNav')}
|
||||
leadingIcon="card-multiple-outline"
|
||||
onPress={() => router.push('/modal')}
|
||||
/>
|
||||
<Menu.Item
|
||||
title={Locales.t('drawerNav')}
|
||||
leadingIcon="gesture-swipe"
|
||||
onPress={() => router.push('/drawer')}
|
||||
/>
|
||||
</Menu>
|
||||
</>
|
||||
),
|
||||
tabBarIcon: (props) => (
|
||||
<MaterialCommunityIcons
|
||||
{...props}
|
||||
size={24}
|
||||
name={props.focused ? 'home' : 'home-outline'}
|
||||
/>
|
||||
),
|
||||
}}
|
||||
/>
|
||||
<Tabs.Screen
|
||||
name="profile"
|
||||
options={{
|
||||
title: Locales.t('profile'),
|
||||
headerRight: () => (
|
||||
<>
|
||||
<Tooltip title={Locales.t('search')}>
|
||||
<Appbar.Action
|
||||
icon="magnify"
|
||||
onPress={() => router.push('/search')}
|
||||
/>
|
||||
</Tooltip>
|
||||
<Tooltip title={Locales.t('titleSettings')}>
|
||||
<Appbar.Action
|
||||
icon="cog"
|
||||
onPress={() => router.push('/(tabs)/settings')}
|
||||
/>
|
||||
</Tooltip>
|
||||
</>
|
||||
),
|
||||
tabBarIcon: (props) => (
|
||||
<MaterialCommunityIcons
|
||||
{...props}
|
||||
size={24}
|
||||
name={props.focused ? 'account' : 'account-outline'}
|
||||
/>
|
||||
),
|
||||
}}
|
||||
/>
|
||||
<Tabs.Screen
|
||||
name="settings"
|
||||
options={{
|
||||
title: Locales.t('titleSettings'),
|
||||
headerRight: () => (
|
||||
<Tooltip title={Locales.t('drawerNav')}>
|
||||
<Appbar.Action
|
||||
icon="gesture-swipe"
|
||||
onPress={() => router.push('/drawer')}
|
||||
/>
|
||||
</Tooltip>
|
||||
),
|
||||
tabBarIcon: (props) => (
|
||||
<MaterialCommunityIcons
|
||||
{...props}
|
||||
size={24}
|
||||
name={props.focused ? 'cog' : 'cog-outline'}
|
||||
/>
|
||||
),
|
||||
}}
|
||||
/>
|
||||
</Tabs>
|
||||
)
|
||||
}
|
||||
|
||||
export default TabLayout
|
||||
13
app/(tabs)/index.tsx
Normal file
@@ -0,0 +1,13 @@
|
||||
import React from 'react'
|
||||
import { Surface } from 'react-native-paper'
|
||||
|
||||
import Locales from '@/lib/locales'
|
||||
import { ScreenInfo, styles } from '@/lib/ui'
|
||||
|
||||
const TabsHome = () => (
|
||||
<Surface style={styles.screen}>
|
||||
<ScreenInfo title={Locales.t('titleHome')} path="app/(tabs)/index.tsx" />
|
||||
</Surface>
|
||||
)
|
||||
|
||||
export default TabsHome
|
||||
34
app/(tabs)/profile.tsx
Normal file
@@ -0,0 +1,34 @@
|
||||
import { router } from 'expo-router'
|
||||
import React from 'react'
|
||||
import { Button, Surface } from 'react-native-paper'
|
||||
|
||||
import Locales from '@/lib/locales'
|
||||
import { ScreenInfo, styles } from '@/lib/ui'
|
||||
|
||||
const Profile = () => (
|
||||
<Surface style={styles.screen}>
|
||||
<ScreenInfo title={Locales.t('profile')} path="app/(tabs)/profile.tsx" />
|
||||
|
||||
<Surface
|
||||
elevation={0}
|
||||
style={{
|
||||
padding: 16,
|
||||
gap: 16,
|
||||
position: 'absolute',
|
||||
left: 0,
|
||||
right: 0,
|
||||
bottom: 0,
|
||||
}}
|
||||
>
|
||||
<Button mode="contained" onPress={() => router.push('/(auth)/login')}>
|
||||
Login
|
||||
</Button>
|
||||
|
||||
<Button mode="contained" onPress={() => router.push('/(auth)/signup')}>
|
||||
Sign Up
|
||||
</Button>
|
||||
</Surface>
|
||||
</Surface>
|
||||
)
|
||||
|
||||
export default Profile
|
||||
310
app/(tabs)/settings.tsx
Normal file
@@ -0,0 +1,310 @@
|
||||
import * as SecureStore from 'expo-secure-store'
|
||||
import React from 'react'
|
||||
import { Platform, useColorScheme } from 'react-native'
|
||||
import {
|
||||
Surface,
|
||||
List,
|
||||
Menu,
|
||||
Button,
|
||||
IconButton,
|
||||
Snackbar,
|
||||
Icon,
|
||||
} from 'react-native-paper'
|
||||
|
||||
import Locales from '@/lib/locales'
|
||||
import { Color, Language, Setting } from '@/lib/types'
|
||||
import { Colors, LoadingIndicator, ScreenInfo, styles } from '@/lib/ui'
|
||||
import { Languages } from '@/lib/utils'
|
||||
|
||||
const Settings = () => {
|
||||
const colorScheme = useColorScheme()
|
||||
const [loading, setLoading] = React.useState<boolean>(false)
|
||||
const [message, setMessage] = React.useState({ visible: false, content: '' })
|
||||
const [settings, setSettings] = React.useState<Setting>({
|
||||
color: 'default',
|
||||
language: 'auto',
|
||||
theme: 'auto',
|
||||
})
|
||||
const [display, setDisplay] = React.useState({
|
||||
color: false,
|
||||
language: false,
|
||||
theme: false,
|
||||
})
|
||||
|
||||
React.useEffect(() => {
|
||||
setLoading(true)
|
||||
|
||||
if (Platform.OS !== 'web') {
|
||||
SecureStore.getItemAsync('settings')
|
||||
.then((result) =>
|
||||
setSettings(JSON.parse(result ?? JSON.stringify(settings))),
|
||||
)
|
||||
.catch((res) =>
|
||||
setMessage({
|
||||
visible: true,
|
||||
content: res.message,
|
||||
}),
|
||||
)
|
||||
}
|
||||
|
||||
setLoading(false)
|
||||
// eslint-disable-next-line react-hooks/exhaustive-deps
|
||||
}, [])
|
||||
|
||||
const themeColors =
|
||||
Colors[
|
||||
settings.theme === 'auto' ? (colorScheme ?? 'light') : settings.theme
|
||||
]
|
||||
|
||||
return (
|
||||
<Surface style={{ flex: 1 }}>
|
||||
{loading ? (
|
||||
<LoadingIndicator />
|
||||
) : (
|
||||
<Surface elevation={0}>
|
||||
<List.AccordionGroup>
|
||||
<List.Accordion
|
||||
id="1"
|
||||
title={Locales.t('appearance')}
|
||||
left={(props) => <List.Icon {...props} icon="palette" />}
|
||||
>
|
||||
<List.Item
|
||||
title={Locales.t('language')}
|
||||
description={Locales.t('changeLanguage')}
|
||||
left={(props) => <List.Icon {...props} icon="translate" />}
|
||||
right={(props) => (
|
||||
<Menu
|
||||
visible={display.language}
|
||||
onDismiss={() =>
|
||||
setDisplay({ ...display, language: false })
|
||||
}
|
||||
anchor={
|
||||
<IconButton
|
||||
{...props}
|
||||
icon="pencil"
|
||||
onPress={() =>
|
||||
setDisplay({ ...display, language: true })
|
||||
}
|
||||
/>
|
||||
}
|
||||
>
|
||||
<Menu.Item
|
||||
title="System"
|
||||
trailingIcon={
|
||||
settings.language === 'auto' ? 'check' : undefined
|
||||
}
|
||||
onPress={() => {
|
||||
setSettings({ ...settings, language: 'auto' })
|
||||
setDisplay({ ...display, language: false })
|
||||
}}
|
||||
/>
|
||||
{Object.entries(Languages).map((lang) => (
|
||||
<Menu.Item
|
||||
key={lang[0]}
|
||||
title={`${lang[1].name} / ${lang[1].nativeName}`}
|
||||
trailingIcon={
|
||||
settings.language === lang[0] ? 'check' : undefined
|
||||
}
|
||||
onPress={() => {
|
||||
setSettings({
|
||||
...settings,
|
||||
language: lang[0] as Language,
|
||||
})
|
||||
setDisplay({ ...display, language: false })
|
||||
}}
|
||||
/>
|
||||
))}
|
||||
</Menu>
|
||||
)}
|
||||
/>
|
||||
<List.Item
|
||||
title={Locales.t('mode')}
|
||||
description={Locales.t('changeMode')}
|
||||
left={(props) => (
|
||||
<List.Icon
|
||||
{...props}
|
||||
icon={
|
||||
settings.theme === 'auto'
|
||||
? 'theme-light-dark'
|
||||
: settings.theme === 'light'
|
||||
? 'weather-sunny'
|
||||
: 'weather-night'
|
||||
}
|
||||
/>
|
||||
)}
|
||||
right={(props) => (
|
||||
<Menu
|
||||
visible={display.theme}
|
||||
onDismiss={() => setDisplay({ ...display, theme: false })}
|
||||
anchor={
|
||||
<IconButton
|
||||
{...props}
|
||||
icon="pencil"
|
||||
onPress={() => setDisplay({ ...display, theme: true })}
|
||||
/>
|
||||
}
|
||||
>
|
||||
<Menu.Item
|
||||
title={Locales.t('system')}
|
||||
leadingIcon="theme-light-dark"
|
||||
trailingIcon={
|
||||
settings.theme === 'auto' ? 'check' : undefined
|
||||
}
|
||||
onPress={() => {
|
||||
setSettings({ ...settings, theme: 'auto' })
|
||||
setDisplay({ ...display, theme: false })
|
||||
}}
|
||||
/>
|
||||
<Menu.Item
|
||||
title={Locales.t('lightMode')}
|
||||
leadingIcon="weather-sunny"
|
||||
trailingIcon={
|
||||
settings.theme === 'light' ? 'check' : undefined
|
||||
}
|
||||
onPress={() => {
|
||||
setSettings({ ...settings, theme: 'light' })
|
||||
setDisplay({ ...display, theme: false })
|
||||
}}
|
||||
/>
|
||||
<Menu.Item
|
||||
title={Locales.t('darkMode')}
|
||||
leadingIcon="weather-night"
|
||||
trailingIcon={
|
||||
settings.theme === 'dark' ? 'check' : undefined
|
||||
}
|
||||
onPress={() => {
|
||||
setSettings({ ...settings, theme: 'dark' })
|
||||
setDisplay({ ...display, theme: false })
|
||||
}}
|
||||
/>
|
||||
</Menu>
|
||||
)}
|
||||
/>
|
||||
<List.Item
|
||||
title={Locales.t('color')}
|
||||
description={Locales.t('changeColor')}
|
||||
left={(props) => (
|
||||
<List.Icon
|
||||
{...props}
|
||||
icon="palette-swatch-variant"
|
||||
color={
|
||||
Colors[
|
||||
settings.theme === 'auto'
|
||||
? (colorScheme ?? 'light')
|
||||
: settings.theme
|
||||
][settings.color]?.primary
|
||||
}
|
||||
/>
|
||||
)}
|
||||
right={(props) => (
|
||||
<Menu
|
||||
visible={display.color}
|
||||
onDismiss={() => setDisplay({ ...display, color: false })}
|
||||
anchor={
|
||||
<IconButton
|
||||
{...props}
|
||||
icon="pencil"
|
||||
onPress={() => setDisplay({ ...display, color: true })}
|
||||
/>
|
||||
}
|
||||
>
|
||||
{Object.keys(Colors.light).map((color) => (
|
||||
<Surface
|
||||
key={color}
|
||||
elevation={0}
|
||||
style={{
|
||||
width: '100%',
|
||||
flexDirection: 'row',
|
||||
alignItems: 'center',
|
||||
}}
|
||||
>
|
||||
<Surface
|
||||
elevation={0}
|
||||
style={{
|
||||
padding: 4,
|
||||
marginLeft: 8,
|
||||
borderRadius: 16,
|
||||
backgroundColor:
|
||||
color !== settings.color
|
||||
? undefined
|
||||
: themeColors[color]?.primary,
|
||||
}}
|
||||
>
|
||||
<Icon
|
||||
size={24}
|
||||
source="palette"
|
||||
color={
|
||||
color !== settings.color
|
||||
? themeColors[color as Color]?.primary
|
||||
: themeColors[color].onPrimary
|
||||
}
|
||||
/>
|
||||
</Surface>
|
||||
|
||||
<Menu.Item
|
||||
key={color}
|
||||
title={Locales.t(color)}
|
||||
onPress={() => {
|
||||
setSettings({
|
||||
...settings,
|
||||
color: color as Color,
|
||||
})
|
||||
setDisplay({ ...display, color: false })
|
||||
}}
|
||||
/>
|
||||
</Surface>
|
||||
))}
|
||||
</Menu>
|
||||
)}
|
||||
/>
|
||||
</List.Accordion>
|
||||
</List.AccordionGroup>
|
||||
</Surface>
|
||||
)}
|
||||
|
||||
<Surface elevation={0} style={styles.screen}>
|
||||
<ScreenInfo
|
||||
title={Locales.t('titleSettings')}
|
||||
path="app/(tabs)/settings.tsx"
|
||||
/>
|
||||
</Surface>
|
||||
|
||||
<Button
|
||||
mode="contained"
|
||||
style={{ margin: 16 }}
|
||||
onPress={() =>
|
||||
Platform.OS !== 'web'
|
||||
? SecureStore.setItemAsync('settings', JSON.stringify(settings))
|
||||
.then(() =>
|
||||
setMessage({
|
||||
visible: true,
|
||||
content: Locales.t('restartApp'),
|
||||
}),
|
||||
)
|
||||
.catch((res) =>
|
||||
setMessage({
|
||||
visible: true,
|
||||
content: res.message,
|
||||
}),
|
||||
)
|
||||
: setMessage({
|
||||
visible: true,
|
||||
content: Locales.t('notAvailable'),
|
||||
})
|
||||
}
|
||||
>
|
||||
{Locales.t('save')}
|
||||
</Button>
|
||||
|
||||
<Snackbar
|
||||
visible={message.visible}
|
||||
onDismiss={() => setMessage({ ...message, visible: false })}
|
||||
onIconPress={() => setMessage({ ...message, visible: false })}
|
||||
>
|
||||
{message.content}
|
||||
</Snackbar>
|
||||
</Surface>
|
||||
)
|
||||
}
|
||||
|
||||
export default Settings
|
||||
42
app/+html.tsx
Normal file
@@ -0,0 +1,42 @@
|
||||
import { ScrollViewStyleReset } from 'expo-router/html'
|
||||
import React from 'react'
|
||||
|
||||
// This file is web-only and used to configure the root HTML for every
|
||||
// web page during static rendering.
|
||||
// The contents of this function only run in Node.js environments and
|
||||
// do not have access to the DOM or browser APIs.
|
||||
export default function Root({ children }: { children: React.ReactNode }) {
|
||||
return (
|
||||
<html lang="en">
|
||||
<head>
|
||||
<meta charSet="utf-8" />
|
||||
<meta httpEquiv="X-UA-Compatible" content="IE=edge" />
|
||||
<meta
|
||||
name="viewport"
|
||||
content="width=device-width, initial-scale=1, shrink-to-fit=no"
|
||||
/>
|
||||
|
||||
{/*
|
||||
Disable body scrolling on web. This makes ScrollView components work closer to how they do on native.
|
||||
However, body scrolling is often nice to have for mobile web. If you want to enable it, remove this line.
|
||||
*/}
|
||||
<ScrollViewStyleReset />
|
||||
|
||||
{/* Using raw CSS styles as an escape-hatch to ensure the background color never flickers in dark-mode. */}
|
||||
<style dangerouslySetInnerHTML={{ __html: responsiveBackground }} />
|
||||
{/* Add any additional <head> elements that you want globally available on web... */}
|
||||
</head>
|
||||
<body>{children}</body>
|
||||
</html>
|
||||
)
|
||||
}
|
||||
|
||||
const responsiveBackground = `
|
||||
body {
|
||||
background-color: #fff;
|
||||
}
|
||||
@media (prefers-color-scheme: dark) {
|
||||
body {
|
||||
background-color: #000;
|
||||
}
|
||||
}`
|
||||
22
app/+not-found.tsx
Normal file
@@ -0,0 +1,22 @@
|
||||
import { Link, Stack } from 'expo-router'
|
||||
import React from 'react'
|
||||
import { Surface, Text } from 'react-native-paper'
|
||||
|
||||
import Locales from '@/lib/locales'
|
||||
import { styles } from '@/lib/ui'
|
||||
|
||||
const NotFound = () => (
|
||||
<Surface style={styles.screen}>
|
||||
<Stack.Screen options={{ title: Locales.t('titleNotFound') }} />
|
||||
|
||||
<Text variant="displayLarge">{Locales.t('titleNotFound')}</Text>
|
||||
|
||||
<Text variant="bodyLarge">{Locales.t('screen404')}</Text>
|
||||
|
||||
<Link href="/">
|
||||
<Text variant="bodyLarge">{Locales.t('goHome')}</Text>
|
||||
</Link>
|
||||
</Surface>
|
||||
)
|
||||
|
||||
export default NotFound
|
||||
122
app/_layout.tsx
Normal file
@@ -0,0 +1,122 @@
|
||||
import { MaterialCommunityIcons } from '@expo/vector-icons'
|
||||
import {
|
||||
useFonts,
|
||||
JetBrainsMono_400Regular,
|
||||
} from '@expo-google-fonts/jetbrains-mono'
|
||||
import { NotoSans_400Regular } from '@expo-google-fonts/noto-sans'
|
||||
import * as Localization from 'expo-localization'
|
||||
import { SplashScreen, Stack } from 'expo-router'
|
||||
import * as SecureStore from 'expo-secure-store'
|
||||
import React from 'react'
|
||||
import { Platform, useColorScheme } from 'react-native'
|
||||
import { PaperProvider } from 'react-native-paper'
|
||||
|
||||
import Locales from '@/lib/locales'
|
||||
import { Setting } from '@/lib/types'
|
||||
import { StackHeader, Themes } from '@/lib/ui'
|
||||
|
||||
export {
|
||||
// Catch any errors thrown by the Layout component.
|
||||
ErrorBoundary,
|
||||
} from 'expo-router'
|
||||
|
||||
export const unstable_settings = {
|
||||
// Ensure that reloading on `/modal` keeps a back button present.
|
||||
initialRouteName: '(tabs)',
|
||||
}
|
||||
|
||||
// Prevent the splash screen from auto-hiding before asset loading is complete.
|
||||
SplashScreen.preventAutoHideAsync()
|
||||
|
||||
const RootLayout = () => {
|
||||
const [loaded, error] = useFonts({
|
||||
NotoSans_400Regular,
|
||||
JetBrainsMono_400Regular,
|
||||
...MaterialCommunityIcons.font,
|
||||
})
|
||||
|
||||
// Expo Router uses Error Boundaries to catch errors in the navigation tree.
|
||||
React.useEffect(() => {
|
||||
if (error) throw error
|
||||
}, [error])
|
||||
|
||||
React.useEffect(() => {
|
||||
if (loaded) {
|
||||
SplashScreen.hideAsync()
|
||||
}
|
||||
}, [loaded])
|
||||
|
||||
if (!loaded) {
|
||||
return null
|
||||
}
|
||||
|
||||
return <RootLayoutNav />
|
||||
}
|
||||
|
||||
const RootLayoutNav = () => {
|
||||
const colorScheme = useColorScheme()
|
||||
const [settings, setSettings] = React.useState<Setting>({
|
||||
theme: 'auto',
|
||||
color: 'default',
|
||||
language: 'auto',
|
||||
})
|
||||
|
||||
// Load settings from the device
|
||||
React.useEffect(() => {
|
||||
if (Platform.OS !== 'web') {
|
||||
SecureStore.getItemAsync('settings').then((result) => {
|
||||
if (result === null) {
|
||||
SecureStore.setItemAsync('settings', JSON.stringify(settings)).then(
|
||||
(res) => console.log(res),
|
||||
)
|
||||
}
|
||||
|
||||
setSettings(JSON.parse(result ?? JSON.stringify(settings)))
|
||||
})
|
||||
} else {
|
||||
setSettings({ ...settings, theme: colorScheme ?? 'light' })
|
||||
}
|
||||
|
||||
// eslint-disable-next-line react-hooks/exhaustive-deps
|
||||
}, [])
|
||||
|
||||
React.useEffect(() => {
|
||||
if (settings.language === 'auto') {
|
||||
Locales.locale = Localization.getLocales()[0].languageCode ?? 'en'
|
||||
} else {
|
||||
Locales.locale = settings.language
|
||||
}
|
||||
|
||||
// eslint-disable-next-line react-hooks/exhaustive-deps
|
||||
}, [])
|
||||
|
||||
return (
|
||||
<PaperProvider
|
||||
theme={
|
||||
Themes[
|
||||
settings.theme === 'auto' ? (colorScheme ?? 'dark') : settings.theme
|
||||
][settings.color]
|
||||
}
|
||||
>
|
||||
<Stack
|
||||
screenOptions={{
|
||||
animation: 'slide_from_bottom',
|
||||
header: (props) => (
|
||||
<StackHeader navProps={props} children={undefined} />
|
||||
),
|
||||
}}
|
||||
>
|
||||
<Stack.Screen name="(auth)" options={{ headerShown: false }} />
|
||||
<Stack.Screen name="(tabs)" options={{ headerShown: false }} />
|
||||
<Stack.Screen name="drawer" options={{ headerShown: false }} />
|
||||
<Stack.Screen name="search" options={{ title: Locales.t('search') }} />
|
||||
<Stack.Screen
|
||||
name="modal"
|
||||
options={{ title: Locales.t('titleModal'), presentation: 'modal' }}
|
||||
/>
|
||||
</Stack>
|
||||
</PaperProvider>
|
||||
)
|
||||
}
|
||||
|
||||
export default RootLayout
|
||||
124
app/drawer/_layout.tsx
Normal file
@@ -0,0 +1,124 @@
|
||||
import { router } from 'expo-router'
|
||||
import { Drawer } from 'expo-router/drawer'
|
||||
import React from 'react'
|
||||
import { GestureHandlerRootView } from 'react-native-gesture-handler'
|
||||
import { Appbar, Menu, Tooltip, useTheme } from 'react-native-paper'
|
||||
|
||||
import Locales from '@/lib/locales'
|
||||
import { DrawerContent, DrawerHeader } from '@/lib/ui'
|
||||
|
||||
const DrawerLayout = () => {
|
||||
const theme = useTheme()
|
||||
const [visible, setVisible] = React.useState(false)
|
||||
|
||||
return (
|
||||
<GestureHandlerRootView style={{ flex: 1 }}>
|
||||
<Drawer
|
||||
drawerContent={(props) => (
|
||||
<DrawerContent
|
||||
navProps={props}
|
||||
showDivider={false}
|
||||
children={undefined}
|
||||
title="Drawer Navigation"
|
||||
/>
|
||||
)}
|
||||
screenOptions={{
|
||||
drawerStyle: {
|
||||
backgroundColor: theme.colors.background,
|
||||
paddingTop: 32,
|
||||
},
|
||||
header: (props) => (
|
||||
<DrawerHeader navProps={props} children={undefined} />
|
||||
),
|
||||
}}
|
||||
>
|
||||
<Drawer.Screen
|
||||
name="index"
|
||||
options={{
|
||||
drawerLabel: Locales.t('titleHome'),
|
||||
title: Locales.t('titleHome'),
|
||||
headerRight: () => (
|
||||
<>
|
||||
<Tooltip title={Locales.t('search')}>
|
||||
<Appbar.Action
|
||||
icon="magnify"
|
||||
onPress={() => router.push('/search')}
|
||||
/>
|
||||
</Tooltip>
|
||||
<Menu
|
||||
statusBarHeight={48}
|
||||
visible={visible}
|
||||
onDismiss={() => setVisible(false)}
|
||||
anchor={
|
||||
<Tooltip title={Locales.t('options')}>
|
||||
<Appbar.Action
|
||||
icon="dots-vertical"
|
||||
onPress={() => setVisible(true)}
|
||||
/>
|
||||
</Tooltip>
|
||||
}
|
||||
>
|
||||
<Menu.Item
|
||||
title={Locales.t('titleSettings')}
|
||||
leadingIcon="cog"
|
||||
onPress={() => router.push('/drawer/settings')}
|
||||
/>
|
||||
<Menu.Item
|
||||
title={Locales.t('stackNav')}
|
||||
leadingIcon="card-multiple-outline"
|
||||
onPress={() => router.push('/modal')}
|
||||
/>
|
||||
<Menu.Item
|
||||
title={Locales.t('drawerNav')}
|
||||
leadingIcon="gesture-swipe"
|
||||
onPress={() => router.push('/drawer')}
|
||||
/>
|
||||
</Menu>
|
||||
</>
|
||||
),
|
||||
}}
|
||||
/>
|
||||
<Drawer.Screen
|
||||
name="profile"
|
||||
options={{
|
||||
drawerLabel: Locales.t('profile'),
|
||||
title: Locales.t('profile'),
|
||||
headerRight: () => (
|
||||
<>
|
||||
<Tooltip title={Locales.t('search')}>
|
||||
<Appbar.Action
|
||||
icon="magnify"
|
||||
onPress={() => router.push('/search')}
|
||||
/>
|
||||
</Tooltip>
|
||||
<Tooltip title={Locales.t('titleSettings')}>
|
||||
<Appbar.Action
|
||||
icon="cog"
|
||||
onPress={() => router.push('/(tabs)/settings')}
|
||||
/>
|
||||
</Tooltip>
|
||||
</>
|
||||
),
|
||||
}}
|
||||
/>
|
||||
<Drawer.Screen
|
||||
name="settings"
|
||||
options={{
|
||||
drawerLabel: Locales.t('titleSettings'),
|
||||
title: Locales.t('titleSettings'),
|
||||
headerRight: () => (
|
||||
<Tooltip title={Locales.t('stackNav')}>
|
||||
<Appbar.Action
|
||||
icon="card-multiple-outline"
|
||||
onPress={() => router.push('/modal')}
|
||||
/>
|
||||
</Tooltip>
|
||||
),
|
||||
}}
|
||||
/>
|
||||
</Drawer>
|
||||
</GestureHandlerRootView>
|
||||
)
|
||||
}
|
||||
|
||||
export default DrawerLayout
|
||||
13
app/drawer/index.tsx
Normal file
@@ -0,0 +1,13 @@
|
||||
import React from 'react'
|
||||
import { Surface } from 'react-native-paper'
|
||||
|
||||
import Locales from '@/lib/locales'
|
||||
import { ScreenInfo, styles } from '@/lib/ui'
|
||||
|
||||
const DrawerHome = () => (
|
||||
<Surface style={styles.screen}>
|
||||
<ScreenInfo title={Locales.t('titleHome')} path="app/drawer/index.tsx" />
|
||||
</Surface>
|
||||
)
|
||||
|
||||
export default DrawerHome
|
||||
34
app/drawer/profile.tsx
Normal file
@@ -0,0 +1,34 @@
|
||||
import { router } from 'expo-router'
|
||||
import React from 'react'
|
||||
import { Button, Surface } from 'react-native-paper'
|
||||
|
||||
import Locales from '@/lib/locales'
|
||||
import { ScreenInfo, styles } from '@/lib/ui'
|
||||
|
||||
const Profile = () => (
|
||||
<Surface style={styles.screen}>
|
||||
<ScreenInfo title={Locales.t('profile')} path="app/(tabs)/profile.tsx" />
|
||||
|
||||
<Surface
|
||||
elevation={0}
|
||||
style={{
|
||||
padding: 16,
|
||||
gap: 16,
|
||||
position: 'absolute',
|
||||
left: 0,
|
||||
right: 0,
|
||||
bottom: 0,
|
||||
}}
|
||||
>
|
||||
<Button mode="contained" onPress={() => router.push('/(auth)/login')}>
|
||||
Login
|
||||
</Button>
|
||||
|
||||
<Button mode="contained" onPress={() => router.push('/(auth)/signup')}>
|
||||
Sign Up
|
||||
</Button>
|
||||
</Surface>
|
||||
</Surface>
|
||||
)
|
||||
|
||||
export default Profile
|
||||
310
app/drawer/settings.tsx
Normal file
@@ -0,0 +1,310 @@
|
||||
import * as SecureStore from 'expo-secure-store'
|
||||
import React from 'react'
|
||||
import { Platform, useColorScheme } from 'react-native'
|
||||
import {
|
||||
Surface,
|
||||
List,
|
||||
Menu,
|
||||
Button,
|
||||
IconButton,
|
||||
Snackbar,
|
||||
Icon,
|
||||
} from 'react-native-paper'
|
||||
|
||||
import Locales from '@/lib/locales'
|
||||
import { Color, Language, Setting } from '@/lib/types'
|
||||
import { Colors, LoadingIndicator, ScreenInfo, styles } from '@/lib/ui'
|
||||
import { Languages } from '@/lib/utils'
|
||||
|
||||
const Settings = () => {
|
||||
const colorScheme = useColorScheme()
|
||||
const [loading, setLoading] = React.useState<boolean>(false)
|
||||
const [message, setMessage] = React.useState({ visible: false, content: '' })
|
||||
const [settings, setSettings] = React.useState<Setting>({
|
||||
color: 'default',
|
||||
language: 'auto',
|
||||
theme: 'auto',
|
||||
})
|
||||
const [display, setDisplay] = React.useState({
|
||||
color: false,
|
||||
language: false,
|
||||
theme: false,
|
||||
})
|
||||
|
||||
React.useEffect(() => {
|
||||
setLoading(true)
|
||||
|
||||
if (Platform.OS !== 'web') {
|
||||
SecureStore.getItemAsync('settings')
|
||||
.then((result) =>
|
||||
setSettings(JSON.parse(result ?? JSON.stringify(settings))),
|
||||
)
|
||||
.catch((res) =>
|
||||
setMessage({
|
||||
visible: true,
|
||||
content: res.message,
|
||||
}),
|
||||
)
|
||||
}
|
||||
|
||||
setLoading(false)
|
||||
// eslint-disable-next-line react-hooks/exhaustive-deps
|
||||
}, [])
|
||||
|
||||
const themeColors =
|
||||
Colors[
|
||||
settings.theme === 'auto' ? (colorScheme ?? 'light') : settings.theme
|
||||
]
|
||||
|
||||
return (
|
||||
<Surface style={{ flex: 1 }}>
|
||||
{loading ? (
|
||||
<LoadingIndicator />
|
||||
) : (
|
||||
<Surface elevation={0}>
|
||||
<List.AccordionGroup>
|
||||
<List.Accordion
|
||||
id="1"
|
||||
title={Locales.t('appearance')}
|
||||
left={(props) => <List.Icon {...props} icon="palette" />}
|
||||
>
|
||||
<List.Item
|
||||
title={Locales.t('language')}
|
||||
description={Locales.t('changeLanguage')}
|
||||
left={(props) => <List.Icon {...props} icon="translate" />}
|
||||
right={(props) => (
|
||||
<Menu
|
||||
visible={display.language}
|
||||
onDismiss={() =>
|
||||
setDisplay({ ...display, language: false })
|
||||
}
|
||||
anchor={
|
||||
<IconButton
|
||||
{...props}
|
||||
icon="pencil"
|
||||
onPress={() =>
|
||||
setDisplay({ ...display, language: true })
|
||||
}
|
||||
/>
|
||||
}
|
||||
>
|
||||
<Menu.Item
|
||||
title="System"
|
||||
trailingIcon={
|
||||
settings.language === 'auto' ? 'check' : undefined
|
||||
}
|
||||
onPress={() => {
|
||||
setSettings({ ...settings, language: 'auto' })
|
||||
setDisplay({ ...display, language: false })
|
||||
}}
|
||||
/>
|
||||
{Object.entries(Languages).map((lang) => (
|
||||
<Menu.Item
|
||||
key={lang[0]}
|
||||
title={`${lang[1].name} / ${lang[1].nativeName}`}
|
||||
trailingIcon={
|
||||
settings.language === lang[0] ? 'check' : undefined
|
||||
}
|
||||
onPress={() => {
|
||||
setSettings({
|
||||
...settings,
|
||||
language: lang[0] as Language,
|
||||
})
|
||||
setDisplay({ ...display, language: false })
|
||||
}}
|
||||
/>
|
||||
))}
|
||||
</Menu>
|
||||
)}
|
||||
/>
|
||||
<List.Item
|
||||
title={Locales.t('mode')}
|
||||
description={Locales.t('changeMode')}
|
||||
left={(props) => (
|
||||
<List.Icon
|
||||
{...props}
|
||||
icon={
|
||||
settings.theme === 'auto'
|
||||
? 'theme-light-dark'
|
||||
: settings.theme === 'light'
|
||||
? 'weather-sunny'
|
||||
: 'weather-night'
|
||||
}
|
||||
/>
|
||||
)}
|
||||
right={(props) => (
|
||||
<Menu
|
||||
visible={display.theme}
|
||||
onDismiss={() => setDisplay({ ...display, theme: false })}
|
||||
anchor={
|
||||
<IconButton
|
||||
{...props}
|
||||
icon="pencil"
|
||||
onPress={() => setDisplay({ ...display, theme: true })}
|
||||
/>
|
||||
}
|
||||
>
|
||||
<Menu.Item
|
||||
title={Locales.t('system')}
|
||||
leadingIcon="theme-light-dark"
|
||||
trailingIcon={
|
||||
settings.theme === 'auto' ? 'check' : undefined
|
||||
}
|
||||
onPress={() => {
|
||||
setSettings({ ...settings, theme: 'auto' })
|
||||
setDisplay({ ...display, theme: false })
|
||||
}}
|
||||
/>
|
||||
<Menu.Item
|
||||
title={Locales.t('lightMode')}
|
||||
leadingIcon="weather-sunny"
|
||||
trailingIcon={
|
||||
settings.theme === 'light' ? 'check' : undefined
|
||||
}
|
||||
onPress={() => {
|
||||
setSettings({ ...settings, theme: 'light' })
|
||||
setDisplay({ ...display, theme: false })
|
||||
}}
|
||||
/>
|
||||
<Menu.Item
|
||||
title={Locales.t('darkMode')}
|
||||
leadingIcon="weather-night"
|
||||
trailingIcon={
|
||||
settings.theme === 'dark' ? 'check' : undefined
|
||||
}
|
||||
onPress={() => {
|
||||
setSettings({ ...settings, theme: 'dark' })
|
||||
setDisplay({ ...display, theme: false })
|
||||
}}
|
||||
/>
|
||||
</Menu>
|
||||
)}
|
||||
/>
|
||||
<List.Item
|
||||
title={Locales.t('color')}
|
||||
description={Locales.t('changeColor')}
|
||||
left={(props) => (
|
||||
<List.Icon
|
||||
{...props}
|
||||
icon="palette-swatch-variant"
|
||||
color={
|
||||
Colors[
|
||||
settings.theme === 'auto'
|
||||
? (colorScheme ?? 'light')
|
||||
: settings.theme
|
||||
][settings.color]?.primary
|
||||
}
|
||||
/>
|
||||
)}
|
||||
right={(props) => (
|
||||
<Menu
|
||||
visible={display.color}
|
||||
onDismiss={() => setDisplay({ ...display, color: false })}
|
||||
anchor={
|
||||
<IconButton
|
||||
{...props}
|
||||
icon="pencil"
|
||||
onPress={() => setDisplay({ ...display, color: true })}
|
||||
/>
|
||||
}
|
||||
>
|
||||
{Object.keys(Colors.light).map((color) => (
|
||||
<Surface
|
||||
key={color}
|
||||
elevation={0}
|
||||
style={{
|
||||
width: '100%',
|
||||
flexDirection: 'row',
|
||||
alignItems: 'center',
|
||||
}}
|
||||
>
|
||||
<Surface
|
||||
elevation={0}
|
||||
style={{
|
||||
padding: 4,
|
||||
marginLeft: 8,
|
||||
borderRadius: 16,
|
||||
backgroundColor:
|
||||
color !== settings.color
|
||||
? undefined
|
||||
: themeColors[color]?.primary,
|
||||
}}
|
||||
>
|
||||
<Icon
|
||||
size={24}
|
||||
source="palette"
|
||||
color={
|
||||
color !== settings.color
|
||||
? themeColors[color as Color]?.primary
|
||||
: themeColors[color].onPrimary
|
||||
}
|
||||
/>
|
||||
</Surface>
|
||||
|
||||
<Menu.Item
|
||||
key={color}
|
||||
title={Locales.t(color)}
|
||||
onPress={() => {
|
||||
setSettings({
|
||||
...settings,
|
||||
color: color as Color,
|
||||
})
|
||||
setDisplay({ ...display, color: false })
|
||||
}}
|
||||
/>
|
||||
</Surface>
|
||||
))}
|
||||
</Menu>
|
||||
)}
|
||||
/>
|
||||
</List.Accordion>
|
||||
</List.AccordionGroup>
|
||||
</Surface>
|
||||
)}
|
||||
|
||||
<Surface elevation={0} style={styles.screen}>
|
||||
<ScreenInfo
|
||||
title={Locales.t('titleSettings')}
|
||||
path="app/(tabs)/settings.tsx"
|
||||
/>
|
||||
</Surface>
|
||||
|
||||
<Button
|
||||
mode="contained"
|
||||
style={{ margin: 16 }}
|
||||
onPress={() =>
|
||||
Platform.OS !== 'web'
|
||||
? SecureStore.setItemAsync('settings', JSON.stringify(settings))
|
||||
.then(() =>
|
||||
setMessage({
|
||||
visible: true,
|
||||
content: Locales.t('restartApp'),
|
||||
}),
|
||||
)
|
||||
.catch((res) =>
|
||||
setMessage({
|
||||
visible: true,
|
||||
content: res.message,
|
||||
}),
|
||||
)
|
||||
: setMessage({
|
||||
visible: true,
|
||||
content: Locales.t('notAvailable'),
|
||||
})
|
||||
}
|
||||
>
|
||||
{Locales.t('save')}
|
||||
</Button>
|
||||
|
||||
<Snackbar
|
||||
visible={message.visible}
|
||||
onDismiss={() => setMessage({ ...message, visible: false })}
|
||||
onIconPress={() => setMessage({ ...message, visible: false })}
|
||||
>
|
||||
{message.content}
|
||||
</Snackbar>
|
||||
</Surface>
|
||||
)
|
||||
}
|
||||
|
||||
export default Settings
|
||||
18
app/modal.tsx
Normal file
@@ -0,0 +1,18 @@
|
||||
import { StatusBar } from 'expo-status-bar'
|
||||
import React from 'react'
|
||||
import { Platform } from 'react-native'
|
||||
import { Surface } from 'react-native-paper'
|
||||
|
||||
import Locales from '@/lib/locales'
|
||||
import { ScreenInfo, styles } from '@/lib/ui'
|
||||
|
||||
const Modal = () => (
|
||||
<Surface style={styles.screen}>
|
||||
<ScreenInfo title={Locales.t('titleModal')} path="app/modal.tsx" />
|
||||
|
||||
{/* Use a light status bar on iOS to account for the black space above the modal */}
|
||||
<StatusBar style={Platform.OS === 'ios' ? 'light' : 'auto'} />
|
||||
</Surface>
|
||||
)
|
||||
|
||||
export default Modal
|
||||
39
app/search.tsx
Normal file
@@ -0,0 +1,39 @@
|
||||
import React from 'react'
|
||||
import { Searchbar, Surface } from 'react-native-paper'
|
||||
|
||||
import Locales from '@/lib/locales'
|
||||
import { ScreenInfo, styles } from '@/lib/ui'
|
||||
|
||||
const Search = () => {
|
||||
const [query, setQuery] = React.useState('')
|
||||
const [loading, setLoading] = React.useState(false)
|
||||
|
||||
// Search logic
|
||||
React.useEffect(() => {
|
||||
if (query !== '') {
|
||||
setLoading(true)
|
||||
}
|
||||
|
||||
setTimeout(() => {
|
||||
setLoading(false)
|
||||
}, 1000)
|
||||
}, [query])
|
||||
|
||||
return (
|
||||
<Surface style={{ flex: 1, gap: 16 }}>
|
||||
<Searchbar
|
||||
value={query}
|
||||
loading={loading}
|
||||
onChangeText={(v) => setQuery(v)}
|
||||
placeholder="Type here to search..."
|
||||
style={{ marginTop: 16, marginHorizontal: 16 }}
|
||||
/>
|
||||
|
||||
<Surface style={styles.screen}>
|
||||
<ScreenInfo title={Locales.t('search')} path="app/search.tsx" />
|
||||
</Surface>
|
||||
</Surface>
|
||||
)
|
||||
}
|
||||
|
||||
export default Search
|
||||
BIN
assets/images/adaptive-icon.png
Normal file
|
After Width: | Height: | Size: 17 KiB |
BIN
assets/images/favicon.png
Normal file
|
After Width: | Height: | Size: 1.4 KiB |
BIN
assets/images/icon.png
Normal file
|
After Width: | Height: | Size: 22 KiB |
BIN
assets/images/splash.png
Normal file
|
After Width: | Height: | Size: 46 KiB |
6
babel.config.ts
Normal file
@@ -0,0 +1,6 @@
|
||||
module.exports = function (api: { cache: (arg0: boolean) => void }) {
|
||||
api.cache(true)
|
||||
return {
|
||||
presets: ['babel-preset-expo'],
|
||||
}
|
||||
}
|
||||
50
lib/locales/ar.ts
Normal file
@@ -0,0 +1,50 @@
|
||||
/**
|
||||
* Arabic Translations
|
||||
*/
|
||||
|
||||
const Arabic = {
|
||||
login: 'تسجيل الدخول',
|
||||
signup: 'إنشاء حساب',
|
||||
profile: 'الملف الشخصي',
|
||||
options: 'الخيارات',
|
||||
search: 'البحث',
|
||||
stackNav: 'التنقل المكدس',
|
||||
drawerNav: 'التنقل الدرج',
|
||||
appearance: 'المظهر',
|
||||
language: 'اللغة',
|
||||
changeLanguage: 'تغيير لغة التطبيق',
|
||||
system: 'النظام',
|
||||
mode: 'الوضع',
|
||||
changeMode: 'التبديل بين الوضع الفاتح والوضع الداكن',
|
||||
lightMode: 'فاتح',
|
||||
darkMode: 'داكن',
|
||||
color: 'اللون',
|
||||
changeColor: 'تغيير لون السمة',
|
||||
changeScreenCode:
|
||||
'قم بتغيير أي نص، احفظ الملف، وسيتم تحديث التطبيق الخاص بك تلقائيًا',
|
||||
goHome: 'الذهاب إلى الشاشة الرئيسية',
|
||||
openScreenCode: 'افتح الكود لهذه الشاشة',
|
||||
save: 'حفظ',
|
||||
screen404: 'هذه الشاشة غير موجودة',
|
||||
titleHome: 'الرئيسية',
|
||||
titleModal: 'مشروط',
|
||||
titleNotFound: 'لم يتم العثور',
|
||||
titleSettings: 'الإعدادات',
|
||||
restartApp: 'أعد تشغيل التطبيق لتطبيق التغييرات',
|
||||
notAvailable: 'Expo SecureStore غير متوفر للويب',
|
||||
adaptive: 'تلقائي',
|
||||
default: 'افتراضي',
|
||||
orange: 'برتقالي',
|
||||
red: 'أحمر',
|
||||
violet: 'بنفسجي',
|
||||
indigo: 'أزرق داكن',
|
||||
blue: 'أزرق',
|
||||
teal: 'أزرق فاتح',
|
||||
cyan: 'سماوي',
|
||||
green: 'أخضر',
|
||||
lime: 'ليموني',
|
||||
olive: 'زيتوني',
|
||||
brown: 'بني',
|
||||
}
|
||||
|
||||
export default Arabic
|
||||
50
lib/locales/en.ts
Normal file
@@ -0,0 +1,50 @@
|
||||
/**
|
||||
* English Translations
|
||||
*/
|
||||
|
||||
const English = {
|
||||
login: 'Log in',
|
||||
signup: 'Sign up',
|
||||
profile: 'Profile',
|
||||
options: 'Options',
|
||||
search: 'Search',
|
||||
stackNav: 'Stack Navigation',
|
||||
drawerNav: 'Drawer Navigation',
|
||||
appearance: 'Appearance',
|
||||
language: 'Language',
|
||||
changeLanguage: "Change app's language",
|
||||
system: 'System',
|
||||
mode: 'Mode',
|
||||
changeMode: 'Switch between light and dark mode',
|
||||
lightMode: 'Light',
|
||||
darkMode: 'Dark',
|
||||
color: 'Color',
|
||||
changeColor: 'Change theme color',
|
||||
changeScreenCode:
|
||||
'Change any of the text, save the file, and your app will automatically update',
|
||||
goHome: 'Go to home screen',
|
||||
openScreenCode: 'Open up the code for this screen',
|
||||
save: 'Save',
|
||||
screen404: "This screen doesn't exist",
|
||||
titleHome: 'Home',
|
||||
titleModal: 'Modal',
|
||||
titleNotFound: 'Not Found',
|
||||
titleSettings: 'Settings',
|
||||
restartApp: 'Restart the app to apply changes',
|
||||
notAvailable: 'Expo SecureStore is not available for web',
|
||||
adaptive: 'adaptive',
|
||||
default: 'default',
|
||||
orange: 'orange',
|
||||
red: 'red',
|
||||
violet: 'violet',
|
||||
indigo: 'indigo',
|
||||
blue: 'blue',
|
||||
teal: 'teal',
|
||||
cyan: 'cyan',
|
||||
green: 'green',
|
||||
lime: 'lime',
|
||||
olive: 'olive',
|
||||
brown: 'brown',
|
||||
}
|
||||
|
||||
export default English
|
||||
19
lib/locales/index.ts
Normal file
@@ -0,0 +1,19 @@
|
||||
/**
|
||||
* Locales
|
||||
*/
|
||||
|
||||
import { I18n } from 'i18n-js'
|
||||
|
||||
import Arabic from '@/lib/locales/ar'
|
||||
import English from '@/lib/locales/en'
|
||||
import Turkish from '@/lib/locales/tr'
|
||||
|
||||
const Locales = new I18n({
|
||||
ar: Arabic,
|
||||
en: English,
|
||||
tr: Turkish,
|
||||
})
|
||||
|
||||
Locales.enableFallback = true
|
||||
|
||||
export default Locales
|
||||
48
lib/locales/tr.ts
Normal file
@@ -0,0 +1,48 @@
|
||||
/**
|
||||
* Turkish Translations
|
||||
*/
|
||||
|
||||
const Turkish = {
|
||||
login: 'Giriş',
|
||||
signup: 'Hesap aç',
|
||||
profile: 'Profil',
|
||||
search: 'Ara',
|
||||
drawerNav: 'Çekmece Navigasyon',
|
||||
appearance: 'Görünüm',
|
||||
language: 'Dil',
|
||||
changeLanguage: 'Uygulamanın dilini değiştir',
|
||||
system: 'Sistem',
|
||||
mode: 'Mod',
|
||||
changeMode: 'Aydınlık ve karanlık mod arasında geçiş yap',
|
||||
lightMode: 'Aydınlık',
|
||||
darkMode: 'Karanlık',
|
||||
color: 'Renk',
|
||||
changeColor: 'Tema rengini değiştir',
|
||||
changeScreenCode:
|
||||
'Herhangi bir metni değiştirin, dosyayı kaydedin ve uygulamanız otomatik olarak güncellenecektir',
|
||||
goHome: 'Ana ekrana git',
|
||||
openScreenCode: 'Bu ekranın kodunu aç',
|
||||
save: 'Kaydet',
|
||||
screen404: 'Bu ekran mevcut değil',
|
||||
titleHome: 'Ana Sayfa',
|
||||
titleModal: 'Modal',
|
||||
titleNotFound: 'Bulunamadı',
|
||||
titleSettings: 'Ayarlar',
|
||||
restartApp: 'Değişiklikleri uygulamak için uygulamayı yeniden başlatın',
|
||||
notAvailable: 'Expo SecureStore web için kullanılabilir değil',
|
||||
adaptive: 'adaptive',
|
||||
default: 'varsayılan',
|
||||
orange: 'turuncu',
|
||||
red: 'kırmızı',
|
||||
violet: 'mor',
|
||||
indigo: 'lacivert',
|
||||
blue: 'mavi',
|
||||
teal: 'turkuaz',
|
||||
cyan: 'gökyüzü mavi',
|
||||
green: 'yeşil',
|
||||
lime: 'limon yeşili',
|
||||
olive: 'zeytin yeşili',
|
||||
brown: 'kahverengi',
|
||||
}
|
||||
|
||||
export default Turkish
|
||||
5
lib/types/Color.ts
Normal file
@@ -0,0 +1,5 @@
|
||||
import { Colors } from '@/lib/ui'
|
||||
|
||||
type Color = keyof typeof Colors.light
|
||||
|
||||
export default Color
|
||||
5
lib/types/Language.ts
Normal file
@@ -0,0 +1,5 @@
|
||||
import { Languages } from '@/lib/utils'
|
||||
|
||||
type Language = keyof typeof Languages
|
||||
|
||||
export default Language
|
||||
9
lib/types/Setting.ts
Normal file
@@ -0,0 +1,9 @@
|
||||
import { Color, Language } from '@/lib/types'
|
||||
|
||||
type Setting = {
|
||||
color: Color
|
||||
theme: 'light' | 'dark' | 'auto'
|
||||
language: Language | 'auto'
|
||||
}
|
||||
|
||||
export default Setting
|
||||
9
lib/types/index.ts
Normal file
@@ -0,0 +1,9 @@
|
||||
/**
|
||||
* Types
|
||||
*/
|
||||
|
||||
import Color from '@/lib/types/Color'
|
||||
import Language from '@/lib/types/Language'
|
||||
import Setting from '@/lib/types/Setting'
|
||||
|
||||
export type { Color, Language, Setting }
|
||||
40
lib/ui/components/DrawerContent.tsx
Normal file
@@ -0,0 +1,40 @@
|
||||
import { DrawerContentComponentProps } from '@react-navigation/drawer'
|
||||
import { router } from 'expo-router'
|
||||
import React from 'react'
|
||||
import { Drawer, DrawerSectionProps } from 'react-native-paper'
|
||||
|
||||
import Locales from '@/lib/locales'
|
||||
|
||||
interface DrawerContentProps extends DrawerSectionProps {
|
||||
navProps: DrawerContentComponentProps
|
||||
}
|
||||
|
||||
const DrawerContent = (props: DrawerContentProps) => (
|
||||
<Drawer.Section {...props}>
|
||||
<Drawer.Item
|
||||
label={Locales.t('goHome')}
|
||||
icon="arrow-left"
|
||||
onPress={() => router.replace('/')}
|
||||
/>
|
||||
<Drawer.Item
|
||||
label={Locales.t('titleHome')}
|
||||
icon="home"
|
||||
active={props.navProps.state.index === 0}
|
||||
onPress={() => router.push('/drawer')}
|
||||
/>
|
||||
<Drawer.Item
|
||||
label={Locales.t('profile')}
|
||||
icon="account"
|
||||
active={props.navProps.state.index === 1}
|
||||
onPress={() => router.push('/drawer/profile')}
|
||||
/>
|
||||
<Drawer.Item
|
||||
label={Locales.t('titleSettings')}
|
||||
icon="cog"
|
||||
active={props.navProps.state.index === 2}
|
||||
onPress={() => router.push('/drawer/settings')}
|
||||
/>
|
||||
</Drawer.Section>
|
||||
)
|
||||
|
||||
export default DrawerContent
|
||||
27
lib/ui/components/DrawerHeader.tsx
Normal file
@@ -0,0 +1,27 @@
|
||||
import { DrawerHeaderProps as BaseProps } from '@react-navigation/drawer'
|
||||
import { getHeaderTitle } from '@react-navigation/elements'
|
||||
import React from 'react'
|
||||
import { Appbar, AppbarProps } from 'react-native-paper'
|
||||
|
||||
interface DrawerHeaderProps extends AppbarProps {
|
||||
navProps: BaseProps
|
||||
}
|
||||
|
||||
const DrawerHeader = (props: DrawerHeaderProps) => (
|
||||
<Appbar.Header {...props}>
|
||||
<Appbar.Action
|
||||
icon="menu"
|
||||
onPress={() => props.navProps.navigation.openDrawer()}
|
||||
/>
|
||||
|
||||
<Appbar.Content
|
||||
title={getHeaderTitle(props.navProps.options, props.navProps.route.name)}
|
||||
/>
|
||||
|
||||
{props.navProps.options.headerRight
|
||||
? props.navProps.options.headerRight({})
|
||||
: undefined}
|
||||
</Appbar.Header>
|
||||
)
|
||||
|
||||
export default DrawerHeader
|
||||
30
lib/ui/components/GradientBackground.tsx
Normal file
@@ -0,0 +1,30 @@
|
||||
import { Canvas, LinearGradient, Rect, vec } from '@shopify/react-native-skia'
|
||||
import { Platform, useWindowDimensions } from 'react-native'
|
||||
import { useTheme } from 'react-native-paper'
|
||||
|
||||
const GradientBackground = (props: { height?: 'full' }) => {
|
||||
const theme = useTheme()
|
||||
const { height, width } = useWindowDimensions()
|
||||
|
||||
return Platform.OS !== 'web' ? (
|
||||
<Canvas
|
||||
style={{
|
||||
left: 0,
|
||||
right: 0,
|
||||
position: 'absolute',
|
||||
height: props.height ? height : 300,
|
||||
width,
|
||||
}}
|
||||
>
|
||||
<Rect x={0} y={0} width={width} height={props.height ? height : 300}>
|
||||
<LinearGradient
|
||||
start={vec(0, 0)}
|
||||
end={vec(width, width)}
|
||||
colors={[theme.colors.primary, theme.colors.inversePrimary]}
|
||||
/>
|
||||
</Rect>
|
||||
</Canvas>
|
||||
) : undefined
|
||||
}
|
||||
|
||||
export default GradientBackground
|
||||
13
lib/ui/components/LoadingIndicator.tsx
Normal file
@@ -0,0 +1,13 @@
|
||||
import React from 'react'
|
||||
import { ActivityIndicator, Surface } from 'react-native-paper'
|
||||
|
||||
const LoadingIndicator = () => (
|
||||
<Surface
|
||||
elevation={0}
|
||||
style={{ flex: 1, alignItems: 'center', justifyContent: 'center' }}
|
||||
>
|
||||
<ActivityIndicator />
|
||||
</Surface>
|
||||
)
|
||||
|
||||
export default LoadingIndicator
|
||||
24
lib/ui/components/ScreenInfo.tsx
Normal file
@@ -0,0 +1,24 @@
|
||||
import { Chip, Text } from 'react-native-paper'
|
||||
|
||||
import Locales from '@/lib/locales'
|
||||
import GradientBackground from '@/lib/ui/components/GradientBackground'
|
||||
|
||||
const ScreenInfo = (props: { title: string; path: string }) => (
|
||||
<>
|
||||
<GradientBackground />
|
||||
|
||||
<Text variant="displaySmall">{props.title}</Text>
|
||||
|
||||
<Text variant="bodyLarge">{Locales.t('openScreenCode')}</Text>
|
||||
|
||||
<Chip textStyle={{ fontFamily: 'JetBrainsMono_400Regular' }}>
|
||||
{props.path}
|
||||
</Chip>
|
||||
|
||||
<Text variant="bodyLarge" style={{ textAlign: 'center' }}>
|
||||
{Locales.t('changeScreenCode')}
|
||||
</Text>
|
||||
</>
|
||||
)
|
||||
|
||||
export default ScreenInfo
|
||||
34
lib/ui/components/StackHeader.tsx
Normal file
@@ -0,0 +1,34 @@
|
||||
import { getHeaderTitle } from '@react-navigation/elements'
|
||||
import { NativeStackHeaderProps } from '@react-navigation/native-stack'
|
||||
import React from 'react'
|
||||
import { Appbar, AppbarProps } from 'react-native-paper'
|
||||
|
||||
interface StackHeaderProps extends AppbarProps {
|
||||
navProps: NativeStackHeaderProps
|
||||
}
|
||||
|
||||
const StackHeader = (props: StackHeaderProps) => (
|
||||
<Appbar.Header {...props}>
|
||||
{props.navProps.options.headerLeft
|
||||
? props.navProps.options.headerLeft({
|
||||
canGoBack: props.navProps.navigation.canGoBack(),
|
||||
})
|
||||
: undefined}
|
||||
|
||||
{props.navProps.back ? (
|
||||
<Appbar.BackAction onPress={props.navProps.navigation.goBack} />
|
||||
) : null}
|
||||
|
||||
<Appbar.Content
|
||||
title={getHeaderTitle(props.navProps.options, props.navProps.route.name)}
|
||||
/>
|
||||
|
||||
{props.navProps.options.headerRight
|
||||
? props.navProps.options.headerRight({
|
||||
canGoBack: props.navProps.navigation.canGoBack(),
|
||||
})
|
||||
: undefined}
|
||||
</Appbar.Header>
|
||||
)
|
||||
|
||||
export default StackHeader
|
||||
49
lib/ui/components/TabBar.tsx
Normal file
@@ -0,0 +1,49 @@
|
||||
import { BottomTabBarProps } from '@react-navigation/bottom-tabs'
|
||||
import { CommonActions } from '@react-navigation/native'
|
||||
import React from 'react'
|
||||
import { BottomNavigation } from 'react-native-paper'
|
||||
|
||||
const TabBar = (props: BottomTabBarProps) => (
|
||||
<BottomNavigation.Bar
|
||||
shifting
|
||||
navigationState={props.state}
|
||||
safeAreaInsets={props.insets}
|
||||
onTabPress={({ route, preventDefault }) => {
|
||||
const event = props.navigation.emit({
|
||||
type: 'tabPress',
|
||||
target: route.key,
|
||||
canPreventDefault: true,
|
||||
})
|
||||
|
||||
if (event.defaultPrevented) {
|
||||
preventDefault()
|
||||
} else {
|
||||
props.navigation.dispatch({
|
||||
...CommonActions.navigate(route.name, route.params),
|
||||
target: props.state.key,
|
||||
})
|
||||
}
|
||||
}}
|
||||
renderIcon={({ route, focused, color }) => {
|
||||
const { options } = props.descriptors[route.key]
|
||||
if (options.tabBarIcon) {
|
||||
return options.tabBarIcon({ focused, color, size: 24 })
|
||||
}
|
||||
|
||||
return null
|
||||
}}
|
||||
getLabelText={({ route }) => {
|
||||
const { options } = props.descriptors[route.key]
|
||||
const label =
|
||||
options.tabBarLabel !== undefined
|
||||
? options.tabBarLabel
|
||||
: options.title !== undefined
|
||||
? options.title
|
||||
: route.title
|
||||
|
||||
return label
|
||||
}}
|
||||
/>
|
||||
)
|
||||
|
||||
export default TabBar
|
||||
26
lib/ui/components/TabsHeader.tsx
Normal file
@@ -0,0 +1,26 @@
|
||||
import { BottomTabHeaderProps } from '@react-navigation/bottom-tabs'
|
||||
import { getHeaderTitle } from '@react-navigation/elements'
|
||||
import React from 'react'
|
||||
import { Appbar, AppbarProps } from 'react-native-paper'
|
||||
|
||||
interface TabsHeaderProps extends AppbarProps {
|
||||
navProps: BottomTabHeaderProps
|
||||
}
|
||||
|
||||
const TabsHeader = (props: TabsHeaderProps) => (
|
||||
<Appbar.Header {...props}>
|
||||
{props.navProps.options.headerLeft
|
||||
? props.navProps.options.headerLeft({})
|
||||
: undefined}
|
||||
|
||||
<Appbar.Content
|
||||
title={getHeaderTitle(props.navProps.options, props.navProps.route.name)}
|
||||
/>
|
||||
|
||||
{props.navProps.options.headerRight
|
||||
? props.navProps.options.headerRight({})
|
||||
: undefined}
|
||||
</Appbar.Header>
|
||||
)
|
||||
|
||||
export default TabsHeader
|
||||
23
lib/ui/components/index.ts
Normal file
@@ -0,0 +1,23 @@
|
||||
/**
|
||||
* Components
|
||||
*/
|
||||
|
||||
import DrawerContent from '@/lib/ui/components/DrawerContent'
|
||||
import DrawerHeader from '@/lib/ui/components/DrawerHeader'
|
||||
import GradientBackground from '@/lib/ui/components/GradientBackground'
|
||||
import LoadingIndicator from '@/lib/ui/components/LoadingIndicator'
|
||||
import ScreenInfo from '@/lib/ui/components/ScreenInfo'
|
||||
import StackHeader from '@/lib/ui/components/StackHeader'
|
||||
import TabBar from '@/lib/ui/components/TabBar'
|
||||
import TabsHeader from '@/lib/ui/components/TabsHeader'
|
||||
|
||||
export {
|
||||
DrawerContent,
|
||||
DrawerHeader,
|
||||
GradientBackground,
|
||||
LoadingIndicator,
|
||||
ScreenInfo,
|
||||
StackHeader,
|
||||
TabBar,
|
||||
TabsHeader,
|
||||
}
|
||||
6
lib/ui/index.tsx
Normal file
@@ -0,0 +1,6 @@
|
||||
/**
|
||||
* UI
|
||||
*/
|
||||
|
||||
export * from '@/lib/ui/components'
|
||||
export * from '@/lib/ui/styles'
|
||||
902
lib/ui/styles/colors.ts
Normal file
@@ -0,0 +1,902 @@
|
||||
/**
|
||||
* Custom colors for using with themes
|
||||
*/
|
||||
|
||||
import { MD3DarkTheme, MD3LightTheme } from 'react-native-paper'
|
||||
|
||||
const Colors = {
|
||||
light: {
|
||||
default: {
|
||||
primary: MD3LightTheme.colors.primary,
|
||||
onPrimary: MD3LightTheme.colors.onPrimary,
|
||||
},
|
||||
orange: {
|
||||
primary: 'rgb(176, 46, 0)',
|
||||
onPrimary: 'rgb(255, 255, 255)',
|
||||
primaryContainer: 'rgb(255, 219, 209)',
|
||||
onPrimaryContainer: 'rgb(59, 9, 0)',
|
||||
secondary: 'rgb(119, 87, 78)',
|
||||
onSecondary: 'rgb(255, 255, 255)',
|
||||
secondaryContainer: 'rgb(255, 219, 209)',
|
||||
onSecondaryContainer: 'rgb(44, 21, 15)',
|
||||
tertiary: 'rgb(108, 93, 47)',
|
||||
onTertiary: 'rgb(255, 255, 255)',
|
||||
tertiaryContainer: 'rgb(246, 225, 166)',
|
||||
onTertiaryContainer: 'rgb(35, 27, 0)',
|
||||
background: 'rgb(255, 251, 255)',
|
||||
onBackground: 'rgb(32, 26, 24)',
|
||||
surface: 'rgb(255, 251, 255)',
|
||||
onSurface: 'rgb(32, 26, 24)',
|
||||
surfaceVariant: 'rgb(245, 222, 216)',
|
||||
onSurfaceVariant: 'rgb(83, 67, 63)',
|
||||
outline: 'rgb(133, 115, 110)',
|
||||
outlineVariant: 'rgb(216, 194, 188)',
|
||||
shadow: 'rgb(0, 0, 0)',
|
||||
scrim: 'rgb(0, 0, 0)',
|
||||
inverseSurface: 'rgb(54, 47, 45)',
|
||||
inverseOnSurface: 'rgb(251, 238, 235)',
|
||||
inversePrimary: 'rgb(255, 181, 160)',
|
||||
elevation: {
|
||||
level0: 'transparent',
|
||||
level1: 'rgb(251, 241, 242)',
|
||||
level2: 'rgb(249, 235, 235)',
|
||||
level3: 'rgb(246, 229, 227)',
|
||||
level4: 'rgb(246, 226, 224)',
|
||||
level5: 'rgb(244, 222, 219)',
|
||||
},
|
||||
surfaceDisabled: 'rgba(32, 26, 24, 0.12)',
|
||||
onSurfaceDisabled: 'rgba(32, 26, 24, 0.38)',
|
||||
backdrop: 'rgba(59, 45, 41, 0.4)',
|
||||
},
|
||||
red: {
|
||||
primary: 'rgb(185, 12, 85)',
|
||||
onPrimary: 'rgb(255, 255, 255)',
|
||||
primaryContainer: 'rgb(255, 217, 223)',
|
||||
onPrimaryContainer: 'rgb(63, 0, 24)',
|
||||
secondary: 'rgb(117, 86, 92)',
|
||||
onSecondary: 'rgb(255, 255, 255)',
|
||||
secondaryContainer: 'rgb(255, 217, 223)',
|
||||
onSecondaryContainer: 'rgb(43, 21, 26)',
|
||||
tertiary: 'rgb(122, 87, 51)',
|
||||
onTertiary: 'rgb(255, 255, 255)',
|
||||
tertiaryContainer: 'rgb(255, 220, 189)',
|
||||
onTertiaryContainer: 'rgb(44, 22, 0)',
|
||||
background: 'rgb(255, 251, 255)',
|
||||
onBackground: 'rgb(32, 26, 27)',
|
||||
surface: 'rgb(255, 251, 255)',
|
||||
onSurface: 'rgb(32, 26, 27)',
|
||||
surfaceVariant: 'rgb(243, 221, 224)',
|
||||
onSurfaceVariant: 'rgb(82, 67, 70)',
|
||||
outline: 'rgb(132, 115, 117)',
|
||||
outlineVariant: 'rgb(214, 194, 196)',
|
||||
shadow: 'rgb(0, 0, 0)',
|
||||
scrim: 'rgb(0, 0, 0)',
|
||||
inverseSurface: 'rgb(53, 47, 48)',
|
||||
inverseOnSurface: 'rgb(250, 238, 239)',
|
||||
inversePrimary: 'rgb(255, 177, 194)',
|
||||
elevation: {
|
||||
level0: 'transparent',
|
||||
level1: 'rgb(252, 239, 247)',
|
||||
level2: 'rgb(249, 232, 241)',
|
||||
level3: 'rgb(247, 225, 236)',
|
||||
level4: 'rgb(247, 222, 235)',
|
||||
level5: 'rgb(245, 218, 231)',
|
||||
},
|
||||
surfaceDisabled: 'rgba(32, 26, 27, 0.12)',
|
||||
onSurfaceDisabled: 'rgba(32, 26, 27, 0.38)',
|
||||
backdrop: 'rgba(58, 45, 47, 0.4)',
|
||||
},
|
||||
violet: {
|
||||
primary: 'rgb(140, 51, 179)',
|
||||
onPrimary: 'rgb(255, 255, 255)',
|
||||
primaryContainer: 'rgb(248, 216, 255)',
|
||||
onPrimaryContainer: 'rgb(50, 0, 71)',
|
||||
secondary: 'rgb(105, 89, 109)',
|
||||
onSecondary: 'rgb(255, 255, 255)',
|
||||
secondaryContainer: 'rgb(241, 220, 244)',
|
||||
onSecondaryContainer: 'rgb(35, 23, 40)',
|
||||
tertiary: 'rgb(129, 82, 80)',
|
||||
onTertiary: 'rgb(255, 255, 255)',
|
||||
tertiaryContainer: 'rgb(255, 218, 216)',
|
||||
onTertiaryContainer: 'rgb(51, 17, 17)',
|
||||
background: 'rgb(255, 251, 255)',
|
||||
onBackground: 'rgb(30, 27, 30)',
|
||||
surface: 'rgb(255, 251, 255)',
|
||||
onSurface: 'rgb(30, 27, 30)',
|
||||
surfaceVariant: 'rgb(235, 223, 233)',
|
||||
onSurfaceVariant: 'rgb(76, 68, 77)',
|
||||
outline: 'rgb(125, 116, 125)',
|
||||
outlineVariant: 'rgb(206, 195, 205)',
|
||||
shadow: 'rgb(0, 0, 0)',
|
||||
scrim: 'rgb(0, 0, 0)',
|
||||
inverseSurface: 'rgb(51, 47, 51)',
|
||||
inverseOnSurface: 'rgb(246, 239, 243)',
|
||||
inversePrimary: 'rgb(235, 178, 255)',
|
||||
elevation: {
|
||||
level0: 'transparent',
|
||||
level1: 'rgb(249, 241, 251)',
|
||||
level2: 'rgb(246, 235, 249)',
|
||||
level3: 'rgb(242, 229, 247)',
|
||||
level4: 'rgb(241, 227, 246)',
|
||||
level5: 'rgb(239, 223, 244)',
|
||||
},
|
||||
surfaceDisabled: 'rgba(30, 27, 30, 0.12)',
|
||||
onSurfaceDisabled: 'rgba(30, 27, 30, 0.38)',
|
||||
backdrop: 'rgba(53, 46, 54, 0.4)',
|
||||
},
|
||||
indigo: {
|
||||
primary: 'rgb(104, 71, 192)',
|
||||
onPrimary: 'rgb(255, 255, 255)',
|
||||
primaryContainer: 'rgb(232, 221, 255)',
|
||||
onPrimaryContainer: 'rgb(33, 0, 93)',
|
||||
secondary: 'rgb(97, 91, 113)',
|
||||
onSecondary: 'rgb(255, 255, 255)',
|
||||
secondaryContainer: 'rgb(232, 222, 248)',
|
||||
onSecondaryContainer: 'rgb(29, 25, 43)',
|
||||
tertiary: 'rgb(125, 82, 96)',
|
||||
onTertiary: 'rgb(255, 255, 255)',
|
||||
tertiaryContainer: 'rgb(255, 217, 227)',
|
||||
onTertiaryContainer: 'rgb(49, 16, 29)',
|
||||
background: 'rgb(255, 251, 255)',
|
||||
onBackground: 'rgb(28, 27, 30)',
|
||||
surface: 'rgb(255, 251, 255)',
|
||||
onSurface: 'rgb(28, 27, 30)',
|
||||
surfaceVariant: 'rgb(230, 224, 236)',
|
||||
onSurfaceVariant: 'rgb(72, 69, 78)',
|
||||
outline: 'rgb(121, 117, 127)',
|
||||
outlineVariant: 'rgb(202, 196, 207)',
|
||||
shadow: 'rgb(0, 0, 0)',
|
||||
scrim: 'rgb(0, 0, 0)',
|
||||
inverseSurface: 'rgb(49, 48, 51)',
|
||||
inverseOnSurface: 'rgb(244, 239, 244)',
|
||||
inversePrimary: 'rgb(206, 189, 255)',
|
||||
elevation: {
|
||||
level0: 'transparent',
|
||||
level1: 'rgb(247, 242, 252)',
|
||||
level2: 'rgb(243, 237, 250)',
|
||||
level3: 'rgb(238, 231, 248)',
|
||||
level4: 'rgb(237, 229, 247)',
|
||||
level5: 'rgb(234, 226, 246)',
|
||||
},
|
||||
surfaceDisabled: 'rgba(28, 27, 30, 0.12)',
|
||||
onSurfaceDisabled: 'rgba(28, 27, 30, 0.38)',
|
||||
backdrop: 'rgba(50, 47, 56, 0.4)',
|
||||
},
|
||||
blue: {
|
||||
primary: 'rgb(0, 99, 154)',
|
||||
onPrimary: 'rgb(255, 255, 255)',
|
||||
primaryContainer: 'rgb(206, 229, 255)',
|
||||
onPrimaryContainer: 'rgb(0, 29, 50)',
|
||||
secondary: 'rgb(81, 96, 111)',
|
||||
onSecondary: 'rgb(255, 255, 255)',
|
||||
secondaryContainer: 'rgb(213, 228, 247)',
|
||||
onSecondaryContainer: 'rgb(14, 29, 42)',
|
||||
tertiary: 'rgb(104, 88, 122)',
|
||||
onTertiary: 'rgb(255, 255, 255)',
|
||||
tertiaryContainer: 'rgb(238, 219, 255)',
|
||||
onTertiaryContainer: 'rgb(35, 21, 51)',
|
||||
background: 'rgb(252, 252, 255)',
|
||||
onBackground: 'rgb(26, 28, 30)',
|
||||
surface: 'rgb(252, 252, 255)',
|
||||
onSurface: 'rgb(26, 28, 30)',
|
||||
surfaceVariant: 'rgb(222, 227, 235)',
|
||||
onSurfaceVariant: 'rgb(66, 71, 78)',
|
||||
outline: 'rgb(114, 119, 127)',
|
||||
outlineVariant: 'rgb(194, 199, 207)',
|
||||
shadow: 'rgb(0, 0, 0)',
|
||||
scrim: 'rgb(0, 0, 0)',
|
||||
inverseSurface: 'rgb(47, 48, 51)',
|
||||
inverseOnSurface: 'rgb(240, 240, 244)',
|
||||
inversePrimary: 'rgb(150, 204, 255)',
|
||||
elevation: {
|
||||
level0: 'transparent',
|
||||
level1: 'rgb(239, 244, 250)',
|
||||
level2: 'rgb(232, 240, 247)',
|
||||
level3: 'rgb(224, 235, 244)',
|
||||
level4: 'rgb(222, 234, 243)',
|
||||
level5: 'rgb(217, 231, 241)',
|
||||
},
|
||||
surfaceDisabled: 'rgba(26, 28, 30, 0.12)',
|
||||
onSurfaceDisabled: 'rgba(26, 28, 30, 0.38)',
|
||||
backdrop: 'rgba(44, 49, 55, 0.4)',
|
||||
},
|
||||
teal: {
|
||||
primary: 'rgb(0, 104, 116)',
|
||||
onPrimary: 'rgb(255, 255, 255)',
|
||||
primaryContainer: 'rgb(151, 240, 255)',
|
||||
onPrimaryContainer: 'rgb(0, 31, 36)',
|
||||
secondary: 'rgb(74, 98, 103)',
|
||||
onSecondary: 'rgb(255, 255, 255)',
|
||||
secondaryContainer: 'rgb(205, 231, 236)',
|
||||
onSecondaryContainer: 'rgb(5, 31, 35)',
|
||||
tertiary: 'rgb(82, 94, 125)',
|
||||
onTertiary: 'rgb(255, 255, 255)',
|
||||
tertiaryContainer: 'rgb(218, 226, 255)',
|
||||
onTertiaryContainer: 'rgb(14, 27, 55)',
|
||||
background: 'rgb(250, 253, 253)',
|
||||
onBackground: 'rgb(25, 28, 29)',
|
||||
surface: 'rgb(250, 253, 253)',
|
||||
onSurface: 'rgb(25, 28, 29)',
|
||||
surfaceVariant: 'rgb(219, 228, 230)',
|
||||
onSurfaceVariant: 'rgb(63, 72, 74)',
|
||||
outline: 'rgb(111, 121, 122)',
|
||||
outlineVariant: 'rgb(191, 200, 202)',
|
||||
shadow: 'rgb(0, 0, 0)',
|
||||
scrim: 'rgb(0, 0, 0)',
|
||||
inverseSurface: 'rgb(46, 49, 50)',
|
||||
inverseOnSurface: 'rgb(239, 241, 241)',
|
||||
inversePrimary: 'rgb(79, 216, 235)',
|
||||
elevation: {
|
||||
level0: 'transparent',
|
||||
level1: 'rgb(238, 246, 246)',
|
||||
level2: 'rgb(230, 241, 242)',
|
||||
level3: 'rgb(223, 237, 238)',
|
||||
level4: 'rgb(220, 235, 237)',
|
||||
level5: 'rgb(215, 232, 234)',
|
||||
},
|
||||
surfaceDisabled: 'rgba(25, 28, 29, 0.12)',
|
||||
onSurfaceDisabled: 'rgba(25, 28, 29, 0.38)',
|
||||
backdrop: 'rgba(41, 50, 52, 0.4)',
|
||||
},
|
||||
cyan: {
|
||||
primary: 'rgb(0, 107, 94)',
|
||||
onPrimary: 'rgb(255, 255, 255)',
|
||||
primaryContainer: 'rgb(118, 248, 226)',
|
||||
onPrimaryContainer: 'rgb(0, 32, 27)',
|
||||
secondary: 'rgb(74, 99, 94)',
|
||||
onSecondary: 'rgb(255, 255, 255)',
|
||||
secondaryContainer: 'rgb(205, 232, 225)',
|
||||
onSecondaryContainer: 'rgb(6, 32, 27)',
|
||||
tertiary: 'rgb(68, 97, 121)',
|
||||
onTertiary: 'rgb(255, 255, 255)',
|
||||
tertiaryContainer: 'rgb(202, 230, 255)',
|
||||
onTertiaryContainer: 'rgb(0, 30, 48)',
|
||||
background: 'rgb(250, 253, 251)',
|
||||
onBackground: 'rgb(25, 28, 27)',
|
||||
surface: 'rgb(250, 253, 251)',
|
||||
onSurface: 'rgb(25, 28, 27)',
|
||||
surfaceVariant: 'rgb(218, 229, 225)',
|
||||
onSurfaceVariant: 'rgb(63, 73, 70)',
|
||||
outline: 'rgb(111, 121, 118)',
|
||||
outlineVariant: 'rgb(190, 201, 197)',
|
||||
shadow: 'rgb(0, 0, 0)',
|
||||
scrim: 'rgb(0, 0, 0)',
|
||||
inverseSurface: 'rgb(45, 49, 48)',
|
||||
inverseOnSurface: 'rgb(239, 241, 239)',
|
||||
inversePrimary: 'rgb(85, 219, 198)',
|
||||
elevation: {
|
||||
level0: 'transparent',
|
||||
level1: 'rgb(238, 246, 243)',
|
||||
level2: 'rgb(230, 241, 238)',
|
||||
level3: 'rgb(223, 237, 234)',
|
||||
level4: 'rgb(220, 236, 232)',
|
||||
level5: 'rgb(215, 233, 229)',
|
||||
},
|
||||
surfaceDisabled: 'rgba(25, 28, 27, 0.12)',
|
||||
onSurfaceDisabled: 'rgba(25, 28, 27, 0.38)',
|
||||
backdrop: 'rgba(41, 50, 48, 0.4)',
|
||||
},
|
||||
green: {
|
||||
primary: 'rgb(16, 109, 32)',
|
||||
onPrimary: 'rgb(255, 255, 255)',
|
||||
primaryContainer: 'rgb(157, 248, 152)',
|
||||
onPrimaryContainer: 'rgb(0, 34, 4)',
|
||||
secondary: 'rgb(82, 99, 79)',
|
||||
onSecondary: 'rgb(255, 255, 255)',
|
||||
secondaryContainer: 'rgb(213, 232, 206)',
|
||||
onSecondaryContainer: 'rgb(17, 31, 15)',
|
||||
tertiary: 'rgb(56, 101, 106)',
|
||||
onTertiary: 'rgb(255, 255, 255)',
|
||||
tertiaryContainer: 'rgb(188, 235, 240)',
|
||||
onTertiaryContainer: 'rgb(0, 32, 35)',
|
||||
background: 'rgb(252, 253, 246)',
|
||||
onBackground: 'rgb(26, 28, 25)',
|
||||
surface: 'rgb(252, 253, 246)',
|
||||
onSurface: 'rgb(26, 28, 25)',
|
||||
surfaceVariant: 'rgb(222, 229, 216)',
|
||||
onSurfaceVariant: 'rgb(66, 73, 64)',
|
||||
outline: 'rgb(114, 121, 111)',
|
||||
outlineVariant: 'rgb(194, 201, 189)',
|
||||
shadow: 'rgb(0, 0, 0)',
|
||||
scrim: 'rgb(0, 0, 0)',
|
||||
inverseSurface: 'rgb(47, 49, 45)',
|
||||
inverseOnSurface: 'rgb(240, 241, 235)',
|
||||
inversePrimary: 'rgb(130, 219, 126)',
|
||||
elevation: {
|
||||
level0: 'transparent',
|
||||
level1: 'rgb(240, 246, 235)',
|
||||
level2: 'rgb(233, 242, 229)',
|
||||
level3: 'rgb(226, 237, 223)',
|
||||
level4: 'rgb(224, 236, 220)',
|
||||
level5: 'rgb(219, 233, 216)',
|
||||
},
|
||||
surfaceDisabled: 'rgba(26, 28, 25, 0.12)',
|
||||
onSurfaceDisabled: 'rgba(26, 28, 25, 0.38)',
|
||||
backdrop: 'rgba(44, 50, 42, 0.4)',
|
||||
},
|
||||
lime: {
|
||||
primary: 'rgb(56, 107, 1)',
|
||||
onPrimary: 'rgb(255, 255, 255)',
|
||||
primaryContainer: 'rgb(183, 244, 129)',
|
||||
onPrimaryContainer: 'rgb(13, 32, 0)',
|
||||
secondary: 'rgb(87, 98, 74)',
|
||||
onSecondary: 'rgb(255, 255, 255)',
|
||||
secondaryContainer: 'rgb(218, 231, 201)',
|
||||
onSecondaryContainer: 'rgb(21, 30, 12)',
|
||||
tertiary: 'rgb(56, 102, 100)',
|
||||
onTertiary: 'rgb(255, 255, 255)',
|
||||
tertiaryContainer: 'rgb(187, 236, 233)',
|
||||
onTertiaryContainer: 'rgb(0, 32, 31)',
|
||||
background: 'rgb(253, 253, 245)',
|
||||
onBackground: 'rgb(26, 28, 24)',
|
||||
surface: 'rgb(253, 253, 245)',
|
||||
onSurface: 'rgb(26, 28, 24)',
|
||||
surfaceVariant: 'rgb(224, 228, 214)',
|
||||
onSurfaceVariant: 'rgb(68, 72, 62)',
|
||||
outline: 'rgb(116, 121, 109)',
|
||||
outlineVariant: 'rgb(196, 200, 186)',
|
||||
shadow: 'rgb(0, 0, 0)',
|
||||
scrim: 'rgb(0, 0, 0)',
|
||||
inverseSurface: 'rgb(47, 49, 44)',
|
||||
inverseOnSurface: 'rgb(241, 241, 234)',
|
||||
inversePrimary: 'rgb(156, 215, 105)',
|
||||
elevation: {
|
||||
level0: 'transparent',
|
||||
level1: 'rgb(243, 246, 233)',
|
||||
level2: 'rgb(237, 241, 226)',
|
||||
level3: 'rgb(231, 237, 218)',
|
||||
level4: 'rgb(229, 236, 216)',
|
||||
level5: 'rgb(225, 233, 211)',
|
||||
},
|
||||
surfaceDisabled: 'rgba(26, 28, 24, 0.12)',
|
||||
onSurfaceDisabled: 'rgba(26, 28, 24, 0.38)',
|
||||
backdrop: 'rgba(45, 50, 40, 0.4)',
|
||||
},
|
||||
olive: {
|
||||
primary: 'rgb(95, 98, 0)',
|
||||
onPrimary: 'rgb(255, 255, 255)',
|
||||
primaryContainer: 'rgb(229, 234, 93)',
|
||||
onPrimaryContainer: 'rgb(28, 29, 0)',
|
||||
secondary: 'rgb(96, 96, 67)',
|
||||
onSecondary: 'rgb(255, 255, 255)',
|
||||
secondaryContainer: 'rgb(229, 229, 192)',
|
||||
onSecondaryContainer: 'rgb(28, 29, 6)',
|
||||
tertiary: 'rgb(61, 102, 88)',
|
||||
onTertiary: 'rgb(255, 255, 255)',
|
||||
tertiaryContainer: 'rgb(191, 236, 218)',
|
||||
onTertiaryContainer: 'rgb(0, 33, 24)',
|
||||
background: 'rgb(255, 251, 255)',
|
||||
onBackground: 'rgb(28, 28, 23)',
|
||||
surface: 'rgb(255, 251, 255)',
|
||||
onSurface: 'rgb(28, 28, 23)',
|
||||
surfaceVariant: 'rgb(229, 227, 209)',
|
||||
onSurfaceVariant: 'rgb(72, 71, 59)',
|
||||
outline: 'rgb(121, 120, 105)',
|
||||
outlineVariant: 'rgb(201, 199, 182)',
|
||||
shadow: 'rgb(0, 0, 0)',
|
||||
scrim: 'rgb(0, 0, 0)',
|
||||
inverseSurface: 'rgb(49, 49, 43)',
|
||||
inverseOnSurface: 'rgb(244, 240, 232)',
|
||||
inversePrimary: 'rgb(200, 206, 68)',
|
||||
elevation: {
|
||||
level0: 'transparent',
|
||||
level1: 'rgb(247, 243, 242)',
|
||||
level2: 'rgb(242, 239, 235)',
|
||||
level3: 'rgb(237, 234, 227)',
|
||||
level4: 'rgb(236, 233, 224)',
|
||||
level5: 'rgb(233, 230, 219)',
|
||||
},
|
||||
surfaceDisabled: 'rgba(28, 28, 23, 0.12)',
|
||||
onSurfaceDisabled: 'rgba(28, 28, 23, 0.38)',
|
||||
backdrop: 'rgba(49, 49, 37, 0.4)',
|
||||
},
|
||||
brown: {
|
||||
primary: 'rgb(155, 68, 39)',
|
||||
onPrimary: 'rgb(255, 255, 255)',
|
||||
primaryContainer: 'rgb(255, 219, 208)',
|
||||
onPrimaryContainer: 'rgb(58, 11, 0)',
|
||||
secondary: 'rgb(119, 87, 77)',
|
||||
onSecondary: 'rgb(255, 255, 255)',
|
||||
secondaryContainer: 'rgb(255, 219, 208)',
|
||||
onSecondaryContainer: 'rgb(44, 22, 14)',
|
||||
tertiary: 'rgb(107, 94, 47)',
|
||||
onTertiary: 'rgb(255, 255, 255)',
|
||||
tertiaryContainer: 'rgb(244, 226, 167)',
|
||||
onTertiaryContainer: 'rgb(34, 27, 0)',
|
||||
background: 'rgb(255, 251, 255)',
|
||||
onBackground: 'rgb(32, 26, 24)',
|
||||
surface: 'rgb(255, 251, 255)',
|
||||
onSurface: 'rgb(32, 26, 24)',
|
||||
surfaceVariant: 'rgb(245, 222, 215)',
|
||||
onSurfaceVariant: 'rgb(83, 67, 63)',
|
||||
outline: 'rgb(133, 115, 110)',
|
||||
outlineVariant: 'rgb(216, 194, 188)',
|
||||
shadow: 'rgb(0, 0, 0)',
|
||||
scrim: 'rgb(0, 0, 0)',
|
||||
inverseSurface: 'rgb(54, 47, 45)',
|
||||
inverseOnSurface: 'rgb(251, 238, 234)',
|
||||
inversePrimary: 'rgb(255, 181, 158)',
|
||||
elevation: {
|
||||
level0: 'transparent',
|
||||
level1: 'rgb(250, 242, 244)',
|
||||
level2: 'rgb(247, 236, 238)',
|
||||
level3: 'rgb(244, 231, 231)',
|
||||
level4: 'rgb(243, 229, 229)',
|
||||
level5: 'rgb(241, 225, 225)',
|
||||
},
|
||||
surfaceDisabled: 'rgba(32, 26, 24, 0.12)',
|
||||
onSurfaceDisabled: 'rgba(32, 26, 24, 0.38)',
|
||||
backdrop: 'rgba(59, 45, 41, 0.4)',
|
||||
},
|
||||
},
|
||||
dark: {
|
||||
default: {
|
||||
primary: MD3DarkTheme.colors.primary,
|
||||
onPrimary: MD3DarkTheme.colors.onPrimary,
|
||||
},
|
||||
orange: {
|
||||
primary: 'rgb(255, 183, 134)',
|
||||
onPrimary: 'rgb(80, 36, 0)',
|
||||
primaryContainer: 'rgb(114, 54, 0)',
|
||||
onPrimaryContainer: 'rgb(255, 220, 198)',
|
||||
secondary: 'rgb(229, 191, 168)',
|
||||
onSecondary: 'rgb(66, 43, 27)',
|
||||
secondaryContainer: 'rgb(91, 65, 48)',
|
||||
onSecondaryContainer: 'rgb(255, 220, 198)',
|
||||
tertiary: 'rgb(201, 202, 147)',
|
||||
onTertiary: 'rgb(49, 50, 10)',
|
||||
tertiaryContainer: 'rgb(72, 73, 31)',
|
||||
onTertiaryContainer: 'rgb(229, 230, 173)',
|
||||
error: 'rgb(255, 180, 171)',
|
||||
onError: 'rgb(105, 0, 5)',
|
||||
errorContainer: 'rgb(147, 0, 10)',
|
||||
onErrorContainer: 'rgb(255, 180, 171)',
|
||||
background: 'rgb(32, 26, 23)',
|
||||
onBackground: 'rgb(236, 224, 218)',
|
||||
surface: 'rgb(32, 26, 23)',
|
||||
onSurface: 'rgb(236, 224, 218)',
|
||||
surfaceVariant: 'rgb(82, 68, 60)',
|
||||
onSurfaceVariant: 'rgb(215, 195, 183)',
|
||||
outline: 'rgb(159, 141, 131)',
|
||||
outlineVariant: 'rgb(82, 68, 60)',
|
||||
shadow: 'rgb(0, 0, 0)',
|
||||
scrim: 'rgb(0, 0, 0)',
|
||||
inverseSurface: 'rgb(236, 224, 218)',
|
||||
inverseOnSurface: 'rgb(54, 47, 43)',
|
||||
inversePrimary: 'rgb(150, 73, 0)',
|
||||
elevation: {
|
||||
level0: 'transparent',
|
||||
level1: 'rgb(43, 34, 29)',
|
||||
level2: 'rgb(50, 39, 32)',
|
||||
level3: 'rgb(57, 43, 35)',
|
||||
level4: 'rgb(59, 45, 36)',
|
||||
level5: 'rgb(63, 48, 39)',
|
||||
},
|
||||
surfaceDisabled: 'rgba(236, 224, 218, 0.12)',
|
||||
onSurfaceDisabled: 'rgba(236, 224, 218, 0.38)',
|
||||
backdrop: 'rgba(58, 46, 38, 0.4)',
|
||||
},
|
||||
red: {
|
||||
primary: 'rgb(255, 177, 194)',
|
||||
onPrimary: 'rgb(102, 0, 43)',
|
||||
primaryContainer: 'rgb(143, 0, 63)',
|
||||
onPrimaryContainer: 'rgb(255, 217, 223)',
|
||||
secondary: 'rgb(228, 189, 195)',
|
||||
onSecondary: 'rgb(67, 41, 47)',
|
||||
secondaryContainer: 'rgb(91, 63, 69)',
|
||||
onSecondaryContainer: 'rgb(255, 217, 223)',
|
||||
tertiary: 'rgb(236, 190, 145)',
|
||||
onTertiary: 'rgb(70, 42, 9)',
|
||||
tertiaryContainer: 'rgb(96, 64, 29)',
|
||||
onTertiaryContainer: 'rgb(255, 220, 189)',
|
||||
error: 'rgb(255, 180, 171)',
|
||||
onError: 'rgb(105, 0, 5)',
|
||||
errorContainer: 'rgb(147, 0, 10)',
|
||||
onErrorContainer: 'rgb(255, 180, 171)',
|
||||
background: 'rgb(32, 26, 27)',
|
||||
onBackground: 'rgb(236, 224, 224)',
|
||||
surface: 'rgb(32, 26, 27)',
|
||||
onSurface: 'rgb(236, 224, 224)',
|
||||
surfaceVariant: 'rgb(82, 67, 70)',
|
||||
onSurfaceVariant: 'rgb(214, 194, 196)',
|
||||
outline: 'rgb(158, 140, 143)',
|
||||
outlineVariant: 'rgb(82, 67, 70)',
|
||||
shadow: 'rgb(0, 0, 0)',
|
||||
scrim: 'rgb(0, 0, 0)',
|
||||
inverseSurface: 'rgb(236, 224, 224)',
|
||||
inverseOnSurface: 'rgb(53, 47, 48)',
|
||||
inversePrimary: 'rgb(185, 12, 85)',
|
||||
elevation: {
|
||||
level0: 'transparent',
|
||||
level1: 'rgb(43, 34, 35)',
|
||||
level2: 'rgb(50, 38, 40)',
|
||||
level3: 'rgb(57, 43, 45)',
|
||||
level4: 'rgb(59, 44, 47)',
|
||||
level5: 'rgb(63, 47, 50)',
|
||||
},
|
||||
surfaceDisabled: 'rgba(236, 224, 224, 0.12)',
|
||||
onSurfaceDisabled: 'rgba(236, 224, 224, 0.38)',
|
||||
backdrop: 'rgba(58, 45, 47, 0.4)',
|
||||
},
|
||||
violet: {
|
||||
primary: 'rgb(235, 178, 255)',
|
||||
onPrimary: 'rgb(82, 0, 113)',
|
||||
primaryContainer: 'rgb(114, 17, 153)',
|
||||
onPrimaryContainer: 'rgb(248, 216, 255)',
|
||||
secondary: 'rgb(212, 192, 215)',
|
||||
onSecondary: 'rgb(57, 44, 61)',
|
||||
secondaryContainer: 'rgb(80, 66, 85)',
|
||||
onSecondaryContainer: 'rgb(241, 220, 244)',
|
||||
tertiary: 'rgb(245, 183, 181)',
|
||||
onTertiary: 'rgb(76, 37, 36)',
|
||||
tertiaryContainer: 'rgb(102, 59, 57)',
|
||||
onTertiaryContainer: 'rgb(255, 218, 216)',
|
||||
error: 'rgb(255, 180, 171)',
|
||||
onError: 'rgb(105, 0, 5)',
|
||||
errorContainer: 'rgb(147, 0, 10)',
|
||||
onErrorContainer: 'rgb(255, 180, 171)',
|
||||
background: 'rgb(30, 27, 30)',
|
||||
onBackground: 'rgb(232, 224, 229)',
|
||||
surface: 'rgb(30, 27, 30)',
|
||||
onSurface: 'rgb(232, 224, 229)',
|
||||
surfaceVariant: 'rgb(76, 68, 77)',
|
||||
onSurfaceVariant: 'rgb(206, 195, 205)',
|
||||
outline: 'rgb(151, 142, 151)',
|
||||
outlineVariant: 'rgb(76, 68, 77)',
|
||||
shadow: 'rgb(0, 0, 0)',
|
||||
scrim: 'rgb(0, 0, 0)',
|
||||
inverseSurface: 'rgb(232, 224, 229)',
|
||||
inverseOnSurface: 'rgb(51, 47, 51)',
|
||||
inversePrimary: 'rgb(140, 51, 179)',
|
||||
elevation: {
|
||||
level0: 'transparent',
|
||||
level1: 'rgb(40, 35, 41)',
|
||||
level2: 'rgb(46, 39, 48)',
|
||||
level3: 'rgb(53, 44, 55)',
|
||||
level4: 'rgb(55, 45, 57)',
|
||||
level5: 'rgb(59, 48, 62)',
|
||||
},
|
||||
surfaceDisabled: 'rgba(232, 224, 229, 0.12)',
|
||||
onSurfaceDisabled: 'rgba(232, 224, 229, 0.38)',
|
||||
backdrop: 'rgba(53, 46, 54, 0.4)',
|
||||
},
|
||||
indigo: {
|
||||
primary: 'rgb(206, 189, 255)',
|
||||
onPrimary: 'rgb(57, 5, 144)',
|
||||
primaryContainer: 'rgb(80, 43, 167)',
|
||||
onPrimaryContainer: 'rgb(232, 221, 255)',
|
||||
secondary: 'rgb(203, 195, 220)',
|
||||
onSecondary: 'rgb(51, 45, 65)',
|
||||
secondaryContainer: 'rgb(73, 68, 88)',
|
||||
onSecondaryContainer: 'rgb(232, 222, 248)',
|
||||
tertiary: 'rgb(239, 184, 201)',
|
||||
onTertiary: 'rgb(73, 37, 50)',
|
||||
tertiaryContainer: 'rgb(99, 59, 73)',
|
||||
onTertiaryContainer: 'rgb(255, 217, 227)',
|
||||
error: 'rgb(255, 180, 171)',
|
||||
onError: 'rgb(105, 0, 5)',
|
||||
errorContainer: 'rgb(147, 0, 10)',
|
||||
onErrorContainer: 'rgb(255, 180, 171)',
|
||||
background: 'rgb(28, 27, 30)',
|
||||
onBackground: 'rgb(230, 225, 230)',
|
||||
surface: 'rgb(28, 27, 30)',
|
||||
onSurface: 'rgb(230, 225, 230)',
|
||||
surfaceVariant: 'rgb(72, 69, 78)',
|
||||
onSurfaceVariant: 'rgb(202, 196, 207)',
|
||||
outline: 'rgb(148, 143, 153)',
|
||||
outlineVariant: 'rgb(72, 69, 78)',
|
||||
shadow: 'rgb(0, 0, 0)',
|
||||
scrim: 'rgb(0, 0, 0)',
|
||||
inverseSurface: 'rgb(230, 225, 230)',
|
||||
inverseOnSurface: 'rgb(49, 48, 51)',
|
||||
inversePrimary: 'rgb(104, 71, 192)',
|
||||
elevation: {
|
||||
level0: 'transparent',
|
||||
level1: 'rgb(37, 35, 41)',
|
||||
level2: 'rgb(42, 40, 48)',
|
||||
level3: 'rgb(48, 45, 55)',
|
||||
level4: 'rgb(49, 46, 57)',
|
||||
level5: 'rgb(53, 50, 62)',
|
||||
},
|
||||
surfaceDisabled: 'rgba(230, 225, 230, 0.12)',
|
||||
onSurfaceDisabled: 'rgba(230, 225, 230, 0.38)',
|
||||
backdrop: 'rgba(50, 47, 56, 0.4)',
|
||||
},
|
||||
blue: {
|
||||
primary: 'rgb(150, 204, 255)',
|
||||
onPrimary: 'rgb(0, 51, 83)',
|
||||
primaryContainer: 'rgb(0, 74, 117)',
|
||||
onPrimaryContainer: 'rgb(206, 229, 255)',
|
||||
secondary: 'rgb(185, 200, 218)',
|
||||
onSecondary: 'rgb(35, 50, 64)',
|
||||
secondaryContainer: 'rgb(58, 72, 87)',
|
||||
onSecondaryContainer: 'rgb(213, 228, 247)',
|
||||
tertiary: 'rgb(211, 191, 230)',
|
||||
onTertiary: 'rgb(56, 42, 73)',
|
||||
tertiaryContainer: 'rgb(79, 64, 97)',
|
||||
onTertiaryContainer: 'rgb(238, 219, 255)',
|
||||
error: 'rgb(255, 180, 171)',
|
||||
onError: 'rgb(105, 0, 5)',
|
||||
errorContainer: 'rgb(147, 0, 10)',
|
||||
onErrorContainer: 'rgb(255, 180, 171)',
|
||||
background: 'rgb(26, 28, 30)',
|
||||
onBackground: 'rgb(226, 226, 229)',
|
||||
surface: 'rgb(26, 28, 30)',
|
||||
onSurface: 'rgb(226, 226, 229)',
|
||||
surfaceVariant: 'rgb(66, 71, 78)',
|
||||
onSurfaceVariant: 'rgb(194, 199, 207)',
|
||||
outline: 'rgb(140, 145, 152)',
|
||||
outlineVariant: 'rgb(66, 71, 78)',
|
||||
shadow: 'rgb(0, 0, 0)',
|
||||
scrim: 'rgb(0, 0, 0)',
|
||||
inverseSurface: 'rgb(226, 226, 229)',
|
||||
inverseOnSurface: 'rgb(47, 48, 51)',
|
||||
inversePrimary: 'rgb(0, 99, 154)',
|
||||
elevation: {
|
||||
level0: 'transparent',
|
||||
level1: 'rgb(32, 37, 41)',
|
||||
level2: 'rgb(36, 42, 48)',
|
||||
level3: 'rgb(40, 47, 55)',
|
||||
level4: 'rgb(41, 49, 57)',
|
||||
level5: 'rgb(43, 53, 62)',
|
||||
},
|
||||
surfaceDisabled: 'rgba(226, 226, 229, 0.12)',
|
||||
onSurfaceDisabled: 'rgba(226, 226, 229, 0.38)',
|
||||
backdrop: 'rgba(44, 49, 55, 0.4)',
|
||||
},
|
||||
teal: {
|
||||
primary: 'rgb(79, 216, 235)',
|
||||
onPrimary: 'rgb(0, 54, 61)',
|
||||
primaryContainer: 'rgb(0, 79, 88)',
|
||||
onPrimaryContainer: 'rgb(151, 240, 255)',
|
||||
secondary: 'rgb(177, 203, 208)',
|
||||
onSecondary: 'rgb(28, 52, 56)',
|
||||
secondaryContainer: 'rgb(51, 75, 79)',
|
||||
onSecondaryContainer: 'rgb(205, 231, 236)',
|
||||
tertiary: 'rgb(186, 198, 234)',
|
||||
onTertiary: 'rgb(36, 48, 77)',
|
||||
tertiaryContainer: 'rgb(59, 70, 100)',
|
||||
onTertiaryContainer: 'rgb(218, 226, 255)',
|
||||
error: 'rgb(255, 180, 171)',
|
||||
onError: 'rgb(105, 0, 5)',
|
||||
errorContainer: 'rgb(147, 0, 10)',
|
||||
onErrorContainer: 'rgb(255, 180, 171)',
|
||||
background: 'rgb(25, 28, 29)',
|
||||
onBackground: 'rgb(225, 227, 227)',
|
||||
surface: 'rgb(25, 28, 29)',
|
||||
onSurface: 'rgb(225, 227, 227)',
|
||||
surfaceVariant: 'rgb(63, 72, 74)',
|
||||
onSurfaceVariant: 'rgb(191, 200, 202)',
|
||||
outline: 'rgb(137, 146, 148)',
|
||||
outlineVariant: 'rgb(63, 72, 74)',
|
||||
shadow: 'rgb(0, 0, 0)',
|
||||
scrim: 'rgb(0, 0, 0)',
|
||||
inverseSurface: 'rgb(225, 227, 227)',
|
||||
inverseOnSurface: 'rgb(46, 49, 50)',
|
||||
inversePrimary: 'rgb(0, 104, 116)',
|
||||
elevation: {
|
||||
level0: 'transparent',
|
||||
level1: 'rgb(28, 37, 39)',
|
||||
level2: 'rgb(29, 43, 46)',
|
||||
level3: 'rgb(31, 49, 52)',
|
||||
level4: 'rgb(32, 51, 54)',
|
||||
level5: 'rgb(33, 54, 58)',
|
||||
},
|
||||
surfaceDisabled: 'rgba(225, 227, 227, 0.12)',
|
||||
onSurfaceDisabled: 'rgba(225, 227, 227, 0.38)',
|
||||
backdrop: 'rgba(41, 50, 52, 0.4)',
|
||||
},
|
||||
cyan: {
|
||||
primary: 'rgb(85, 219, 198)',
|
||||
onPrimary: 'rgb(0, 55, 48)',
|
||||
primaryContainer: 'rgb(0, 80, 71)',
|
||||
onPrimaryContainer: 'rgb(118, 248, 226)',
|
||||
secondary: 'rgb(177, 204, 197)',
|
||||
onSecondary: 'rgb(28, 53, 48)',
|
||||
secondaryContainer: 'rgb(51, 75, 70)',
|
||||
onSecondaryContainer: 'rgb(205, 232, 225)',
|
||||
tertiary: 'rgb(172, 202, 229)',
|
||||
onTertiary: 'rgb(19, 51, 72)',
|
||||
tertiaryContainer: 'rgb(44, 74, 96)',
|
||||
onTertiaryContainer: 'rgb(202, 230, 255)',
|
||||
error: 'rgb(255, 180, 171)',
|
||||
onError: 'rgb(105, 0, 5)',
|
||||
errorContainer: 'rgb(147, 0, 10)',
|
||||
onErrorContainer: 'rgb(255, 180, 171)',
|
||||
background: 'rgb(25, 28, 27)',
|
||||
onBackground: 'rgb(224, 227, 225)',
|
||||
surface: 'rgb(25, 28, 27)',
|
||||
onSurface: 'rgb(224, 227, 225)',
|
||||
surfaceVariant: 'rgb(63, 73, 70)',
|
||||
onSurfaceVariant: 'rgb(190, 201, 197)',
|
||||
outline: 'rgb(137, 147, 144)',
|
||||
outlineVariant: 'rgb(63, 73, 70)',
|
||||
shadow: 'rgb(0, 0, 0)',
|
||||
scrim: 'rgb(0, 0, 0)',
|
||||
inverseSurface: 'rgb(224, 227, 225)',
|
||||
inverseOnSurface: 'rgb(45, 49, 48)',
|
||||
inversePrimary: 'rgb(0, 107, 94)',
|
||||
elevation: {
|
||||
level0: 'transparent',
|
||||
level1: 'rgb(28, 38, 36)',
|
||||
level2: 'rgb(30, 43, 41)',
|
||||
level3: 'rgb(32, 49, 46)',
|
||||
level4: 'rgb(32, 51, 48)',
|
||||
level5: 'rgb(33, 55, 51)',
|
||||
},
|
||||
surfaceDisabled: 'rgba(224, 227, 225, 0.12)',
|
||||
onSurfaceDisabled: 'rgba(224, 227, 225, 0.38)',
|
||||
backdrop: 'rgba(41, 50, 48, 0.4)',
|
||||
},
|
||||
green: {
|
||||
primary: 'rgb(130, 219, 126)',
|
||||
onPrimary: 'rgb(0, 57, 10)',
|
||||
primaryContainer: 'rgb(0, 83, 18)',
|
||||
onPrimaryContainer: 'rgb(157, 248, 152)',
|
||||
secondary: 'rgb(186, 204, 179)',
|
||||
onSecondary: 'rgb(37, 52, 35)',
|
||||
secondaryContainer: 'rgb(59, 75, 56)',
|
||||
onSecondaryContainer: 'rgb(213, 232, 206)',
|
||||
tertiary: 'rgb(160, 207, 212)',
|
||||
onTertiary: 'rgb(0, 54, 59)',
|
||||
tertiaryContainer: 'rgb(31, 77, 82)',
|
||||
onTertiaryContainer: 'rgb(188, 235, 240)',
|
||||
error: 'rgb(255, 180, 171)',
|
||||
onError: 'rgb(105, 0, 5)',
|
||||
errorContainer: 'rgb(147, 0, 10)',
|
||||
onErrorContainer: 'rgb(255, 180, 171)',
|
||||
background: 'rgb(26, 28, 25)',
|
||||
onBackground: 'rgb(226, 227, 221)',
|
||||
surface: 'rgb(26, 28, 25)',
|
||||
onSurface: 'rgb(226, 227, 221)',
|
||||
surfaceVariant: 'rgb(66, 73, 64)',
|
||||
onSurfaceVariant: 'rgb(194, 201, 189)',
|
||||
outline: 'rgb(140, 147, 136)',
|
||||
outlineVariant: 'rgb(66, 73, 64)',
|
||||
shadow: 'rgb(0, 0, 0)',
|
||||
scrim: 'rgb(0, 0, 0)',
|
||||
inverseSurface: 'rgb(226, 227, 221)',
|
||||
inverseOnSurface: 'rgb(47, 49, 45)',
|
||||
inversePrimary: 'rgb(16, 109, 32)',
|
||||
elevation: {
|
||||
level0: 'transparent',
|
||||
level1: 'rgb(31, 38, 30)',
|
||||
level2: 'rgb(34, 43, 33)',
|
||||
level3: 'rgb(37, 49, 36)',
|
||||
level4: 'rgb(39, 51, 37)',
|
||||
level5: 'rgb(41, 55, 39)',
|
||||
},
|
||||
surfaceDisabled: 'rgba(226, 227, 221, 0.12)',
|
||||
onSurfaceDisabled: 'rgba(226, 227, 221, 0.38)',
|
||||
backdrop: 'rgba(44, 50, 42, 0.4)',
|
||||
},
|
||||
lime: {
|
||||
primary: 'rgb(156, 215, 105)',
|
||||
onPrimary: 'rgb(26, 55, 0)',
|
||||
primaryContainer: 'rgb(40, 80, 0)',
|
||||
onPrimaryContainer: 'rgb(183, 244, 129)',
|
||||
secondary: 'rgb(190, 203, 174)',
|
||||
onSecondary: 'rgb(41, 52, 31)',
|
||||
secondaryContainer: 'rgb(63, 74, 52)',
|
||||
onSecondaryContainer: 'rgb(218, 231, 201)',
|
||||
tertiary: 'rgb(160, 207, 205)',
|
||||
onTertiary: 'rgb(0, 55, 54)',
|
||||
tertiaryContainer: 'rgb(30, 78, 77)',
|
||||
onTertiaryContainer: 'rgb(187, 236, 233)',
|
||||
error: 'rgb(255, 180, 171)',
|
||||
onError: 'rgb(105, 0, 5)',
|
||||
errorContainer: 'rgb(147, 0, 10)',
|
||||
onErrorContainer: 'rgb(255, 180, 171)',
|
||||
background: 'rgb(26, 28, 24)',
|
||||
onBackground: 'rgb(227, 227, 220)',
|
||||
surface: 'rgb(26, 28, 24)',
|
||||
onSurface: 'rgb(227, 227, 220)',
|
||||
surfaceVariant: 'rgb(68, 72, 62)',
|
||||
onSurfaceVariant: 'rgb(196, 200, 186)',
|
||||
outline: 'rgb(142, 146, 134)',
|
||||
outlineVariant: 'rgb(68, 72, 62)',
|
||||
shadow: 'rgb(0, 0, 0)',
|
||||
scrim: 'rgb(0, 0, 0)',
|
||||
inverseSurface: 'rgb(227, 227, 220)',
|
||||
inverseOnSurface: 'rgb(47, 49, 44)',
|
||||
inversePrimary: 'rgb(56, 107, 1)',
|
||||
elevation: {
|
||||
level0: 'transparent',
|
||||
level1: 'rgb(33, 37, 28)',
|
||||
level2: 'rgb(36, 43, 31)',
|
||||
level3: 'rgb(40, 49, 33)',
|
||||
level4: 'rgb(42, 50, 34)',
|
||||
level5: 'rgb(44, 54, 35)',
|
||||
},
|
||||
surfaceDisabled: 'rgba(227, 227, 220, 0.12)',
|
||||
onSurfaceDisabled: 'rgba(227, 227, 220, 0.38)',
|
||||
backdrop: 'rgba(45, 50, 40, 0.4)',
|
||||
},
|
||||
olive: {
|
||||
primary: 'rgb(200, 206, 68)',
|
||||
onPrimary: 'rgb(49, 51, 0)',
|
||||
primaryContainer: 'rgb(71, 74, 0)',
|
||||
onPrimaryContainer: 'rgb(229, 234, 93)',
|
||||
secondary: 'rgb(201, 201, 165)',
|
||||
onSecondary: 'rgb(49, 50, 25)',
|
||||
secondaryContainer: 'rgb(72, 72, 45)',
|
||||
onSecondaryContainer: 'rgb(229, 229, 192)',
|
||||
tertiary: 'rgb(163, 208, 190)',
|
||||
onTertiary: 'rgb(9, 55, 43)',
|
||||
tertiaryContainer: 'rgb(36, 78, 65)',
|
||||
onTertiaryContainer: 'rgb(191, 236, 218)',
|
||||
error: 'rgb(255, 180, 171)',
|
||||
onError: 'rgb(105, 0, 5)',
|
||||
errorContainer: 'rgb(147, 0, 10)',
|
||||
onErrorContainer: 'rgb(255, 180, 171)',
|
||||
background: 'rgb(28, 28, 23)',
|
||||
onBackground: 'rgb(229, 226, 218)',
|
||||
surface: 'rgb(28, 28, 23)',
|
||||
onSurface: 'rgb(229, 226, 218)',
|
||||
surfaceVariant: 'rgb(72, 71, 59)',
|
||||
onSurfaceVariant: 'rgb(201, 199, 182)',
|
||||
outline: 'rgb(147, 145, 130)',
|
||||
outlineVariant: 'rgb(72, 71, 59)',
|
||||
shadow: 'rgb(0, 0, 0)',
|
||||
scrim: 'rgb(0, 0, 0)',
|
||||
inverseSurface: 'rgb(229, 226, 218)',
|
||||
inverseOnSurface: 'rgb(49, 49, 43)',
|
||||
inversePrimary: 'rgb(95, 98, 0)',
|
||||
elevation: {
|
||||
level0: 'transparent',
|
||||
level1: 'rgb(37, 37, 25)',
|
||||
level2: 'rgb(42, 42, 27)',
|
||||
level3: 'rgb(47, 48, 28)',
|
||||
level4: 'rgb(49, 49, 28)',
|
||||
level5: 'rgb(52, 53, 29)',
|
||||
},
|
||||
surfaceDisabled: 'rgba(229, 226, 218, 0.12)',
|
||||
onSurfaceDisabled: 'rgba(229, 226, 218, 0.38)',
|
||||
backdrop: 'rgba(49, 49, 37, 0.4)',
|
||||
},
|
||||
brown: {
|
||||
primary: 'rgb(255, 181, 158)',
|
||||
onPrimary: 'rgb(93, 24, 0)',
|
||||
primaryContainer: 'rgb(124, 45, 18)',
|
||||
onPrimaryContainer: 'rgb(255, 219, 208)',
|
||||
secondary: 'rgb(231, 189, 177)',
|
||||
onSecondary: 'rgb(68, 42, 34)',
|
||||
secondaryContainer: 'rgb(93, 64, 55)',
|
||||
onSecondaryContainer: 'rgb(255, 219, 208)',
|
||||
tertiary: 'rgb(215, 198, 141)',
|
||||
onTertiary: 'rgb(58, 48, 5)',
|
||||
tertiaryContainer: 'rgb(82, 70, 26)',
|
||||
onTertiaryContainer: 'rgb(244, 226, 167)',
|
||||
error: 'rgb(255, 180, 171)',
|
||||
onError: 'rgb(105, 0, 5)',
|
||||
errorContainer: 'rgb(147, 0, 10)',
|
||||
onErrorContainer: 'rgb(255, 180, 171)',
|
||||
background: 'rgb(32, 26, 24)',
|
||||
onBackground: 'rgb(237, 224, 220)',
|
||||
surface: 'rgb(32, 26, 24)',
|
||||
onSurface: 'rgb(237, 224, 220)',
|
||||
surfaceVariant: 'rgb(83, 67, 63)',
|
||||
onSurfaceVariant: 'rgb(216, 194, 188)',
|
||||
outline: 'rgb(160, 141, 135)',
|
||||
outlineVariant: 'rgb(83, 67, 63)',
|
||||
shadow: 'rgb(0, 0, 0)',
|
||||
scrim: 'rgb(0, 0, 0)',
|
||||
inverseSurface: 'rgb(237, 224, 220)',
|
||||
inverseOnSurface: 'rgb(54, 47, 45)',
|
||||
inversePrimary: 'rgb(155, 68, 39)',
|
||||
elevation: {
|
||||
level0: 'transparent',
|
||||
level1: 'rgb(43, 34, 31)',
|
||||
level2: 'rgb(50, 38, 35)',
|
||||
level3: 'rgb(57, 43, 39)',
|
||||
level4: 'rgb(59, 45, 40)',
|
||||
level5: 'rgb(63, 48, 43)',
|
||||
},
|
||||
surfaceDisabled: 'rgba(237, 224, 220, 0.12)',
|
||||
onSurfaceDisabled: 'rgba(237, 224, 220, 0.38)',
|
||||
backdrop: 'rgba(59, 45, 41, 0.4)',
|
||||
},
|
||||
},
|
||||
}
|
||||
|
||||
export default Colors
|
||||
20
lib/ui/styles/index.ts
Normal file
@@ -0,0 +1,20 @@
|
||||
/**
|
||||
* Styles
|
||||
*/
|
||||
|
||||
import { StyleSheet } from 'react-native'
|
||||
|
||||
import Colors from '@/lib/ui/styles/colors'
|
||||
import Themes from '@/lib/ui/styles/themes'
|
||||
|
||||
const styles = StyleSheet.create({
|
||||
screen: {
|
||||
flex: 1,
|
||||
gap: 16,
|
||||
padding: 32,
|
||||
alignItems: 'center',
|
||||
justifyContent: 'center',
|
||||
},
|
||||
})
|
||||
|
||||
export { Colors, Themes, styles }
|
||||
212
lib/ui/styles/themes.ts
Normal file
@@ -0,0 +1,212 @@
|
||||
/**
|
||||
* Themes
|
||||
*/
|
||||
|
||||
import {
|
||||
DarkTheme as NavigationDarkTheme,
|
||||
DefaultTheme as NavigationDefaultTheme,
|
||||
} from '@react-navigation/native'
|
||||
import {
|
||||
adaptNavigationTheme,
|
||||
MD3LightTheme,
|
||||
MD3DarkTheme,
|
||||
configureFonts,
|
||||
} from 'react-native-paper'
|
||||
|
||||
import Colors from '@/lib/ui/styles/colors'
|
||||
|
||||
const { LightTheme, DarkTheme } = adaptNavigationTheme({
|
||||
reactNavigationLight: NavigationDefaultTheme,
|
||||
reactNavigationDark: NavigationDarkTheme,
|
||||
})
|
||||
|
||||
const fonts = configureFonts({ config: { fontFamily: 'NotoSans_400Regular' } })
|
||||
|
||||
const BaseLightTheme = {
|
||||
...LightTheme,
|
||||
...MD3LightTheme,
|
||||
fonts,
|
||||
}
|
||||
|
||||
const BaseDarkTheme = {
|
||||
...DarkTheme,
|
||||
...MD3DarkTheme,
|
||||
fonts,
|
||||
}
|
||||
|
||||
const Themes = {
|
||||
light: {
|
||||
default: {
|
||||
...BaseLightTheme,
|
||||
colors: {
|
||||
...BaseLightTheme.colors,
|
||||
...Colors.light.default,
|
||||
},
|
||||
},
|
||||
orange: {
|
||||
...BaseLightTheme,
|
||||
colors: {
|
||||
...BaseLightTheme.colors,
|
||||
...Colors.light.orange,
|
||||
},
|
||||
},
|
||||
red: {
|
||||
...BaseLightTheme,
|
||||
colors: {
|
||||
...BaseLightTheme.colors,
|
||||
...Colors.light.red,
|
||||
},
|
||||
},
|
||||
violet: {
|
||||
...BaseLightTheme,
|
||||
colors: {
|
||||
...BaseLightTheme.colors,
|
||||
...Colors.light.violet,
|
||||
},
|
||||
},
|
||||
indigo: {
|
||||
...BaseLightTheme,
|
||||
colors: {
|
||||
...BaseLightTheme.colors,
|
||||
...Colors.light.indigo,
|
||||
},
|
||||
},
|
||||
blue: {
|
||||
...BaseLightTheme,
|
||||
colors: {
|
||||
...BaseLightTheme.colors,
|
||||
...Colors.light.blue,
|
||||
},
|
||||
},
|
||||
teal: {
|
||||
...BaseLightTheme,
|
||||
colors: {
|
||||
...BaseLightTheme.colors,
|
||||
...Colors.light.teal,
|
||||
},
|
||||
},
|
||||
cyan: {
|
||||
...BaseLightTheme,
|
||||
colors: {
|
||||
...BaseLightTheme.colors,
|
||||
...Colors.light.cyan,
|
||||
},
|
||||
},
|
||||
green: {
|
||||
...BaseLightTheme,
|
||||
colors: {
|
||||
...BaseLightTheme.colors,
|
||||
...Colors.light.green,
|
||||
},
|
||||
},
|
||||
lime: {
|
||||
...BaseLightTheme,
|
||||
colors: {
|
||||
...BaseLightTheme.colors,
|
||||
...Colors.light.lime,
|
||||
},
|
||||
},
|
||||
olive: {
|
||||
...BaseLightTheme,
|
||||
colors: {
|
||||
...BaseLightTheme.colors,
|
||||
...Colors.light.olive,
|
||||
},
|
||||
},
|
||||
brown: {
|
||||
...BaseLightTheme,
|
||||
colors: {
|
||||
...BaseLightTheme.colors,
|
||||
...Colors.light.brown,
|
||||
},
|
||||
},
|
||||
},
|
||||
dark: {
|
||||
default: {
|
||||
...BaseDarkTheme,
|
||||
colors: {
|
||||
...BaseDarkTheme.colors,
|
||||
...Colors.dark.default,
|
||||
},
|
||||
},
|
||||
red: {
|
||||
...BaseDarkTheme,
|
||||
colors: {
|
||||
...BaseDarkTheme.colors,
|
||||
...Colors.dark.red,
|
||||
},
|
||||
},
|
||||
orange: {
|
||||
...BaseDarkTheme,
|
||||
colors: {
|
||||
...BaseDarkTheme.colors,
|
||||
...Colors.dark.orange,
|
||||
},
|
||||
},
|
||||
violet: {
|
||||
...BaseDarkTheme,
|
||||
colors: {
|
||||
...BaseDarkTheme.colors,
|
||||
...Colors.dark.violet,
|
||||
},
|
||||
},
|
||||
indigo: {
|
||||
...BaseDarkTheme,
|
||||
colors: {
|
||||
...BaseDarkTheme.colors,
|
||||
...Colors.dark.indigo,
|
||||
},
|
||||
},
|
||||
blue: {
|
||||
...BaseDarkTheme,
|
||||
colors: {
|
||||
...BaseDarkTheme.colors,
|
||||
...Colors.dark.blue,
|
||||
},
|
||||
},
|
||||
teal: {
|
||||
...BaseDarkTheme,
|
||||
colors: {
|
||||
...BaseDarkTheme.colors,
|
||||
...Colors.dark.teal,
|
||||
},
|
||||
},
|
||||
cyan: {
|
||||
...BaseDarkTheme,
|
||||
colors: {
|
||||
...BaseDarkTheme.colors,
|
||||
...Colors.dark.cyan,
|
||||
},
|
||||
},
|
||||
green: {
|
||||
...BaseDarkTheme,
|
||||
colors: {
|
||||
...BaseDarkTheme.colors,
|
||||
...Colors.dark.green,
|
||||
},
|
||||
},
|
||||
lime: {
|
||||
...BaseDarkTheme,
|
||||
colors: {
|
||||
...BaseDarkTheme.colors,
|
||||
...Colors.dark.lime,
|
||||
},
|
||||
},
|
||||
olive: {
|
||||
...BaseDarkTheme,
|
||||
colors: {
|
||||
...BaseDarkTheme.colors,
|
||||
...Colors.dark.olive,
|
||||
},
|
||||
},
|
||||
brown: {
|
||||
...BaseDarkTheme,
|
||||
colors: {
|
||||
...BaseDarkTheme.colors,
|
||||
...Colors.dark.brown,
|
||||
},
|
||||
},
|
||||
},
|
||||
}
|
||||
|
||||
export default Themes
|
||||
7
lib/utils/index.ts
Normal file
@@ -0,0 +1,7 @@
|
||||
/**
|
||||
* Utilities
|
||||
*/
|
||||
|
||||
import Languages from '@/lib/utils/languages'
|
||||
|
||||
export { Languages }
|
||||
20
lib/utils/languages.ts
Normal file
@@ -0,0 +1,20 @@
|
||||
/**
|
||||
* Available languages
|
||||
*/
|
||||
|
||||
const Languages = {
|
||||
ar: {
|
||||
name: 'Arabic',
|
||||
nativeName: 'العربية',
|
||||
},
|
||||
en: {
|
||||
name: 'English',
|
||||
nativeName: 'English',
|
||||
},
|
||||
tr: {
|
||||
name: 'Turkish',
|
||||
nativeName: 'Türkçe',
|
||||
},
|
||||
}
|
||||
|
||||
export default Languages
|
||||
65
package.json
Normal file
@@ -0,0 +1,65 @@
|
||||
{
|
||||
"name": "expo-react-native-paper",
|
||||
"main": "expo-router/entry",
|
||||
"version": "1.0.0",
|
||||
"scripts": {
|
||||
"start": "expo start",
|
||||
"android": "expo start --android",
|
||||
"ios": "expo start --ios",
|
||||
"web": "expo start --web",
|
||||
"test": "jest --watchAll",
|
||||
"lint": "eslint . --fix",
|
||||
"expo:fix": "expo install --fix",
|
||||
"expo:lint": "expo lint",
|
||||
"format": "prettier -w ."
|
||||
},
|
||||
"jest": {
|
||||
"preset": "jest-expo"
|
||||
},
|
||||
"dependencies": {
|
||||
"@expo-google-fonts/jetbrains-mono": "^0.2.3",
|
||||
"@expo-google-fonts/noto-sans": "^0.2.3",
|
||||
"@expo/vector-icons": "^14.0.0",
|
||||
"@react-navigation/drawer": "^6.6.15",
|
||||
"@react-navigation/native": "^6.0.2",
|
||||
"@shopify/flash-list": "1.6.4",
|
||||
"@shopify/react-native-skia": "1.2.3",
|
||||
"expo": "~51.0.31",
|
||||
"expo-font": "~12.0.5",
|
||||
"expo-linking": "~6.3.1",
|
||||
"expo-localization": "~15.0.3",
|
||||
"expo-router": "~3.5.23",
|
||||
"expo-secure-store": "~13.0.1",
|
||||
"expo-splash-screen": "~0.27.4",
|
||||
"expo-status-bar": "~1.12.1",
|
||||
"expo-system-ui": "~3.0.4",
|
||||
"expo-web-browser": "~13.0.3",
|
||||
"formik": "^2.4.6",
|
||||
"i18n-js": "^4.4.3",
|
||||
"react": "18.2.0",
|
||||
"react-dom": "18.2.0",
|
||||
"react-native": "0.74.5",
|
||||
"react-native-gesture-handler": "~2.16.1",
|
||||
"react-native-paper": "^5.12.3",
|
||||
"react-native-reanimated": "~3.10.1",
|
||||
"react-native-safe-area-context": "4.10.5",
|
||||
"react-native-screens": "3.31.1",
|
||||
"react-native-web": "~0.19.6",
|
||||
"yup": "^1.4.0",
|
||||
"expo-image": "~1.12.15"
|
||||
},
|
||||
"devDependencies": {
|
||||
"@babel/core": "^7.20.0",
|
||||
"@types/react": "~18.2.45",
|
||||
"@typescript-eslint/eslint-plugin": "^7.8.0",
|
||||
"eslint": "^8.57.0",
|
||||
"eslint-config-expo": "^7.1.2",
|
||||
"eslint-config-universe": "^12.0.0",
|
||||
"jest": "^29.2.1",
|
||||
"jest-expo": "~51.0.4",
|
||||
"prettier": "^3.2.5",
|
||||
"react-test-renderer": "18.2.0",
|
||||
"typescript": "~5.3.3"
|
||||
},
|
||||
"private": true
|
||||
}
|
||||
BIN
screenshots/home-blue-dark.png
Normal file
|
After Width: | Height: | Size: 98 KiB |
BIN
screenshots/home-default-light.png
Normal file
|
After Width: | Height: | Size: 109 KiB |
BIN
screenshots/login-violet-light.png
Normal file
|
After Width: | Height: | Size: 70 KiB |
BIN
screenshots/modal-light-red.png
Normal file
|
After Width: | Height: | Size: 96 KiB |
BIN
screenshots/profile-olive-light.png
Normal file
|
After Width: | Height: | Size: 84 KiB |
BIN
screenshots/profile-teal-dark.png
Normal file
|
After Width: | Height: | Size: 92 KiB |
BIN
screenshots/search-orange-dark.png
Normal file
|
After Width: | Height: | Size: 107 KiB |
BIN
screenshots/settings-lime-light.png
Normal file
|
After Width: | Height: | Size: 118 KiB |
BIN
screenshots/settings-violet-light.png
Normal file
|
After Width: | Height: | Size: 126 KiB |
BIN
screenshots/signup-green-dark.png
Normal file
|
After Width: | Height: | Size: 67 KiB |
10
tsconfig.json
Normal file
@@ -0,0 +1,10 @@
|
||||
{
|
||||
"extends": "expo/tsconfig.base",
|
||||
"compilerOptions": {
|
||||
"strict": true,
|
||||
"paths": {
|
||||
"@/*": ["./*"]
|
||||
}
|
||||
},
|
||||
"include": ["**/*.ts", "**/*.tsx", ".expo/types/**/*.ts", "expo-env.d.ts"]
|
||||
}
|
||||