diff --git a/.eslintrc.json b/.eslintrc.json
index dbb66640..f7971cda 100644
--- a/.eslintrc.json
+++ b/.eslintrc.json
@@ -2,6 +2,7 @@
"extends": ["airbnb", "prettier", "plugin:prettier/recommended", "eslint-config-prettier"],
"parser": "@babel/eslint-parser",
"rules": {
+ "react-native/no-unused-styles": 2,
"curly": "warn",
"import/no-unresolved": "off",
"global-require": 0,
@@ -25,7 +26,87 @@
"trailingComma": "es5",
"printWidth": 100
}
+ ],
+ "import/order": [
+ "error",
+ {
+ "groups": ["builtin", "external", "internal", "parent", "sibling", "index"],
+ "pathGroups": [
+ {
+ "pattern": "react*",
+ "group": "external",
+ "position": "before"
+ },
+ {
+ "pattern": "@common/**",
+ "group": "internal",
+ "position": "before"
+ },
+ {
+ "pattern": "@**",
+ "group": "internal",
+ "position": "before"
+ }
+ ],
+ "pathGroupsExcludedImportTypes": ["builtin"],
+ "newlines-between": "never",
+ "alphabetize": {
+ "order": "asc",
+ "caseInsensitive": true
+ }
+ }
]
},
- "plugins": ["prettier"]
+ "overrides": [
+ {
+ "files": ["**/*.test.js", "**/*.test.jsx", "**/__mocks__/**", "**/test-utils/**"],
+ "env": {
+ "jest": true
+ },
+ "rules": {
+ "react/jsx-filename-extension": "off",
+ "import/prefer-default-export": "off",
+ "react/prop-types": "off",
+ "no-unused-vars": ["error", { "argsIgnorePattern": "^_" }],
+ "react/react-in-jsx-scope": "off",
+ "react/jsx-no-useless-fragment": "off",
+ "no-nested-ternary": "off"
+ }
+ },
+ {
+ "files": ["**/*.test.jsx"],
+ "rules": {
+ "import/order": [
+ "error",
+ {
+ "groups": ["builtin", "external", "internal", "parent", "sibling", "index"],
+ "pathGroups": [
+ {
+ "pattern": "react*",
+ "group": "external",
+ "position": "before"
+ },
+ {
+ "pattern": "@common/**",
+ "group": "internal",
+ "position": "before"
+ },
+ {
+ "pattern": "@**",
+ "group": "internal",
+ "position": "before"
+ }
+ ],
+ "pathGroupsExcludedImportTypes": ["builtin"],
+ "newlines-between": "always",
+ "alphabetize": {
+ "order": "asc",
+ "caseInsensitive": true
+ }
+ }
+ ]
+ }
+ }
+ ],
+ "plugins": ["prettier", "react-native"]
}
diff --git a/.github/ISSUE_TEMPLATE/config.yml b/.github/ISSUE_TEMPLATE/config.yml
index c696c3dc..af7bba82 100644
--- a/.github/ISSUE_TEMPLATE/config.yml
+++ b/.github/ISSUE_TEMPLATE/config.yml
@@ -4,5 +4,5 @@ contact_links:
url: https://github.com/KhalisFoundation/sundar-gutka-react/blob/master/README.md
about: Please check our documentation before creating an issue.
- name: ๐ Known Issues
- url: https://github.com/WahegurooNetwork/SundarGutka/issues?q=is%3Aissue+is%3Aopen+label%3Abug
+ url: https://github.com/KhalisFoundation/sundar-gutka-react/issues?q=is%3Aissue+is%3Aopen+label%3Abug
about: Check if your issue has already been reported.
diff --git a/.github/workflows/pr-tests.yml b/.github/workflows/pr-tests.yml
new file mode 100644
index 00000000..d30459a0
--- /dev/null
+++ b/.github/workflows/pr-tests.yml
@@ -0,0 +1,36 @@
+name: PR CI
+
+on:
+ pull_request:
+ types: [opened, synchronize, reopened, ready_for_review]
+ branches: [master, dev]
+
+concurrency:
+ group: pr-ci-${{ github.ref }}
+ cancel-in-progress: true
+
+jobs:
+ lint-and-test:
+ if: github.event.pull_request.draft == false
+ runs-on: ubuntu-latest
+
+ steps:
+ - name: Check out code
+ uses: actions/checkout@v4
+
+ - name: Set up Node.js
+ uses: actions/setup-node@v4
+ with:
+ node-version: "20"
+ cache: "yarn"
+
+ - name: Install dependencies
+ run: yarn install --frozen-lockfile
+
+ # Lint step
+ - name: Run ESLint
+ run: yarn lint
+
+ # Unit tests (Jest example)
+ - name: Run tests
+ run: yarn test --ci --watchAll=false --reporters=default
diff --git a/.gitignore b/.gitignore
index 077202fa..d3057780 100644
--- a/.gitignore
+++ b/.gitignore
@@ -34,6 +34,7 @@ local.properties
*.keystore
!debug.keystore
.kotlin/
+android/node
# node.js
#
diff --git a/.vscode/settings.json b/.vscode/settings.json
index 5fd5f475..82e13719 100644
--- a/.vscode/settings.json
+++ b/.vscode/settings.json
@@ -6,5 +6,25 @@
"prettier.semi": true,
"javascript.format.semicolons": "insert",
"prettier.printWidth": 100,
- "cSpell.words": ["Pressable"]
+ "cSpell.words": ["Larivaar", "padched", "Pressable", "shabad", "vishraam"],
+ "react-native-ide.jsxRuntime": "automatic",
+ "react-native-ide.reactVersion": "19.0.0",
+ "react-native-ide.reactNativeVersion": "0.78.0",
+ "typescript.preferences.includePackageJsonAutoImports": "on",
+ "javascript.preferences.includePackageJsonAutoImports": "on",
+ "react-native-ide.enableTypeScript": true,
+ "react-native-ide.enableJavaScript": true,
+ "react-native-ide.enableJSX": true,
+ "react-native-ide.moduleResolution": "node",
+ "react-native-ide.allowSyntheticDefaultImports": true,
+ "react-native-ide.esModuleInterop": true,
+ "react-native-ide.enableRenderer": false,
+ "react-native-ide.enableFabric": false,
+ "react-native-ide.enableNewArchitecture": false,
+ "react-native-ide.compatibilityMode": true,
+ "react-native-ide.moduleMapping": {
+ "__RNIDE_lib__/JSXRuntime/react-native-78-79/react-jsx-dev-runtime.development.js": "react/jsx-dev-runtime",
+ "__RNIDE_lib__/rn-renderer/react-native-78-79/ReactFabric-dev.js": "react-native/Libraries/Renderer/implementations/ReactFabric-dev.js"
+ },
+ "java.configuration.updateBuildConfiguration": "interactive"
}
diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md
new file mode 100644
index 00000000..fabef89e
--- /dev/null
+++ b/CONTRIBUTING.md
@@ -0,0 +1,106 @@
+# Contributing to Sundar Gutka
+
+First of all, thank you for taking the time to contribute!
+
+The following is a set of guidelines for contributing to Sundar Gutka, which is hosted in the Khalis Foundation organization on GitHub. These are mostly guidelines, not rules. Use your best judgment, and feel free to propose changes to this document in a pull request.
+
+## Recommendation
+
+Always write code using functional components (hooks based) following latest React and React-native standards.
+
+## Styleguides
+
+### Git Workflow
+
+We are currently following conventional commit style:
+
+1. **Branch Naming**: Use descriptive branch names (`feature/`, `fix/`, `refactor/`)
+2. **Small PRs**: Keep pull requests focused and small
+3. **Commit Messages**: Follow conventional commit format
+4. **Review**: All PRs require review before merging
+
+### JavaScript Styleguide
+
+All JavaScript must adhere to our ESLint and Prettier rules. We recommend using VSCode with Prettier plugin installed to avoid linting errors. We anyway lint the code before pushing to repo.
+
+## Testing
+
+### Writing Tests
+
+**All new features and bug fixes must include tests.** Tests are required for:
+
+---
+
+- New components
+- New hooks
+- New utility functions
+- Bug fixes (regression tests)
+
+### Test Structure
+
+- Place test files next to the component/function they test: `ComponentName.test.jsx` or `hookName.test.js`
+- Use Jest and React Testing Library for component testing
+- Use the test utilities from `@common/test-utils` for mocking
+
+### Running Tests
+
+```bash
+# Run all tests
+yarn test
+```
+
+### Test Utilities
+
+See [`src/common/test-utils/README.md`](src/common/test-utils/README.md) for available mocks and utilities.
+
+## React Native Best Practices
+
+### Component Structure
+
+1. **Use Functional Components**: Always use functional components with hooks
+2. **Memoization**: Use `React.memo()` for components that receive stable props
+3. **Custom Hooks**: Extract reusable logic into custom hooks
+4. **Component Organization**: Keep components small and focused on a single responsibility
+
+### State Management
+
+1. **Redux**: Use Redux Toolkit for global state management
+2. **Local State**: Use `useState` for component-specific state
+3. **Context**: Use React Context sparingly, prefer Redux for shared state
+
+### Navigation
+
+1. **Type Safety**: Use TypeScript types for navigation params when possible
+2. **Deep Linking**: Consider deep linking when adding new screens
+3. **Back Handler**: Use `useBackHandler` hook for Android back button handling
+
+### Styling
+
+1. **Themed Styles**: Always use `useThemedStyles` hook for styling
+2. **StyleSheet**: Use `StyleSheet.create()` for performance
+3. **Responsive Design**: Consider different screen sizes and orientations
+
+### Error Handling
+
+1. **Error Boundaries**: Wrap components in error boundaries where appropriate
+2. **Try-Catch**: Use try-catch for async operations
+3. **User Feedback**: Show user-friendly error messages
+
+### Accessibility
+
+1. **Accessibility Labels**: Add `accessibilityLabel` props to interactive elements
+2. **Test IDs**: Use `testID` for testing purposes
+3. **Screen Reader**: Test with screen readers (VoiceOver/TalkBack)
+
+### Code Organization
+
+1. **File Naming**: Use PascalCase for components, camelCase for utilities
+2. **Import Order**: Follow ESLint import order rules
+3. **Barrel Exports**: Use index.js files for clean imports
+4. **Path Aliases**: Use `@common`, `@database`, `@service`, etc. for imports
+
+### Dependencies
+
+1. **Keep Updated**: Regularly update dependencies
+2. **Native Modules**: Test native module changes on both platforms
+3. **Lock File**: Commit `yarn.lock` to version control
diff --git a/README.md b/README.md
index 190d13d7..d8205307 100644
--- a/README.md
+++ b/README.md
@@ -1,25 +1,242 @@
-# sundar-gutka-react
+
+
+
-### Getting Started
+# Sundar Gutka
-- `git clone https://github.com/KhalisFoundation/sundar-gutka-react.git`.
-- `cd sundar-gutka-react`.
-- `npm install`.
+
+[](https://khalis.slack.com)
+
+
-### Android
+Sundar Gutka is a feature-rich mobile application that provides access to Gurbani with extensive customization options for reading preferences, audio playback, translations, and more. The app supports both iOS and Android platforms and offers a seamless experience for daily Paath.
-To setup android development environment: https://reactnative.dev/docs/environment-setup
+## โจ Features
-Post environment setup
+#### Reading Features
-- Start the application - `npx react-native run-android`.
-- Start metro bundler `npx react-native start`.
+- **Multiple Font Options**: Choose from various Gurbani fonts including GurbaniAkharTrue, GurbaniAkharThickTrue, BalooPaaji, AnmolLipi, and more
+- **Adjustable Font Size**: Five size options from Extra Small to Extra Large
+- **Larivaar Mode**: Read Gurbani in continuous text format with optional assist mode
+- **Paragraph Mode**: Toggle between traditional and paragraph formatting
+- **Vishraam Options**: Color-coded or gradient punctuation marks for better reading flow
+- **Auto Scroll**: Automatic scrolling synchronized with audio playback
+- **Bookmarks**: Save and quickly navigate to your favorite Shabads
+- **Position Saving**: Automatically saves your reading position for each Bani
-### IOS
+#### Translation & Transliteration
-To setup ios development environment: https://reactnative.dev/docs/environment-setup
+- **Multiple Languages**: Support for English, Hindi, Punjabi, Spanish, French, Italian, and more
+- **Transliteration**: Romanized text options (English, Hindi, Shahmukhi, IPA)
+- **Translations**: English, Punjabi, and Spanish translations available
+- **Multi-language UI**: Interface available in multiple languages
-Post environment setup
+#### Audio Features
-- Start the application - `npx react-native run-ios`.
-- Start metro bundler `npx react-native start`.
+- **Audio Player**: Built-in audio playback with React Native Track Player
+- **Audio Sync**: Synchronized scrolling with audio playback
+- **Background Playback**: Continue listening when app is in background
+- **Auto Play**: Automatic audio playback option
+- **Default Audio Selection**: Choose preferred audio source
+
+#### Customization Options
+
+- **Theme Support**: Light and Dark themes
+- **Bani Order**: Customize the order of Banis in your Gutka
+- **Bani Length**: Select from different lengths (SGPC, Taksal, Medium, Long, Extra Long) for major Banis
+- **Keep Screen Awake**: Prevent screen from sleeping during reading
+- **Status Bar Control**: Show or hide status bar
+
+#### Additional Features
+
+- **Folders**: Organize Banis into folders
+- **Reminders**: Set up notification reminders for daily Paath
+- **Database Updates**: In-app database update functionality
+- **Statistics**: Optional usage statistics collection
+- **Donation Support**: Support the Khalis Foundation
+
+## ๐ Getting Started
+
+### Prerequisites
+
+- **Node.js**: >= 18
+- **Package Manager**: Yarn (recommended)
+- **React Native CLI**: Follow the [React Native environment setup guide](https://reactnative.dev/docs/environment-setup)
+
+### Installation
+
+1. Clone the repository:
+
+ ```bash
+ git clone https://github.com/KhalisFoundation/sundar-gutka-react.git
+ cd sundar-gutka-react
+ ```
+
+2. Install dependencies:
+
+ ```bash
+ yarn install
+ ```
+
+## ๐ฑ Platform Setup
+
+### Android Development
+
+1. **Environment Setup**: Follow the [React Native Android setup guide](https://reactnative.dev/docs/environment-setup)
+
+2. **Run the application**:
+
+ ```bash
+ yarn android
+ ```
+
+3. **Start Metro Bundler** (if not started automatically):
+
+ ```bash
+ yarn start
+ ```
+
+### iOS Development
+
+1. **Environment Setup**: Follow the [React Native iOS setup guide](https://reactnative.dev/docs/environment-setup)
+
+2. **Install CocoaPods dependencies**:
+
+ ```bash
+ cd ios
+ pod install
+ cd ..
+ ```
+
+3. **Run the application**:
+
+ ```bash
+ yarn ios
+ ```
+
+4. **Start Metro Bundler** (if not started automatically):
+
+ ```bash
+ yarn start
+ ```
+
+## ๐๏ธ Project Structure
+
+For detailed project structure information, see [PROJECT_STRUCTURE.md](docs/PROJECT_STRUCTURE.md).
+
+## ๐ ๏ธ Key Technologies
+
+- **[React Native](https://github.com/facebook/react-native)**: ^0.78.0
+- **[React](https://github.com/facebook/react)**: 19.0.0
+- **[Redux Toolkit](https://github.com/reduxjs/redux-toolkit)**: State management
+- **[React Navigation](https://github.com/react-navigation/react-navigation)**: Navigation library
+- **[React Native Track Player](https://github.com/doublesymmetry/react-native-track-player)**: Audio playback
+- **[React Native SQLite Storage](https://github.com/andpor/react-native-sqlite-storage)**: Local database
+- **[Firebase](https://github.com/firebase/firebase-js-sdk)**: Analytics, Crashlytics, Messaging, Performance
+- **[Anvaad JS](https://github.com/KhalisFoundation/anvaad-js)**: Gurbani transliteration library
+- **[React Native WebView](https://github.com/react-native-webview/react-native-webview)**: HTML rendering for Gurbani text
+
+## ๐ Available Scripts
+
+- `start`: Start Metro bundler with ESLint
+- `android`: Run Android app with ESLint
+- `ios`: Run iOS app with ESLint
+- `lint`: Run ESLint
+- `test`: Run tests
+
+## โ๏ธ Configuration
+
+### Firebase Setup
+
+The app uses Firebase for:
+
+- Analytics
+- Crashlytics
+- Push Notifications (Messaging)
+- Performance Monitoring
+
+Ensure `google-services.json` (Android) and `GoogleService-Info.plist` (iOS) are properly configured.
+
+### Database
+
+The app uses SQLite for local storage. Database files are located in:
+
+- iOS: `ios/www/gutka_v01.db`
+- Android: Bundled with the app
+
+## ๐จ Customization
+
+### Themes
+
+The app supports light and dark themes. Theme configuration is located in `src/theme/`.
+
+### Fonts
+
+Custom fonts are located in `assets/fonts/`. Supported fonts include:
+
+- GurbaniAkharTrue
+- GurbaniAkharThickTrue
+- GurbaniAkharHeavyTrue
+- BalooPaaji2-Regular
+- BalooPaaji2-SemiBold
+- AnmolLipiSG
+
+### Localization
+
+Localization strings are managed in `src/common/localization.js`. The app supports multiple languages for the UI.
+
+## ๐งช Testing
+
+Run tests with:
+
+```bash
+yarn test
+```
+
+## ๐ค Contributing
+
+Contributions are welcome! Please feel free to submit a Pull Request.
+
+For detailed contribution guidelines, please see [CONTRIBUTING.md](CONTRIBUTING.md).
+
+**Before raising a pull request, please go through CONTRIBUTING.md.** We use `dev` branch as the development branch, while `master` is the production branch. You should branch out from `dev` branch and raise a PR against `dev` branch.
+
+1. Fork the repository
+2. Create your feature branch from `dev` (`git checkout -b feature/AmazingFeature dev`)
+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 against the `dev` branch
+
+## ๐ License
+
+This project is maintained by the [Khalis Foundation](https://khalisfoundation.org).
+
+## ๐ Acknowledgments
+
+- **BaniDB**: Sundar Gutka utilizes the open source Gurbani database and API used in many Gurbani applications, such as SikhiToTheMax
+- **Khalis Foundation**: For maintaining and supporting this project
+
+## ๐ Support
+
+For information, suggestions, or help, visit:
+
+- [Khalis Foundation](https://khalisfoundation.org)
+- [BaniDB](https://www.banidb.com/)
+- [Slack Channel](https://khalis.slack.com) - Join our community for discussions and support
+
+## โ ๏ธ Important Notes
+
+- Please respectfully cover your head and remove your shoes when using this app
+- The app respects different sampardhas (traditions) and provides options for various Bani lengths while maintaining SGPC/Akaal Takht standards
+- Bhul Chuk Maaf! (Please forgive any mistakes)
+
+---
+
+
diff --git a/__mocks__/@react-native/js-polyfills/error-guard.js b/__mocks__/@react-native/js-polyfills/error-guard.js
new file mode 100644
index 00000000..5fce8839
--- /dev/null
+++ b/__mocks__/@react-native/js-polyfills/error-guard.js
@@ -0,0 +1,7 @@
+// Mock for @react-native/js-polyfills/error-guard
+// This avoids parsing Flow type syntax in Jest
+
+module.exports = {
+ setGlobalErrorHandler: jest.fn(),
+ getGlobalErrorHandler: jest.fn(() => null),
+};
diff --git a/__tests__/App-test.js b/__tests__/App-test.js
deleted file mode 100644
index 48c93a85..00000000
--- a/__tests__/App-test.js
+++ /dev/null
@@ -1,3 +0,0 @@
-/**
- * @format
- */
diff --git a/android/app/build.gradle b/android/app/build.gradle
index 08a9a397..15af404e 100644
--- a/android/app/build.gradle
+++ b/android/app/build.gradle
@@ -86,8 +86,8 @@ android {
applicationId = "com.WahegurooNetwork.SundarGutka"
minSdkVersion = rootProject.ext.minSdkVersion
targetSdkVersion = rootProject.ext.targetSdkVersion
- versionCode = 162
- versionName = "5.8.2"
+ versionCode = 170
+ versionName = "5.9"
ndk {
abiFilters "arm64-v8a", "armeabi-v7a", "x86", "x86_64"
}
diff --git a/android/app/release/app-release.apk b/android/app/release/app-release.apk
deleted file mode 100644
index 9d8ae0f2..00000000
Binary files a/android/app/release/app-release.apk and /dev/null differ
diff --git a/android/app/src/debug/AndroidManifest.xml b/android/app/src/debug/AndroidManifest.xml
deleted file mode 100644
index 7a44a92a..00000000
--- a/android/app/src/debug/AndroidManifest.xml
+++ /dev/null
@@ -1,9 +0,0 @@
-
-
-
-
-
diff --git a/android/app/src/main/AndroidManifest.xml b/android/app/src/main/AndroidManifest.xml
index 55365840..7c777edd 100644
--- a/android/app/src/main/AndroidManifest.xml
+++ b/android/app/src/main/AndroidManifest.xml
@@ -2,9 +2,17 @@
xmlns:tools="http://schemas.android.com/tools">
+
+
+
+ android:roundIcon="@mipmap/ic_launcher_round" android:allowBackup="false" android:theme="@style/AppTheme" android:supportsRtl="true"
+ android:networkSecurityConfig="@xml/network_security_config">
+
@@ -16,5 +24,10 @@
+
+
diff --git a/android/app/src/main/assets/fonts/BalooPaaji2-Regular.ttf b/android/app/src/main/assets/fonts/BalooPaaji2-Regular.ttf
new file mode 100644
index 00000000..fef04c40
Binary files /dev/null and b/android/app/src/main/assets/fonts/BalooPaaji2-Regular.ttf differ
diff --git a/android/app/src/main/assets/fonts/BalooPaaji2-SemiBold.ttf b/android/app/src/main/assets/fonts/BalooPaaji2-SemiBold.ttf
new file mode 100644
index 00000000..d2404af3
Binary files /dev/null and b/android/app/src/main/assets/fonts/BalooPaaji2-SemiBold.ttf differ
diff --git a/android/app/src/main/res/drawable/scrollbar_vertical_thumb.xml b/android/app/src/main/res/drawable/scrollbar_vertical_thumb.xml
new file mode 100644
index 00000000..6b2d83c9
--- /dev/null
+++ b/android/app/src/main/res/drawable/scrollbar_vertical_thumb.xml
@@ -0,0 +1,6 @@
+
+
+
+
+
+
\ No newline at end of file
diff --git a/android/app/src/main/res/values/styles.xml b/android/app/src/main/res/values/styles.xml
index 00d2e9c6..66a81c7c 100644
--- a/android/app/src/main/res/values/styles.xml
+++ b/android/app/src/main/res/values/styles.xml
@@ -6,6 +6,7 @@
- @color/primary_dark
- @color/primary_variant
- @color/primary_accent
+ - @drawable/scrollbar_vertical_thumb
-
+
${content}
diff --git a/src/ReaderScreen/utils/index.js b/src/ReaderScreen/utils/index.js
index 1b3fa4fb..ed580906 100644
--- a/src/ReaderScreen/utils/index.js
+++ b/src/ReaderScreen/utils/index.js
@@ -1,19 +1,17 @@
-import { Platform } from "react-native";
-import { colors, constant, baseFontSize, logError, logMessage } from "@common";
+import { constant, baseFontSize, logError, logMessage } from "@common";
import htmlTemplate from "./gutkahtml";
import script from "./gutkaScript";
-export const fontColorForReader = (header, nightMode, text) => {
- const { HEADER_COLOR_1_DARK, HEADER_COLOR_1_LIGHT, WHITE_COLOR, NIGHT_BLACK } = colors;
+export const fontColorForReader = (header, theme, text) => {
const { GURMUKHI, TRANSLATION, TRANSLITERATION } = constant;
- const getHeaderColor1 = () => (nightMode ? HEADER_COLOR_1_DARK : HEADER_COLOR_1_LIGHT);
- const getHeaderColor2 = () => (nightMode ? WHITE_COLOR : NIGHT_BLACK);
+ const getHeaderColor1 = () => theme.colors.primaryHeaderVariant;
+ const getHeaderColor2 = () => theme.colors.primaryText;
const defaultColor = getHeaderColor2();
const gurmukhiMapping = {
1: getHeaderColor1(),
- 2: defaultColor,
+ 2: getHeaderColor1(),
6: defaultColor,
default: defaultColor,
};
@@ -53,9 +51,10 @@ export const createDiv = (
type,
textAlign,
fontSize,
- isNightMode,
+ theme,
isLarivaar,
- punjabiTranslation = ""
+ punjabiTranslation = "",
+ fontFace = null
) => {
const fontClass =
type === constant.GURMUKHI.toLowerCase() || punjabiTranslation !== ""
@@ -66,7 +65,7 @@ export const createDiv = (
fontSize,
header,
type === constant.TRANSLITERATION.toLowerCase() || type === constant.TRANSLATION.toLowerCase()
- )}px; color: ${fontColorForReader(header, isNightMode, type.toUpperCase())};">
+ )}px; font-family: ${fontFace}; color: ${fontColorForReader(header, theme, type.toUpperCase())};">
${content}
`;
@@ -80,38 +79,40 @@ export const loadHTML = (
isEnglishTranslation,
isPunjabiTranslation,
isSpanishTranslation,
- isNightMode,
- isLarivaar,
- savePosition
+ theme,
+ isLarivaar
) => {
try {
- const backColor = isNightMode ? colors.NIGHT_BLACK : colors.WHITE_COLOR;
- const fileUri = Platform.select({
- ios: `${fontFace}.ttf`,
- android: `file:///android_asset/fonts/${fontFace}.ttf`,
- });
-
+ const backColor = theme.colors.surface;
const content = shabad
.map((item) => {
const textAlignMap = {
0: "left",
- 1: "center",
- 2: "center",
+ 1: "left",
+ 2: "left",
};
let textAlign = textAlignMap[item.header];
if (textAlign === undefined) {
textAlign = "right";
}
- let contentHtml = ``;
+ // Use pipe delimiters for easy CSS selector matching
+ const paragraphId = item.sequences ? item.sequences[0] : item.sequence;
+ const sequencesData = item.sequences
+ ? ` data-sequences='|${item.sequences.join("|")}|'`
+ : "";
+ const sequenceData = ` data-sequence='${paragraphId}'`;
+ let contentHtml = `
`;
contentHtml += createDiv(
- item.gurmukhi,
+ fontFace === constant.BALOO_PAAJI ? item.gurmukhiUni : item.gurmukhi,
item.header,
constant.GURMUKHI.toLowerCase(),
textAlign,
fontSize,
- isNightMode,
- isLarivaar
+ theme,
+ isLarivaar,
+ "",
+ fontFace
);
if (isTransliteration) {
@@ -121,7 +122,7 @@ export const loadHTML = (
constant.TRANSLITERATION.toLowerCase(),
textAlign,
fontSize,
- isNightMode,
+ theme,
isLarivaar
);
}
@@ -133,7 +134,7 @@ export const loadHTML = (
constant.TRANSLATION.toLowerCase(),
textAlign,
fontSize,
- isNightMode,
+ theme,
isLarivaar
);
}
@@ -145,9 +146,10 @@ export const loadHTML = (
constant.TRANSLATION.toLowerCase(),
textAlign,
fontSize,
- isNightMode,
+ theme,
isLarivaar,
- constant.GURMUKHI.toLowerCase()
+ constant.GURMUKHI.toLowerCase(),
+ constant.GURBANI_AKHAR_TRUE
);
}
@@ -158,7 +160,7 @@ export const loadHTML = (
constant.TRANSLATION.toLowerCase(),
textAlign,
fontSize,
- isNightMode,
+ theme,
isLarivaar
);
}
@@ -167,14 +169,7 @@ export const loadHTML = (
return contentHtml;
})
.join("");
- const htmlContent = htmlTemplate(
- backColor,
- fileUri,
- fontFace,
- content,
- isNightMode,
- savePosition
- );
+ const htmlContent = htmlTemplate(backColor, fontFace, content, theme);
return htmlContent;
} catch (error) {
logError(error);
diff --git a/src/Settings/components/audio.jsx b/src/Settings/components/audio.jsx
new file mode 100644
index 00000000..43a774ec
--- /dev/null
+++ b/src/Settings/components/audio.jsx
@@ -0,0 +1,70 @@
+import React from "react";
+import { useSelector, useDispatch } from "react-redux";
+import { ListItem, Icon, Switch } from "@rneui/themed";
+import { toggleAudio, toggleAudioAutoPlay, toggleAutoScroll } from "@common/actions";
+import useTheme from "@common/context";
+import useThemedStyles from "@common/hooks/useThemedStyles";
+import { STRINGS, ListItemTitle } from "@common";
+import createStyles from "../styles";
+
+const Audio = () => {
+ const { theme } = useTheme();
+ const styles = useThemedStyles(createStyles);
+ const isAudio = useSelector((state) => state.isAudio);
+ const isAudioAutoPlay = useSelector((state) => state.isAudioAutoPlay);
+ const isAutoScroll = useSelector((state) => state.isAutoScroll);
+ const dispatch = useDispatch();
+ const { AUDIO, AUDIO_AUTO_PLAY } = STRINGS;
+
+ // Audio settings configuration
+ const audioSettings = [
+ {
+ id: "main",
+ title: AUDIO,
+ icon: "music-note",
+ value: isAudio,
+ action: toggleAudio,
+ showAlways: true,
+ },
+ {
+ id: "autoPlay",
+ title: AUDIO_AUTO_PLAY,
+ icon: "play-circle-outline",
+ value: isAudioAutoPlay,
+ action: toggleAudioAutoPlay,
+ showAlways: false,
+ },
+ ];
+
+ const renderAudioSetting = (setting) => {
+ const shouldShow = setting.showAlways || isAudio;
+
+ if (!shouldShow) return null;
+
+ return (
+
+
+
+
+
+ {
+ if (isAutoScroll) {
+ dispatch(toggleAutoScroll(false));
+ }
+ dispatch(setting.action(value));
+ }}
+ />
+
+ );
+ };
+
+ return <>{audioSettings.map(renderAudioSetting)}>;
+};
+
+export default Audio;
diff --git a/src/Settings/components/autoScroll.jsx b/src/Settings/components/autoScroll.jsx
index c6876438..37570a85 100644
--- a/src/Settings/components/autoScroll.jsx
+++ b/src/Settings/components/autoScroll.jsx
@@ -1,26 +1,25 @@
import React from "react";
-import { ListItem, Icon, Switch } from "@rneui/themed";
import { useSelector, useDispatch } from "react-redux";
-import { STRINGS } from "@common";
+import { ListItem, Icon, Switch } from "@rneui/themed";
import { toggleScreenAwake, toggleAutoScroll } from "@common/actions";
-import { iconNightColor, nightModeStyles, nightModeColor } from "../styles/nightModeStyles";
+import useTheme from "@common/context";
+import { STRINGS, ListItemTitle } from "@common";
const AutoScroll = () => {
- const isNightMode = useSelector((state) => state.isNightMode);
+ const { theme } = useTheme();
const isAutoScroll = useSelector((state) => state.isAutoScroll);
+ const isAudio = useSelector((state) => state.isAudio);
const dispatch = useDispatch();
- const iconColor = iconNightColor(isNightMode);
- const { containerNightStyles } = nightModeStyles(isNightMode);
- const nightColor = nightModeColor(isNightMode);
const { AUTO_SCROLL } = STRINGS;
return (
-
-
+
+
- {AUTO_SCROLL}
+
{
/* The screen should remain active whenever Auto Scroll is enabled. */
dispatch(toggleScreenAwake(value));
diff --git a/src/Settings/components/collectStatistics.jsx b/src/Settings/components/collectStatistics.jsx
index 4e22b6b2..a68e4ad2 100644
--- a/src/Settings/components/collectStatistics.jsx
+++ b/src/Settings/components/collectStatistics.jsx
@@ -1,23 +1,20 @@
import React from "react";
-import { ListItem, Avatar, Switch } from "@rneui/themed";
import { useDispatch, useSelector } from "react-redux";
-import { STRINGS, actions } from "@common";
-import { nightModeStyles, nightModeColor } from "../styles/nightModeStyles";
-import { styles } from "../styles";
+import { ListItem, Avatar, Switch } from "@rneui/themed";
+import { STRINGS, actions, useThemedStyles, ListItemTitle } from "@common";
+import createStyles from "../styles";
const CollectStatistics = () => {
- const isNightMode = useSelector((state) => state.isNightMode);
const isStatistics = useSelector((state) => state.isStatistics);
- const { containerNightStyles } = nightModeStyles(isNightMode);
- const nightColor = nightModeColor(isNightMode);
+ const styles = useThemedStyles(createStyles);
const { COLLECT_STATISTICS } = STRINGS;
const analyticsIcon = require("../../../images/analyticsicon.png");
const dispatch = useDispatch();
return (
-
+
- {COLLECT_STATISTICS}
+
{
- const isNightMode = useSelector((state) => state.isNightMode);
- const iconColor = useMemo(() => iconNightColor(isNightMode), [isNightMode]);
- const { containerNightStyles } = useMemo(() => nightModeStyles(isNightMode), [isNightMode]);
- const nightColor = useMemo(() => nightModeColor(isNightMode), [isNightMode]);
-
+ const { theme } = useTheme();
+ const styles = useThemedStyles(createStyles);
return (
navigate(navigationTarget)}
>
-
+
- {title}
+
diff --git a/src/Settings/components/comon/bottomSheetComponent.jsx b/src/Settings/components/comon/bottomSheetComponent.jsx
index b6087914..f0445fe9 100644
--- a/src/Settings/components/comon/bottomSheetComponent.jsx
+++ b/src/Settings/components/comon/bottomSheetComponent.jsx
@@ -1,13 +1,15 @@
import React, { useEffect, useState } from "react";
-import { View, Modal, Text, Dimensions, Pressable, Platform, StyleSheet } from "react-native";
-import { Divider, Icon, ListItem } from "@rneui/themed";
-import { BlurView } from "@react-native-community/blur";
-import { useDispatch, useSelector } from "react-redux";
-import { constant, colors } from "@common";
-import PropTypes from "prop-types";
+import { View, Modal, Dimensions, Pressable, Platform, StyleSheet } from "react-native";
import { SafeAreaProvider, SafeAreaView } from "react-native-safe-area-context";
import SoundPlayer from "react-native-sound-player";
-import { styles, nightModeStyles, nightModeColor } from "../../styles";
+import { useDispatch } from "react-redux";
+import { BlurView } from "@react-native-community/blur";
+import { Divider, Icon, ListItem } from "@rneui/themed";
+import PropTypes from "prop-types";
+import useTheme from "@common/context";
+import useThemedStyles from "@common/hooks/useThemedStyles";
+import { constant, CustomText, ListItemTitle } from "@common";
+import createStyles from "../../styles";
const BottomSheetComponent = ({
isVisible,
@@ -17,10 +19,9 @@ const BottomSheetComponent = ({
action,
toggleVisible,
}) => {
+ const { theme } = useTheme();
+ const styles = useThemedStyles(createStyles);
const dispatch = useDispatch();
- const isNightMode = useSelector((state) => state.isNightMode);
- const { containerNightStyles, textNightStyle } = nightModeStyles(isNightMode);
- const nightStyles = nightModeColor(isNightMode);
const { width, height } = Dimensions.get("window");
const [orientation, setOrientation] = useState(width < height ? "PORTRAIT" : "LANDSCAPE");
@@ -58,21 +59,23 @@ const BottomSheetComponent = ({
>
toggleVisible(false)}>
-
+
{title}
-
+
{actionConstant.map((item) => (
{
toggleVisible(false);
dispatch(action(item.key));
@@ -83,13 +86,13 @@ const BottomSheetComponent = ({
}}
>
- {item.title}
+
- {value === item.key && }
+ {value === item.key && }
))}
{Platform.OS === "ios" && (
-
+
)}
diff --git a/src/Settings/components/comon/index.jsx b/src/Settings/components/comon/index.jsx
index 5ec0ac43..d2bb7794 100644
--- a/src/Settings/components/comon/index.jsx
+++ b/src/Settings/components/comon/index.jsx
@@ -1,4 +1,5 @@
-import ListItemComponent from "./listItemComponent";
import BottomSheetComponent from "./bottomSheetComponent";
+import ListItemComponent from "./listItemComponent";
+import ListItemWithIcon from "./ListitemWithIcon";
-export { ListItemComponent, BottomSheetComponent };
+export { ListItemComponent, BottomSheetComponent, ListItemWithIcon };
diff --git a/src/Settings/components/comon/listItemComponent.jsx b/src/Settings/components/comon/listItemComponent.jsx
index 0cae7241..122e5252 100644
--- a/src/Settings/components/comon/listItemComponent.jsx
+++ b/src/Settings/components/comon/listItemComponent.jsx
@@ -1,27 +1,26 @@
-import React, { useMemo } from "react";
+import React from "react";
import { ListItem, Avatar, Icon } from "@rneui/themed";
import PropTypes from "prop-types";
-import { useSelector } from "react-redux";
-import { styles, nightModeStyles, iconNightColor } from "../../styles";
+import useTheme from "@common/context";
+import useThemedStyles from "@common/hooks/useThemedStyles";
+import { ListItemTitle } from "@common";
+import createStyles from "../../styles";
const ListItemComponent = ({ icon, title, value, isAvatar, actionConstant, onPressAction }) => {
- const isNightMode = useSelector((state) => state.isNightMode);
- const { containerNightStyles, textNightStyle, textNightGrey } = useMemo(
- () => nightModeStyles(isNightMode),
- [isNightMode]
- );
- const iconColor = useMemo(() => iconNightColor(isNightMode), [isNightMode]);
+ const { theme } = useTheme();
+ const styles = useThemedStyles(createStyles);
return (
-
+
{isAvatar && }
- {!isAvatar && }
+ {!isAvatar && }
- {title}
+
{value && (
-
- {actionConstant.filter((item) => item.key === value).map((item) => item.title)[0]}
-
+ item.key === value).map((item) => item.title)[0]}
+ style={[styles.titleInfoStyle]}
+ />
)}
diff --git a/src/Settings/components/comon/strings.js b/src/Settings/components/comon/strings.js
index 84bb9753..8a46441d 100644
--- a/src/Settings/components/comon/strings.js
+++ b/src/Settings/components/comon/strings.js
@@ -9,10 +9,11 @@ export const getFontSizes = (strings) => [
];
export const getFontFaces = (strings) => [
- { key: "AnmolLipiSG", title: strings.anmol_lipi },
- { key: "GurbaniAkharTrue", title: strings.gurbani_akhar_default },
- { key: "GurbaniAkharHeavyTrue", title: strings.gurbani_akhar_heavy },
- { key: "GurbaniAkharThickTrue", title: strings.gurbani_akhar_think },
+ { key: constant.ANMOL_LIPI, title: strings.anmol_lipi },
+ { key: constant.GURBANI_AKHAR_TRUE, title: strings.gurbani_akhar_default },
+ { key: constant.GURBANI_AKHAR_HEAVY_TRUE, title: strings.gurbani_akhar_heavy },
+ { key: constant.GURBANI_AKHAR_THICK_TRUE, title: strings.gurbani_akhar_think },
+ { key: constant.BALOO_PAAJI, title: strings.baloo_paaji },
];
export const getBaniLengths = (strings) => [
diff --git a/src/Settings/components/databaseUpdate.jsx b/src/Settings/components/databaseUpdate.jsx
index 37203d7d..04fcec5c 100644
--- a/src/Settings/components/databaseUpdate.jsx
+++ b/src/Settings/components/databaseUpdate.jsx
@@ -1,17 +1,18 @@
-import PropTypes from "prop-types";
import React from "react";
-import { Image, Pressable, Text, View } from "react-native";
-import { STRINGS } from "@common";
-import { styles } from "../styles";
+import { Image, Pressable, View } from "react-native";
+import PropTypes from "prop-types";
+import { STRINGS, CustomText, useThemedStyles } from "@common";
+import createStyles from "../styles";
const baniDbLogo = require("../../../images/banidblogo.png");
const DatabaseUpdateBanner = ({ navigate }) => {
+ const styles = useThemedStyles(createStyles);
return (
navigate("DatabaseUpdate")}>
- {STRINGS.baniDBBannerText}
+ {STRINGS.baniDBBannerText}
);
diff --git a/src/Settings/components/donate.jsx b/src/Settings/components/donate.jsx
index 707bb9c6..613512c5 100644
--- a/src/Settings/components/donate.jsx
+++ b/src/Settings/components/donate.jsx
@@ -1,25 +1,22 @@
import React from "react";
import { Linking } from "react-native";
import { ListItem, Icon } from "@rneui/themed";
-import { useSelector } from "react-redux";
-import { STRINGS } from "@common";
-import { iconNightColor, nightModeStyles, nightModeColor } from "../styles/nightModeStyles";
+import { STRINGS, useTheme, useThemedStyles, ListItemTitle } from "@common";
+import createStyles from "../styles";
const Donate = () => {
- const isNightMode = useSelector((state) => state.isNightMode);
- const iconColor = iconNightColor(isNightMode);
- const { containerNightStyles } = nightModeStyles(isNightMode);
- const nightColor = nightModeColor(isNightMode);
+ const { theme } = useTheme();
+ const styles = useThemedStyles(createStyles);
const { donate } = STRINGS;
return (
Linking.openURL("https://khalisfoundation.org/donate/")}
>
-
+
- {donate}
+
diff --git a/src/Settings/components/editBaniOrder.jsx b/src/Settings/components/editBaniOrder.jsx
index 76f87190..15600801 100644
--- a/src/Settings/components/editBaniOrder.jsx
+++ b/src/Settings/components/editBaniOrder.jsx
@@ -1,24 +1,22 @@
import React from "react";
import { ListItem, Avatar } from "@rneui/themed";
import PropTypes from "prop-types";
-import { STRINGS } from "@common";
-import { nightModeStyles, nightModeColor } from "../styles/nightModeStyles";
-import { styles } from "../styles";
+import { STRINGS, useThemedStyles, ListItemTitle } from "@common";
+import createStyles from "../styles";
-const EditBaniOrder = ({ navigate, isNightMode }) => {
- const { containerNightStyles } = nightModeStyles(isNightMode);
- const nightColor = nightModeColor(isNightMode);
+const EditBaniOrder = ({ navigate }) => {
+ const styles = useThemedStyles(createStyles);
const { EDIT_BANI_ORDER } = STRINGS;
const rearrangeIcon = require("../../../images/rearrangeicon.png");
return (
navigate("EditBaniOrder")}
>
- {EDIT_BANI_ORDER}
+
@@ -26,6 +24,5 @@ const EditBaniOrder = ({ navigate, isNightMode }) => {
};
EditBaniOrder.propTypes = {
navigate: PropTypes.func.isRequired,
- isNightMode: PropTypes.bool.isRequired,
};
export default EditBaniOrder;
diff --git a/src/Settings/components/keepAwake.jsx b/src/Settings/components/keepAwake.jsx
index 8d05c6eb..44078d8e 100644
--- a/src/Settings/components/keepAwake.jsx
+++ b/src/Settings/components/keepAwake.jsx
@@ -1,26 +1,23 @@
import React from "react";
-import { ListItem, Switch, Avatar } from "@rneui/themed";
import { useSelector, useDispatch } from "react-redux";
-import { STRINGS } from "@common";
+import { ListItem, Switch, Avatar } from "@rneui/themed";
import { toggleScreenAwake } from "@common/actions";
-import { nightModeStyles, nightModeColor } from "../styles/nightModeStyles";
-import { styles } from "../styles";
+import { STRINGS, useThemedStyles, ListItemTitle } from "@common";
+import createStyles from "../styles";
const KeepAwake = () => {
+ const styles = useThemedStyles(createStyles);
const dispatch = useDispatch();
- const isNightMode = useSelector((state) => state.isNightMode);
const isScreenAwake = useSelector((state) => state.isScreenAwake);
const isAutoScroll = useSelector((state) => state.isAutoScroll);
- const { containerNightStyles } = nightModeStyles(isNightMode);
- const nightColor = nightModeColor(isNightMode);
const { KEEP_AWAKE } = STRINGS;
const screenIcon = require("../../../images/screenonicon.png");
return (
-
+
- {KEEP_AWAKE}
+
{
- const isNightMode = useSelector((state) => state.isNightMode);
+ const { theme } = useTheme();
+ const styles = useThemedStyles(createStyles);
const isLarivaar = useSelector((state) => state.isLarivaar);
const isLarivaarAssist = useSelector((state) => state.isLarivaarAssist);
const dispatch = useDispatch();
- const { containerNightStyles, textNightStyle } = nightModeStyles(isNightMode);
- const iconColor = iconNightColor(isNightMode);
const larivaarIcon = require("../../../images/larivaaricon.png");
return (
<>
-
+
- {STRINGS.larivaar}
+
dispatch(toggleLarivaar(value))} />
{isLarivaar && (
-
-
+
+
- {STRINGS.larivaar_assist}
+
{
+ const { theme } = useTheme();
+ const styles = useThemedStyles(createStyles);
const isParagraphMode = useSelector((state) => state.isParagraphMode);
- const isNightMode = useSelector((state) => state.isNightMode);
const dispatch = useDispatch();
- const iconColor = iconNightColor(isNightMode);
- const { containerNightStyles } = nightModeStyles(isNightMode);
- const nightColor = nightModeColor(isNightMode);
const { PARAGRAPH_MODE } = STRINGS;
return (
-
-
+
+
- {PARAGRAPH_MODE}
+
{
- const isNightMode = useSelector((state) => state.isNightMode);
+ const { theme } = useTheme();
+ const styles = useThemedStyles(createStyles);
const [isLabelModal, toggleLabelModal] = useState(false);
const reminderBanis = useSelector((state) => state.reminderBanis);
const dispatch = useDispatch();
const { key, title } = section;
- let backColor;
- if (isActive) {
- backColor = isNightMode ? colors.ACTIVE_VIEW_COLOR_NIGHT_MODE : colors.ACTIVE_VIEW_COLOR;
- } else {
- backColor = isNightMode ? colors.INACTIVE_VIEW_COLOR_NIGHT_MODE : colors.INACTIVE_VIEW_COLOR;
- }
+
+ const backColor = isActive ? theme.colors.activeView : theme.colors.inactiveView;
+
const hideModal = () => {
toggleLabelModal(false);
};
@@ -38,19 +38,8 @@ const AccordianContent = ({ section, isActive }) => {
}}
>
-
-
- {title}
-
+
+ {title}
@@ -60,21 +49,11 @@ const AccordianContent = ({ section, isActive }) => {
}}
>
-
-
- {STRINGS.delete}
-
+
+ {STRINGS.delete}
-
+
{isLabelModal && }
);
diff --git a/src/Settings/components/reminders/ReminderOptions/components/AccordianHeader.jsx b/src/Settings/components/reminders/ReminderOptions/components/AccordianHeader.jsx
index 807ed816..9daeab1e 100644
--- a/src/Settings/components/reminders/ReminderOptions/components/AccordianHeader.jsx
+++ b/src/Settings/components/reminders/ReminderOptions/components/AccordianHeader.jsx
@@ -1,17 +1,20 @@
import React, { useState } from "react";
-import { View, TouchableOpacity, Text } from "react-native";
-import { Switch, Icon, Divider } from "@rneui/themed";
-import PropTypes from "prop-types";
-import { useDispatch, useSelector } from "react-redux";
+import { View, TouchableOpacity } from "react-native";
import DateTimePicker from "react-native-modal-datetime-picker";
+import { useDispatch, useSelector } from "react-redux";
+import { Switch, Icon, Divider } from "@rneui/themed";
import moment from "moment";
-import { colors, constant, updateReminders, trackReminderEvent } from "@common";
+import PropTypes from "prop-types";
import { setReminderBanis } from "@common/actions";
-import { styles } from "../styles";
+import useTheme from "@common/context";
+import useThemedStyles from "@common/hooks/useThemedStyles";
+import { constant, updateReminders, trackReminderEvent, CustomText } from "@common";
+import createStyles from "../styles";
const AccordianHeader = ({ section, isActive }) => {
+ const { theme } = useTheme();
+ const styles = useThemedStyles(createStyles);
const reminderBanis = useSelector((state) => state.reminderBanis);
- const isNightMode = useSelector((state) => state.isNightMode);
const isTransliteration = useSelector((state) => state.isTransliteration);
const isReminders = useSelector((state) => state.isReminders);
const reminderSound = useSelector((state) => state.reminderSound);
@@ -54,39 +57,37 @@ const AccordianHeader = ({ section, isActive }) => {
-
{isTransliteration ? translit : gurmukhi}
-
+
handelSwitchToggled(value, key)} />
toggleTimePicker(true)}>
-
{time}
-
+
-
+
{
+ const { theme } = useTheme();
const dispatch = useDispatch();
const isReminders = useSelector((state) => state.isReminders);
const reminderSound = useSelector((state) => state.reminderSound);
@@ -13,7 +15,7 @@ const useHeader = (baniListData, navigation, selector) => {
name="arrow-back"
size={30}
onPress={() => navigation.goBack()}
- color={colors.WHITE_COLOR}
+ color={theme.staticColors.WHITE_COLOR}
/>
);
const headerRight = () => {
@@ -21,7 +23,7 @@ const useHeader = (baniListData, navigation, selector) => {
<>
{
@@ -30,7 +32,7 @@ const useHeader = (baniListData, navigation, selector) => {
/>
{
selector.current.open();
@@ -43,12 +45,12 @@ const useHeader = (baniListData, navigation, selector) => {
navigation.setOptions({
title: STRINGS.set_reminder_options,
headerTitleStyle: {
- color: colors.WHITE_COLOR,
+ color: theme.staticColors.WHITE_COLOR,
fontWeight: "normal",
fontSize: 18,
},
headerStyle: {
- backgroundColor: colors.TOOLBAR_COLOR_ALT2,
+ backgroundColor: theme.colors.headerVariant,
},
headerLeft,
headerRight: () => (baniListData.length > 0 ? headerRight() : null),
diff --git a/src/Settings/components/reminders/ReminderOptions/index.js b/src/Settings/components/reminders/ReminderOptions/index.js
index 71480cc5..a97d74af 100644
--- a/src/Settings/components/reminders/ReminderOptions/index.js
+++ b/src/Settings/components/reminders/ReminderOptions/index.js
@@ -1,13 +1,14 @@
import React, { useState, useRef, useMemo } from "react";
-import { useDispatch, useSelector } from "react-redux";
import { View, ScrollView } from "react-native";
-import { SafeAreaProvider, SafeAreaView } from "react-native-safe-area-context";
-import Accordion from "react-native-collapsible/Accordion";
-import PropTypes from "prop-types";
import ModalSelector from "react-native-modal-selector";
+import { SafeAreaProvider, SafeAreaView } from "react-native-safe-area-context";
+import { useDispatch, useSelector } from "react-redux";
import moment from "moment";
+import PropTypes from "prop-types";
+import Accordion from "react-native-collapsible/Accordion";
+import useTheme from "@common/context";
+import useThemedStyles from "@common/hooks/useThemedStyles";
import {
- colors,
constant,
STRINGS,
actions,
@@ -16,13 +17,14 @@ import {
logMessage,
StatusBarComponent,
} from "@common";
-import { styles, accordianNightColor, optionContainer } from "./styles";
import { AccordianContent, AccordianHeader } from "./components";
import { useHeader, useFetchBani } from "./hooks";
+import createStyles from "./styles";
const ReminderOptions = ({ navigation }) => {
logMessage(constant.REMINDER_OPTIONS);
- const isNightMode = useSelector((state) => state.isNightMode);
+ const { theme } = useTheme();
+ const styles = useThemedStyles(createStyles);
const reminderBanis = useSelector((state) => state.reminderBanis);
const isReminders = useSelector((state) => state.isReminders);
const reminderSound = useSelector((state) => state.reminderSound);
@@ -37,9 +39,6 @@ const ReminderOptions = ({ navigation }) => {
const dispatch = useDispatch();
const selector = useRef(null);
- const { backgroundColor, color } = optionContainer(isNightMode);
-
- const accNightColor = useMemo(() => accordianNightColor(isNightMode), [isNightMode]);
useFetchBani(setBaniListData, setReminderBaniData, setStateData, parsedReminderBanis);
useHeader(baniListData, navigation, selector);
@@ -73,20 +72,15 @@ const ReminderOptions = ({ navigation }) => {
return (
-
+
-
+
{stateData.length > 0 && (
(
)}
@@ -105,15 +99,19 @@ const ReminderOptions = ({ navigation }) => {
]}
data={reminderBaniData}
cancelText={STRINGS.cancel}
- optionTextStyle={{ ...styles.modalSelectText, color, ...fontFamily }}
+ optionTextStyle={{
+ ...styles.modalSelectText,
+ color: theme.colors.primaryText,
+ ...fontFamily,
+ }}
onChange={(option) => {
createReminder(option);
}}
customSelector={ }
ref={selector}
- cancelTextStyle={{ color }}
- cancelStyle={{ backgroundColor }}
- optionContainerStyle={{ backgroundColor }}
+ cancelTextStyle={{ color: theme.colors.primaryText }}
+ cancelStyle={{ backgroundColor: theme.colors.surfaceGrey }}
+ optionContainerStyle={{ backgroundColor: theme.colors.surfaceGrey }}
/>
diff --git a/src/Settings/components/reminders/ReminderOptions/modals/LabelModal.jsx b/src/Settings/components/reminders/ReminderOptions/modals/LabelModal.jsx
index e56d5cb4..592aff74 100644
--- a/src/Settings/components/reminders/ReminderOptions/modals/LabelModal.jsx
+++ b/src/Settings/components/reminders/ReminderOptions/modals/LabelModal.jsx
@@ -1,19 +1,19 @@
import React, { useState } from "react";
-import { Modal, Text, TextInput, View, TouchableOpacity } from "react-native";
+import { Modal, TextInput, View, TouchableOpacity } from "react-native";
import { useDispatch, useSelector } from "react-redux";
import PropTypes from "prop-types";
import { setReminderBanis } from "@common/actions";
-import { updateReminders, colors, STRINGS } from "@common";
-import { styles } from "../styles";
+import { updateReminders, STRINGS, CustomText, useThemedStyles, useTheme } from "@common";
+import createStyles from "../styles";
const LabelModal = ({ section, onHide }) => {
+ const styles = useThemedStyles(createStyles);
const { title } = section;
const [reminderTitle, setReminderTitle] = useState(title);
- const isNightMode = useSelector((state) => state.isNightMode);
const reminderBanis = useSelector((state) => state.reminderBanis);
const isReminders = useSelector((state) => state.isReminders);
const reminderSound = useSelector((state) => state.reminderSound);
-
+ const { theme } = useTheme();
const dispatch = useDispatch();
const confirmReminderLabel = () => {
@@ -29,15 +29,12 @@ const LabelModal = ({ section, onHide }) => {
- {STRINGS.notification_text}
+ {STRINGS.notification_text}
setReminderTitle(label)}
- selectionColor={colors.MODAL_ACCENT_NIGHT_MODE}
+ selectionColor={theme.colors.underlayColor}
/>
@@ -47,7 +44,7 @@ const LabelModal = ({ section, onHide }) => {
}}
style={{ marginRight: 30 }}
>
- {STRINGS.cancel}
+ {STRINGS.cancel}
{
@@ -55,7 +52,7 @@ const LabelModal = ({ section, onHide }) => {
onHide();
}}
>
- {STRINGS.ok}
+ {STRINGS.ok}
diff --git a/src/Settings/components/reminders/ReminderOptions/styles.js b/src/Settings/components/reminders/ReminderOptions/styles.js
index c8614212..804574fc 100644
--- a/src/Settings/components/reminders/ReminderOptions/styles.js
+++ b/src/Settings/components/reminders/ReminderOptions/styles.js
@@ -1,42 +1,59 @@
-import { StyleSheet } from "react-native";
-import { colors } from "@common";
-
-export const styles = StyleSheet.create({
+const createStyles = (theme) => ({
viewColumn: { flexDirection: "column" },
viewRow: { flexDirection: "row", justifyContent: "space-between" },
- cardTitle: { fontSize: 24 },
- flexView: { flex: 1 },
- timeFont: { fontSize: 44 },
- accContentText: { fontSize: 14 },
- accContentWrapper: { flexDirection: "row", alignItems: "center", margin: 5 },
- modalSelectText: { fontSize: 28 },
+ cardTitle: {
+ fontSize: theme.typography.sizes.xxxl,
+ color: theme.colors.primaryText,
+ fontWeight: theme.typography.weights.medium,
+ },
+ flexView: { flex: 1, backgroundColor: theme.colors.inactiveView },
+ timeFont: {
+ fontSize: theme.typography.sizes.huge + theme.spacing.lg,
+ color: theme.colors.primaryText,
+ fontWeight: theme.typography.weights.light,
+ },
+ accContentText: {
+ fontSize: theme.typography.sizes.md,
+ color: theme.colors.componentColor,
+ },
+ accContentWrapper: {
+ flexDirection: "row",
+ alignItems: "center",
+ margin: theme.spacing.sm,
+ },
+ modalSelectText: {
+ fontSize: theme.typography.sizes.huge,
+ fontWeight: theme.typography.weights.medium,
+ color: theme.colors.primaryText,
+ },
textInput: {
- height: 40,
- borderRadius: 5,
- borderColor: colors.MODAL_ACCENT_NIGHT_MODE_ALT,
+ height: theme.components.input.minHeight - theme.spacing.sm,
+ borderRadius: theme.components.input.borderRadius,
+ borderColor: theme.colors.underlayColor,
borderWidth: 1,
- padding: 5,
+ padding: theme.components.input.paddingHorizontal,
+ color: theme.colors.primaryText,
+ fontSize: theme.typography.sizes.lg,
},
labelModalWrapper: { flex: 1, justifyContent: "center", alignItems: "center" },
- labelViewWrapper: { backgroundColor: colors.WHITE_COLOR, padding: 20, width: 300 },
- labelText: { paddingBottom: 5, color: colors.MODAL_ACCENT_NIGHT_MODE },
+ labelViewWrapper: {
+ backgroundColor: theme.staticColors.WHITE_COLOR,
+ padding: theme.spacing.xl,
+ width: 300,
+ borderRadius: theme.radius.lg,
+ },
+ labelText: {
+ paddingBottom: theme.spacing.sm,
+ color: theme.colors.underlayColor,
+ fontSize: theme.typography.sizes.lg,
+ fontWeight: theme.typography.weights.medium,
+ },
labelButtonWrapper: {
flexDirection: "row",
justifyContent: "flex-end",
alignItems: "center",
- padding: 10,
+ padding: theme.spacing.md,
},
- modalBackColor: { backgroundColor: colors.NIGHT_GREY_COLOR },
+ modalBackColor: { backgroundColor: theme.colors.surfaceGrey },
});
-
-export const accordianNightColor = (isNightMode) => {
- const color = isNightMode ? colors.NIGHT_BLACK : colors.WHITE_COLOR;
- return color;
-};
-
-export const optionContainer = (isNightMode) => {
- return {
- backgroundColor: isNightMode ? colors.NIGHT_GREY_COLOR : colors.WHITE_COLOR,
- color: isNightMode ? colors.WHITE_COLOR : colors.NIGHT_GREY_COLOR,
- };
-};
+export default createStyles;
diff --git a/src/Settings/components/reminders/ReminderOptions/utils/index.js b/src/Settings/components/reminders/ReminderOptions/utils/index.js
index e5a737b9..b527cce5 100644
--- a/src/Settings/components/reminders/ReminderOptions/utils/index.js
+++ b/src/Settings/components/reminders/ReminderOptions/utils/index.js
@@ -1,5 +1,5 @@
-import { updateReminders, constant, trackReminderEvent, STRINGS } from "@common";
import { setReminderBanis } from "@common/actions";
+import { updateReminders, constant, trackReminderEvent, STRINGS } from "@common";
const setDefaultReminders = async (baniListData, dispatch, isReminders, reminderSound) => {
const baniList = baniListData;
diff --git a/src/Settings/components/reminders/reminders.jsx b/src/Settings/components/reminders/reminders.jsx
index ae193334..60f2c9db 100644
--- a/src/Settings/components/reminders/reminders.jsx
+++ b/src/Settings/components/reminders/reminders.jsx
@@ -1,8 +1,10 @@
import React, { useState } from "react";
import { Alert, Linking } from "react-native";
-import { ListItem, Icon, Switch } from "@rneui/themed";
import { useSelector, useDispatch } from "react-redux";
+import { ListItem, Icon, Switch } from "@rneui/themed";
import PropTypes from "prop-types";
+import useTheme from "@common/context";
+import useThemedStyles from "@common/hooks/useThemedStyles";
import {
STRINGS,
cancelAllReminders,
@@ -11,16 +13,18 @@ import {
logError,
logMessage,
FallBack,
+ ListItemTitle,
} from "@common";
import { getBaniList } from "@database";
-import { nightModeStyles, iconNightColor } from "../../styles";
+import createStyles from "../../styles";
import { ListItemComponent, BottomSheetComponent } from "../comon";
-import setDefaultReminders from "./ReminderOptions/utils";
import { getReminderSound } from "../comon/strings";
+import setDefaultReminders from "./ReminderOptions/utils";
const RemindersComponent = ({ navigation }) => {
+ const { theme } = useTheme();
+ const styles = useThemedStyles(createStyles);
const REMINDER_SOUNDS = getReminderSound(STRINGS);
- const isNightMode = useSelector((state) => state.isNightMode);
const isReminders = useSelector((state) => state.isReminders);
const reminderSound = useSelector((state) => state.reminderSound);
const transliterationLanguage = useSelector((state) => state.transliterationLanguage);
@@ -28,8 +32,6 @@ const RemindersComponent = ({ navigation }) => {
const dispatch = useDispatch();
const { navigate } = navigation;
- const { containerNightStyles, textNightStyle } = nightModeStyles(isNightMode);
- const iconColor = iconNightColor(isNightMode);
const redirectToSettings = async () => {
Alert.alert(STRINGS.permissionTitle, STRINGS.premissionDescription, [
@@ -76,10 +78,10 @@ const RemindersComponent = ({ navigation }) => {
return (
<>
-
-
+
+
- {STRINGS.reminders}
+
handleReminders(value)} />
@@ -87,12 +89,12 @@ const RemindersComponent = ({ navigation }) => {
{isReminders && (
navigate("ReminderOptions")}
>
-
+
- {STRINGS.set_reminder_options}
+
diff --git a/src/Settings/components/statusBar.jsx b/src/Settings/components/statusBar.jsx
index aa7a6680..27e85079 100644
--- a/src/Settings/components/statusBar.jsx
+++ b/src/Settings/components/statusBar.jsx
@@ -1,26 +1,27 @@
import React from "react";
-import { ListItem, Icon, Switch } from "@rneui/themed";
import { useSelector, useDispatch } from "react-redux";
+import { ListItem, Icon, Switch } from "@rneui/themed";
import { toggleStatusBar } from "@common/actions";
-import STRINGS from "@common/localization";
-import { iconNightColor, nightModeStyles, nightModeColor } from "../styles/nightModeStyles";
+import useTheme from "@common/context";
+import useThemedStyles from "@common/hooks/useThemedStyles";
+import { STRINGS, ListItemTitle } from "@common";
+import createStyles from "../styles";
const StatusBar = () => {
const isStatusBar = useSelector((state) => state.isStatusBar);
- const isNightMode = useSelector((state) => state.isNightMode);
-
+ const { theme } = useTheme();
+ const styles = useThemedStyles(createStyles);
const dispatch = useDispatch();
- const iconColor = iconNightColor(isNightMode);
- const { containerNightStyles } = nightModeStyles(isNightMode);
- const nightColor = nightModeColor(isNightMode);
const { HIDE_STATUS_BAR } = STRINGS;
return (
-
- {!isStatusBar && }
- {isStatusBar && }
+
+ {!isStatusBar && (
+
+ )}
+ {isStatusBar && }
- {HIDE_STATUS_BAR}
+
dispatch(toggleStatusBar(value))} />
diff --git a/src/Settings/components/theme.jsx b/src/Settings/components/theme.jsx
index 6e1fbb0c..1641dc5e 100644
--- a/src/Settings/components/theme.jsx
+++ b/src/Settings/components/theme.jsx
@@ -1,8 +1,7 @@
-import React, { useState, useEffect } from "react";
-import { Appearance } from "react-native";
-import { useDispatch, useSelector } from "react-redux";
-import { setTheme, toggleNightMode } from "@common/actions";
-import { constant, STRINGS } from "@common";
+import React, { useState } from "react";
+import { useSelector } from "react-redux";
+import { setTheme } from "@common/actions";
+import { STRINGS } from "@common";
import { BottomSheetComponent, ListItemComponent } from "./comon";
import { getTheme } from "./comon/strings";
@@ -10,24 +9,8 @@ const ThemeComponent = () => {
const [isVisible, toggleVisible] = useState(false);
const theme = useSelector((state) => state.theme);
const themeIcon = require("../../../images/bgcoloricon.png");
- const dispatch = useDispatch();
const THEMES = getTheme(STRINGS);
- useEffect(() => {
- const colorScheme = Appearance.getColorScheme();
-
- switch (theme) {
- case constant.Light:
- dispatch(toggleNightMode(false));
- break;
- case constant.Dark:
- dispatch(toggleNightMode(true));
- break;
- default:
- dispatch(toggleNightMode(colorScheme !== constant.Light.toLowerCase()));
- }
- }, [theme]);
-
return (
<>
{
+ const { theme } = useTheme();
+ const styles = useThemedStyles(createStyles);
const translationAvatar = require("../../../images/englishicon.png");
const isEnglishTranslation = useSelector((state) => state.isEnglishTranslation);
const isSpanishTranslation = useSelector((state) => state.isSpanishTranslation);
const isPunjabiTranslation = useSelector((state) => state.isPunjabiTranslation);
- const isNightMode = useSelector((state) => state.isNightMode);
const dispatch = useDispatch();
const [isExpanded, toggleIsExpanded] = useState(false);
- const nightColor = iconNightColor(isNightMode);
return (
toggleIsExpanded(!isExpanded)}
content={
<>
-
- {STRINGS.translations}
-
+
>
}
icon={{
name: "chevron-down",
type: "material-community",
- color: nightColor,
+ color: theme.colors.primaryText,
size: 26,
}}
>
-
+
-
- {STRINGS.en_translations}
-
+
{
/>
-
+
-
- {STRINGS.pu_translations}
-
+
{
/>
-
+
-
- {STRINGS.es_translations}
-
+
{
+ const styles = useThemedStyles(createStyles);
const romanizedIcon = require("../../../images/romanizeicon.png");
const [isVisible, toggleVisible] = useState(false);
const transliterationLanguage = useSelector((state) => state.transliterationLanguage);
const isTransliteration = useSelector((state) => state.isTransliteration);
- const isNightMode = useSelector((state) => state.isNightMode);
const TRANSLITERATION_LANGUAGES = getTransliteration(STRINGS);
const dispatch = useDispatch();
- // Set default transliteration to English
- useEffect(() => {
- if (!isTransliteration) {
- dispatch(setTransliteration(constant.ENGLISH));
- }
- }, [isTransliteration]);
-
return (
<>
-
+
-
- {STRINGS.transliteration}
-
+
{
+ const { theme } = useTheme();
+ const styles = useThemedStyles(createStyles);
const [isVishraamOptionVisible, toggleVishraamOptionVisible] = useState(false);
const [isVishraamSourceVisible, toggleVishraamSourceVisible] = useState(false);
const isVishraam = useSelector((state) => state.isVishraam);
const vishraamOption = useSelector((state) => state.vishraamOption);
const vishraamSource = useSelector((state) => state.vishraamSource);
- const isNightMode = useSelector((state) => state.isNightMode);
const dispatch = useDispatch();
- const { containerNightStyles, textNightStyle } = nightModeStyles(isNightMode);
- const iconColor = iconNightColor(isNightMode);
const VISHRAAM_OPTIONS = getVishraamOption(STRINGS);
const VISHRAAM_SOURCES = getVishraamSource(STRINGS);
return (
<>
-
-
+
+
- {STRINGS.show_vishraams}
+
dispatch(toggleVishraam(value))} />
diff --git a/src/Settings/hooks/useHeader.js b/src/Settings/hooks/useHeader.js
new file mode 100644
index 00000000..faab8a42
--- /dev/null
+++ b/src/Settings/hooks/useHeader.js
@@ -0,0 +1,21 @@
+import React, { useEffect } from "react";
+import { BackIconComponent } from "@common/components";
+import useTheme from "@common/context";
+import useThemedStyles from "@common/hooks/useThemedStyles";
+import { STRINGS } from "@common";
+import createStyles from "../styles";
+
+const useHeader = (navigation) => {
+ const { theme } = useTheme();
+ const { headerTitleStyle, headerStyle } = useThemedStyles(createStyles);
+ const headerLeft = () => ;
+ useEffect(() => {
+ navigation.setOptions({
+ title: STRINGS.Settings,
+ headerTitleStyle,
+ headerStyle,
+ headerLeft,
+ });
+ }, [theme]);
+};
+export default useHeader;
diff --git a/src/Settings/index.js b/src/Settings/index.js
index aadb9cd1..ef0df163 100644
--- a/src/Settings/index.js
+++ b/src/Settings/index.js
@@ -1,47 +1,49 @@
import React, { useEffect } from "react";
+import { StatusBar, ScrollView } from "react-native";
import { useSelector } from "react-redux";
import PropTypes from "prop-types";
-import { StatusBar, ScrollView, Text } from "react-native";
+import useTheme from "@common/context";
+import useThemedStyles from "@common/hooks/useThemedStyles";
import {
STRINGS,
- colors,
- useScreenAnalytics,
- constant,
- logMessage,
StatusBarComponent,
SafeArea,
+ CustomText,
+ BottomNavigation,
+ useBackHandler,
} from "@common";
-import { nightModeStyles } from "./styles/nightModeStyles";
-import FontSizeComponent from "./components/fontSize";
+import Audio from "./components/audio";
+import AutoScroll from "./components/autoScroll";
+import BaniLengthComponent from "./components/baniLength";
+import CollectStatistics from "./components/collectStatistics";
+import ListItemWithIcon from "./components/comon/ListitemWithIcon";
+import DatabaseUpdateBanner from "./components/databaseUpdate";
+import Donate from "./components/donate";
+import EditBaniOrder from "./components/editBaniOrder";
import FontFaceComponent from "./components/fontFace";
+import FontSizeComponent from "./components/fontSize";
+import KeepAwake from "./components/keepAwake";
import LanguageComponent from "./components/language";
-import TransliterationComponent from "./components/transliteration";
-import ThemeComponent from "./components/theme";
-import HideStatusBar from "./components/statusBar";
-import BaniLengthComponent from "./components/baniLength";
import LarivaarComponent from "./components/larivaar";
import PadchedSettingsComponent from "./components/padched";
-import VishraamComponent from "./components/vishraam";
-import TranslationComponent from "./components/translation";
-import RemindersComponent from "./components/reminders/reminders";
-import AutoScroll from "./components/autoScroll";
-import KeepAwake from "./components/keepAwake";
-import EditBaniOrder from "./components/editBaniOrder";
import ParagraphMode from "./components/paragraphMode";
-import CollectStatistics from "./components/collectStatistics";
-import Donate from "./components/donate";
-import styles from "./styles/styles";
-import ListItemWithIcon from "./components/comon/ListitemWithIcon";
-import DatabaseUpdateBanner from "./components/databaseUpdate";
+import RemindersComponent from "./components/reminders/reminders";
+import HideStatusBar from "./components/statusBar";
+import ThemeComponent from "./components/theme";
+import TranslationComponent from "./components/translation";
+import TransliterationComponent from "./components/transliteration";
+import VishraamComponent from "./components/vishraam";
+import useHeader from "./hooks/useHeader";
+import createStyles from "./styles";
const Settings = ({ navigation }) => {
- logMessage(constant.SETTINGS);
- useScreenAnalytics(constant.SETTINGS);
- const isNightMode = useSelector((state) => state.isNightMode);
+ useHeader(navigation);
+ useBackHandler();
const isDatabaseUpdateAvailable = useSelector((state) => state.isDatabaseUpdateAvailable);
const { navigate } = navigation;
- const { scrollViewNightStyles, backgroundNightStyle } = nightModeStyles(isNightMode);
+ const { theme } = useTheme();
+ const styles = useThemedStyles(createStyles);
const { displayOptionsText, end } = styles;
const { DISPLAY_OPTIONS, BANI_OPTIONS, OTHER_OPTIONS } = STRINGS;
const language = useSelector((state) => state.language);
@@ -49,20 +51,17 @@ const Settings = ({ navigation }) => {
useEffect(() => {
navigation.setOptions({
title: STRINGS.settings,
+ headerTitleStyle: styles.headerTitleStyle,
});
}, [language]);
return (
-
-
+
+
{isDatabaseUpdateAvailable && }
- {DISPLAY_OPTIONS}
+ {DISPLAY_OPTIONS}
@@ -73,16 +72,17 @@ const Settings = ({ navigation }) => {
+
{/* Bani Options */}
- {BANI_OPTIONS}
-
+ {BANI_OPTIONS}
+
- {OTHER_OPTIONS}
+ {OTHER_OPTIONS}
{
navigate={navigate}
navigationTarget="DatabaseUpdate"
/>
-
+
+
);
};
diff --git a/src/Settings/styles/index.js b/src/Settings/styles/index.js
index 27469734..f7dd14ca 100644
--- a/src/Settings/styles/index.js
+++ b/src/Settings/styles/index.js
@@ -1,4 +1,94 @@
-import styles from "./styles";
-import { iconNightColor, nightModeStyles, nightModeColor } from "./nightModeStyles";
-
-export { styles, iconNightColor, nightModeColor, nightModeStyles };
+const createStyles = (theme) => ({
+ headerTitleStyle: {
+ color: theme.colors.primaryText,
+ fontFamily: theme.typography.fonts.balooPaajiSemiBold,
+ },
+ headerStyle: {
+ backgroundColor: theme.colors.surface,
+ },
+ nightBackColor: { backgroundColor: theme.staticColors.NIGHT_BLACK },
+ iconStyle: { alignSelf: "flex-start" },
+ imageStyle: {},
+ settingText: {
+ fontSize: theme.typography.sizes.xl,
+ alignSelf: "center",
+ color: theme.colors.primaryText,
+ position: "absolute",
+ top: theme.spacing.xl,
+ fontFamily: theme.typography.fonts.balooPaaji,
+ },
+ settingsView: { backgroundColor: theme.colors.surface },
+ displayOptionsText: {
+ padding: theme.spacing.sm + theme.spacing.xs,
+ backgroundColor: theme.colors.surface,
+ color: theme.colors.primaryText,
+ fontSize: theme.typography.sizes.md,
+ lineHeight: theme.typography.sizes.md * theme.typography.lineHeights.normal,
+ borderTopWidth: 1,
+ borderTopColor: theme.colors.separator,
+ },
+ bottomSheetTitle: {
+ textAlign: "center",
+ fontSize: theme.typography.sizes.xxl,
+ padding: theme.spacing.xl,
+ borderTopLeftRadius: theme.radius.lg + theme.spacing.sm,
+ borderTopRightRadius: theme.radius.lg + theme.spacing.sm,
+ fontWeight: theme.typography.weights.medium,
+ },
+ titleInfoStyle: {
+ fontSize: theme.typography.sizes.sm,
+ color: theme.colors.textDisabled,
+ },
+ end: {
+ padding: theme.spacing.xxl + theme.spacing.md,
+ backgroundColor: theme.colors.surface,
+ },
+ avatarStyle: { width: "100%", height: "100%", resizeMode: "contain" },
+ viewWrapper: {
+ justifyContent: "center",
+ marginTop: "auto",
+ marginLeft: "auto",
+ marginRight: "auto",
+ bottom: 0,
+ borderTopLeftRadius: theme.radius.lg + theme.spacing.sm,
+ borderTopRightRadius: theme.radius.lg + theme.spacing.sm,
+ overflow: "hidden",
+ },
+ width_100: {
+ width: "98%",
+ },
+ width_90: {
+ width: "70%",
+ },
+ blurViewStyle: { position: "absolute", top: 0, bottom: 0, left: 0, right: 0 },
+ androidViewWrapper: {
+ flex: 1,
+ justifyContent: "flex-end",
+ backgroundColor: "transparent",
+ width: "100%",
+ },
+ databaseUpdateBannerWrapper: {
+ flex: 1,
+ flexDirection: "row",
+ backgroundColor: theme.colors.primary,
+ justifyContent: "center",
+ alignItems: "center",
+ padding: theme.spacing.sm,
+ },
+ baniDbImage: {
+ width: theme.spacing.xl,
+ height: theme.spacing.xl,
+ marginRight: theme.spacing.md,
+ },
+ updateText: {
+ color: theme.staticColors.WHITE_COLOR,
+ fontSize: theme.typography.sizes.md,
+ },
+ listItemTitle: {
+ color: theme.colors.primaryText,
+ fontSize: theme.typography.sizes.lg,
+ lineHeight: theme.typography.sizes.lg * theme.typography.lineHeights.normal,
+ },
+ containerNightStyles: { backgroundColor: theme.colors.surfaceGrey },
+});
+export default createStyles;
diff --git a/src/Settings/styles/nightModeStyles.js b/src/Settings/styles/nightModeStyles.js
deleted file mode 100644
index df1dd7ec..00000000
--- a/src/Settings/styles/nightModeStyles.js
+++ /dev/null
@@ -1,29 +0,0 @@
-import colors from "@common/colors";
-
-export const nightModeStyles = (isNightMode) => ({
- scrollViewNightStyles: {
- backgroundColor: isNightMode ? colors.NIGHT_BLACK : colors.LABEL_COLORS,
- color: isNightMode ? colors.WHITE_COLOR : colors.NIGHT_BLACK,
- },
- containerNightStyles: {
- backgroundColor: isNightMode ? colors.NIGHT_GREY_COLOR : colors.WHITE_COLOR,
- },
- backgroundNightStyle: {
- backgroundColor: isNightMode ? colors.NIGHT_BLACK : colors.WHITE_COLOR,
- },
- textNightStyle: {
- color: isNightMode ? colors.WHITE_COLOR : colors.NIGHT_BLACK,
- },
- textNightGrey: {
- color: isNightMode ? colors.WHITE_COLOR : colors.DISABLED_TEXT_COLOR,
- },
-});
-
-export const iconNightColor = (isNightMode) => {
- const color = isNightMode ? colors.COMPONENT_COLOR_NIGHT_MODE : colors.COMPONENT_COLOR;
- return color;
-};
-
-export const nightModeColor = (isNightMode) => ({
- color: isNightMode ? colors.WHITE_COLOR : colors.NIGHT_BLACK,
-});
diff --git a/src/Settings/styles/styles.js b/src/Settings/styles/styles.js
deleted file mode 100644
index 71a0de9b..00000000
--- a/src/Settings/styles/styles.js
+++ /dev/null
@@ -1,64 +0,0 @@
-import { StyleSheet } from "react-native";
-import { colors } from "@common";
-
-const styles = StyleSheet.create({
- nightBackColor: { backgdroundColor: colors.NIGHT_BLACK },
- iconStyle: { alignSelf: "flex-start" },
- imageStyle: {},
- headerView: { backgroundColor: colors.TOOLBAR_COLOR_ALT, padding: 15 },
- settingText: {
- fontSize: 18,
- alignSelf: "center",
- color: colors.TOOLBAR_TINT_DARK,
- position: "absolute",
- top: 20,
- },
- settingsView: { backgroundColor: colors.TOOLBAR_COLOR_ALT },
- displayOptionsText: { padding: 7 },
- bottomSheetTitle: {
- textAlign: "center",
- fontSize: 20,
- padding: 20,
- borderTopLeftRadius: 20,
- borderTopRightRadius: 20,
- },
- titleInfoStyle: {
- fontSize: 12,
- },
- end: { padding: 40 },
- avatarStyle: { width: "100%", height: "100%", resizeMode: "contain" },
- viewWrapper: {
- justifyContent: "center",
- marginTop: "auto",
- marginLeft: "auto",
- marginRight: "auto",
- bottom: 0,
- borderTopLeftRadius: 20,
- borderTopRightRadius: 20,
- overflow: "hidden",
- },
- width_100: {
- width: "98%",
- },
- width_90: {
- width: "70%",
- },
- blurViewStyle: { position: "absolute", top: 0, bottom: 0, left: 0, right: 0 },
- androidViewWrapper: {
- flex: 1,
- justifyContent: "flex-end",
- backgroundColor: "transparent",
- width: "100%",
- },
- databaseUpdateBannerWrapper: {
- fles: 1,
- flexDirection: "row",
- backgroundColor: colors.HEADER_COLOR_1_LIGHT,
- justifyContent: "center",
- alignItems: "center",
- padding: 5,
- },
- baniDbImage: { width: 20, height: 20, marginRight: 10 },
- updateText: { color: colors.WHITE_TEXT, fontSize: 14 },
-});
-export default styles;
diff --git a/src/common/TrackPlayerUtils.js b/src/common/TrackPlayerUtils.js
new file mode 100644
index 00000000..ec68fde4
--- /dev/null
+++ b/src/common/TrackPlayerUtils.js
@@ -0,0 +1,168 @@
+import TrackPlayer, { RepeatMode, AppKilledPlaybackBehavior } from "react-native-track-player";
+import { logError, logMessage } from "./index";
+
+// Singleton service to manage TrackPlayer initialization
+class TrackPlayerService {
+ constructor() {
+ this.isInitialized = false;
+ this.initPromise = null;
+ this.activeListeners = new Set();
+ }
+
+ async initialize() {
+ // Return existing promise if already initializing
+ if (this.initPromise) {
+ return this.initPromise;
+ }
+
+ // Return immediately if already initialized
+ if (this.isInitialized) {
+ return Promise.resolve();
+ }
+
+ // Create initialization promise
+ this.initPromise = (async () => {
+ try {
+ logMessage("Initializing TrackPlayer service...");
+
+ // Setup the player with optimized configuration
+ // setupPlayer() will throw if already initialized, so we catch it
+ await TrackPlayer.setupPlayer({
+ waitForBuffer: false, // Don't wait for buffer on startup for better performance
+ maxCacheSize: 512, // Reduced cache for faster startup
+ iosCategory: "playback",
+ alwaysPauseOnInterruption: true,
+ });
+
+ // Set repeat mode
+ await TrackPlayer.setRepeatMode(RepeatMode.Off);
+
+ // Configure capabilities
+ await TrackPlayer.updateOptions({
+ android: {
+ appKilledPlaybackBehavior: AppKilledPlaybackBehavior.StopPlaybackAndRemoveNotification,
+ },
+ capabilities: [
+ TrackPlayer.Capability.Play,
+ TrackPlayer.Capability.Pause,
+ TrackPlayer.Capability.SkipToNext,
+ TrackPlayer.Capability.SkipToPrevious,
+ TrackPlayer.Capability.Stop,
+ TrackPlayer.Capability.SeekTo,
+ ],
+ compactCapabilities: [
+ TrackPlayer.Capability.Play,
+ TrackPlayer.Capability.Pause,
+ TrackPlayer.Capability.SkipToNext,
+ ],
+ });
+
+ this.isInitialized = true;
+ logMessage("TrackPlayer service initialized successfully");
+ } catch (error) {
+ // If setupPlayer throws because it's already initialized, that's okay
+ if (
+ error?.message?.includes("already initialized") ||
+ error?.code === "player_already_initialized"
+ ) {
+ this.isInitialized = true;
+ logMessage("TrackPlayer already initialized");
+ } else {
+ logError(`TrackPlayer initialization failed: ${error?.message || "Unknown error"}`);
+ this.isInitialized = false;
+ throw error;
+ }
+ } finally {
+ this.initPromise = null;
+ }
+ })();
+
+ return this.initPromise;
+ }
+
+ async cleanup() {
+ try {
+ logMessage("Cleaning up TrackPlayer service...");
+
+ // Stop any active playback
+ await TrackPlayer.stop();
+ await TrackPlayer.reset();
+
+ this.isInitialized = false;
+ logMessage("TrackPlayer service cleaned up successfully");
+ } catch (error) {
+ logError(`TrackPlayer cleanup failed: ${error?.message || "Unknown error"}`);
+ }
+ }
+
+ getState() {
+ return {
+ isInitialized: this.isInitialized,
+ };
+ }
+}
+
+// Export singleton instance
+const trackPlayerService = new TrackPlayerService();
+
+export const TrackPlayerSetup = async () => {
+ return trackPlayerService.initialize();
+};
+
+export const TrackPlayerCleanup = async () => {
+ return trackPlayerService.cleanup();
+};
+
+export const getTrackPlayerState = () => {
+ return trackPlayerService.getState();
+};
+
+export const addTrack = async (track) => {
+ try {
+ // Validate track object
+ if (!track.url) {
+ logError("Track URL is missing or empty");
+ throw new Error("Track URL is missing or empty");
+ }
+ if (!track.id) {
+ logError("Track ID is missing");
+ }
+
+ await TrackPlayer.add(track);
+ } catch (error) {
+ logError(`โ Error adding track to TrackPlayer: ${error}`);
+ throw error; // Re-throw to handle upstream
+ }
+};
+
+export const playTrack = async () => {
+ try {
+ await TrackPlayer.play();
+ } catch (error) {
+ logError(error);
+ }
+};
+
+export const pauseTrack = async () => {
+ try {
+ await TrackPlayer.pause();
+ } catch (error) {
+ logError(`Error pausing track: ${error}`);
+ }
+};
+
+export const stopTrack = async () => {
+ try {
+ await TrackPlayer.stop();
+ } catch (error) {
+ logError(`Error stopping track: ${error}`);
+ }
+};
+
+export const resetPlayer = async () => {
+ try {
+ await TrackPlayer.reset();
+ } catch (error) {
+ logError(`Error resetting player: ${error}`);
+ }
+};
diff --git a/src/common/actions/actionTypes.js b/src/common/actions/actionTypes.js
index 92c5fb32..0f671b33 100644
--- a/src/common/actions/actionTypes.js
+++ b/src/common/actions/actionTypes.js
@@ -21,6 +21,7 @@ export const TOGGLE_ENGLISH_TRANSLATION = "TOGGLE_ENGLISH_TRANSLATION";
export const TOGGLE_PUNJABI_TRANSLATION = "TOGGLE_PUNJABI_TRANSLATION";
export const TOGGLE_SPANISH_TRANSLATION = "TOGGLE_SPANISH_TRANSLATION";
export const SET_BOOKMARK_POSITION = "SET_BOOKMARK_POSITION";
+export const SET_BOOKMARK_SEQUENCE_STRING = "SET_BOOKMARK_SEQUENCE_STRING";
export const TOGGLE_REMINDERS = "TOGGLE_REMINDERS";
export const SET_REMINDER_BANIS = "SET_REMINDER_BANIS";
export const SET_REMINDER_SOUND = "SET_REMINDER_SOUND";
@@ -32,3 +33,16 @@ export const SET_BANI_ORDER = "SET_BANI_ORDER";
export const SET_SCROLL_POSITION = "SET_SCROLL_POSITION";
export const TOGGLE_HEADER_FOOTER = "TOGGLE_HEADER_FOOTER";
export const TOGGLE_DATABASE_UPDATE_AVAILABLE = "TOGGLE_DATABASE_UPDATE_AVAILABLE";
+export const TOGGLE_AUDIO = "TOGGLE_AUDIO";
+export const TOGGLE_AUDIO_AUTO_PLAY = "TOGGLE_AUDIO_AUTO_PLAY";
+export const TOGGLE_AUDIO_SYNC_SCROLL = "TOGGLE_AUDIO_SYNC_SCROLL";
+export const SET_DEFAULT_AUDIO = "SET_DEFAULT_AUDIO";
+export const SET_AUDIO_PLAYBACK_SPEED = "SET_AUDIO_PLAYBACK_SPEED";
+export const SET_CURRENT_BANI = "SET_CURRENT_BANI";
+
+// Manifest actions
+export const SET_AUDIO_MANIFEST = "SET_AUDIO_MANIFEST";
+
+// Audio progress actions
+export const SET_AUDIO_PROGRESS = "SET_AUDIO_PROGRESS";
+export const CLEAR_AUDIO_PROGRESS = "CLEAR_AUDIO_PROGRESS";
diff --git a/src/common/actions/index.js b/src/common/actions/index.js
index d7314eef..ea7f3303 100644
--- a/src/common/actions/index.js
+++ b/src/common/actions/index.js
@@ -1,7 +1,7 @@
-import * as actionTypes from "./actionTypes";
-import STRINGS from "../localization";
-import { trackSettingEvent } from "../firebase/analytics";
import constant from "../constant";
+import { trackSettingEvent, trackAudioEvent, trackArtist } from "../firebase/analytics";
+import STRINGS from "../localization";
+import * as actionTypes from "./actionTypes";
export const toggleNightMode = (value) => {
trackSettingEvent(constant.NIGHT_MODE, value);
@@ -40,6 +40,36 @@ export const toggleAutoScroll = (value) => {
return { type: actionTypes.TOGGLE_AUTO_SCROLL, value };
};
+export const toggleAudio = (value) => {
+ trackSettingEvent(constant.AUDIO, value);
+ return { type: actionTypes.TOGGLE_AUDIO, value };
+};
+
+export const toggleAudioAutoPlay = (value) => {
+ trackAudioEvent(constant.AUDIO_AUTO_PLAY, value);
+ return { type: actionTypes.TOGGLE_AUDIO_AUTO_PLAY, value };
+};
+
+export const toggleAudioSyncScroll = (value) => {
+ trackAudioEvent(constant.AUDIO_SYNC_SCROLL, value);
+ return { type: actionTypes.TOGGLE_AUDIO_SYNC_SCROLL, value };
+};
+
+export const setDefaultAudio = (audio, shabadId) => {
+ trackArtist(shabadId, audio.displayName);
+ const value = { [shabadId]: audio };
+ return { type: actionTypes.SET_DEFAULT_AUDIO, value };
+};
+
+export const setAudioPlaybackSpeed = (value) => {
+ trackAudioEvent("audioPlaybackSpeed", value);
+ return { type: actionTypes.SET_AUDIO_PLAYBACK_SPEED, value };
+};
+
+export const setCurrentBani = (bani) => {
+ return { type: actionTypes.SET_CURRENT_BANI, value: bani };
+};
+
export const toggleStatusBar = (value) => {
trackSettingEvent(constant.STATUS_BAR, value);
return { type: actionTypes.TOGGLE_STATUS_BAR, value };
@@ -110,6 +140,10 @@ export const setBookmarkPosition = (value) => {
trackSettingEvent(constant.BOOKMARKS, value);
return { type: actionTypes.SET_BOOKMARK_POSITION, value };
};
+
+export const setBookmarkSequenceString = (value) => {
+ return { type: actionTypes.SET_BOOKMARK_SEQUENCE_STRING, value };
+};
export const toggleReminders = (value) => {
trackSettingEvent(constant.REMINDERS, value);
return { type: actionTypes.TOGGLE_REMINDERS, value };
@@ -124,6 +158,7 @@ export const setReminderSound = (value) => {
};
export const setAutoScrollSpeed = (speed, shabad) => {
+ trackSettingEvent(constant.AUTO_SCROLL_SPEED, speed);
const value = { [shabad]: speed };
return { type: actionTypes.SET_AUTO_SCROLL_SPEED, value };
};
@@ -150,3 +185,26 @@ export const toggleHeaderFooter = (value) => {
export const toggleDatabaseUpdateAvailable = (value) => {
return { type: actionTypes.TOGGLE_DATABASE_UPDATE_AVAILABLE, value };
};
+
+// Manifest actions
+export const setAudioManifest = (baniId, tracks) => {
+ return {
+ type: actionTypes.SET_AUDIO_MANIFEST,
+ payload: { baniId, tracks },
+ };
+};
+
+// Audio progress actions
+export const setAudioProgress = (baniId, trackId, position, sequence) => {
+ return {
+ type: actionTypes.SET_AUDIO_PROGRESS,
+ payload: { baniId, trackId, position, sequence },
+ };
+};
+
+export const clearAudioProgress = (baniId) => {
+ return {
+ type: actionTypes.CLEAR_AUDIO_PROGRESS,
+ payload: { baniId },
+ };
+};
diff --git a/src/common/colors.js b/src/common/colors.js
index 696de164..3782471f 100644
--- a/src/common/colors.js
+++ b/src/common/colors.js
@@ -1,59 +1,9 @@
export default {
- TOOLBAR_COLOR: "#2a3381",
- READER_HEADER_COLOR: "#171d47dd",
- READER_FOOTER_COLOR: "#171d47dd",
- READER_STATUS_BAR_COLOR: "#363C5D",
- READER_STATUS_BAR_COLOR_NIGHT_MODE: "#141a3c",
- TOOLBAR_COLOR_ALT: "#DEBB0A",
- TOOLBAR_COLOR_ALT_NIGHT_MODE: "#99852c",
- TOOLBAR_COLOR_ALT2: "#003436",
- TOOLBAR_TINT: "#faf9f6",
- TOOLBAR_TINT_DARK: "#121212",
- SETTING_SWITCH_COLOR: "#5195ea",
- SETTING_SWITCH_THUMB: "#A7CAF8",
- SETTING_BACKGROUND_COLOR: "#efeff4",
- ACTIVE_VIEW_COLOR_NIGHT_MODE: "#2d2d2d",
- INACTIVE_VIEW_COLOR_NIGHT_MODE: "#232323",
- ACTIVE_VIEW_COLOR: "#C7C7D7",
- INACTIVE_VIEW_COLOR: "#e9e9ee",
- MODAL_BACKGROUND_NIGHT_MODE: "#202124",
- MODAL_BACKGROUND: "#e9e9ee",
- MODAL_ACCENT_NIGHT_MODE: "#2581df",
- MODAL_ACCENT_NIGHT_MODE_ALT: "#5195ea",
- MODAL_TEXT_NIGHT_MODE: "#faf9f6",
- MODAL_TEXT: "#121212",
- ENABELED_TEXT_COLOR_NIGHT_MODE: "#2581df",
- DISABLED_TEXT_COLOR_NIGHT_MODE: "#a3a3a3",
- ENABELED_TEXT_COLOR: "#2581df",
- DISABLED_TEXT_COLOR: "#a3a3a3",
- COMPONENT_COLOR_NIGHT_MODE: "#fefefe",
- COMPONENT_COLOR: "#232323",
+ // These can be gradually migrated to the new semantic names above
VISHRAM_SHORT: "#16a085",
VISHRAM_LONG: "#d35400",
VISHRAM_SHORT_GRADIENT: "rgba(22, 160, 133,1.0)",
VISHRAM_LONG_GRADIENT: "rgba(211, 84, 0,1.0)",
- WHITE_COLOR: "#faf9f6",
- NIGHT_BLACK: "#121212",
- UNDERLAY_COLOR: "#009bff",
- BANI_ORDER_BACK_COLOR: "eee",
- IOS_SHADOW_COLOR: "rgba(0,0,0,0.2)",
- LIGHT_MODE_COLOR: "#222222",
- HOME_BACK_COLOR: "#faf9f6",
SLIDER_TRACK_MAX_TINT: "#464646",
SLIDER_TRACK_MIN_TINT: "#BFBFBF",
- VIEW_BACK_COLOR: "#464646",
- VISHRAM_BASIC: "#c0392b",
- MODAL_BACKGROUND_COLOR: "rgba(0,0,0,0.5)",
- LABEL_COLORS: "#faf9f6",
- NIGHT_GREY_COLOR: "#464646",
- HEADER_COLOR_1_DARK: "#77baff",
- HEADER_COLOR_1_LIGHT: "#0066ff",
- HEADER_COLOR_2_LIGHT: "#727272",
- BANIDB_LIGHT: "#eaa040",
- SHADOW_COLOR: "#000",
- LIGHT_GRAY: "#aaa",
- WHITE_TEXT: "#fff",
- ANIMATION_STROKE_LIGHT: "#e6e6e6",
- ANIMATION_STROKE_ACTIVE: "#007AFF",
- NIGHT_OPACITY_BLACK: "rgba(0, 0, 0, 0.5)",
};
diff --git a/src/common/components/BackIconComponent/index.jsx b/src/common/components/BackIconComponent/index.jsx
new file mode 100644
index 00000000..5c41db47
--- /dev/null
+++ b/src/common/components/BackIconComponent/index.jsx
@@ -0,0 +1,29 @@
+import React, { useCallback } from "react";
+import { Pressable } from "react-native";
+import { useNavigation } from "@react-navigation/native";
+import PropTypes from "prop-types";
+import { BackArrowIcon } from "@common/icons";
+
+const BackIconComponent = ({ size, color }) => {
+ const navigation = useNavigation();
+
+ const handleBackPress = useCallback(() => {
+ navigation.goBack();
+ }, []);
+
+ return (
+
+
+
+ );
+};
+
+BackIconComponent.defaultProps = {
+ size: 25,
+};
+BackIconComponent.propTypes = {
+ size: PropTypes.number,
+ color: PropTypes.string.isRequired,
+};
+
+export default BackIconComponent;
diff --git a/src/common/components/BaniLengthSelector/BaniLengthSelector.jsx b/src/common/components/BaniLengthSelector/BaniLengthSelector.jsx
index 9f925d2b..38fbd3bd 100644
--- a/src/common/components/BaniLengthSelector/BaniLengthSelector.jsx
+++ b/src/common/components/BaniLengthSelector/BaniLengthSelector.jsx
@@ -1,14 +1,15 @@
import React from "react";
-import { View, Text, Pressable, Alert } from "react-native";
+import { View, Pressable, Alert } from "react-native";
import { SafeAreaProvider, SafeAreaView } from "react-native-safe-area-context";
-import { Icon } from "@rneui/themed";
import { useDispatch } from "react-redux";
-import STRINGS from "../../localization";
-import styles from "./style";
-import colors from "../../colors";
+import { Icon } from "@rneui/themed";
+import { CustomText, STRINGS, useThemedStyles, useTheme } from "@common";
import { setBaniLength } from "../../actions";
+import createStyles from "./style";
const BaniLengthSelector = () => {
+ const styles = useThemedStyles(createStyles);
+ const { theme } = useTheme();
const baniLengths = [STRINGS.short, STRINGS.medium, STRINGS.long, STRINGS.extra_long];
const dispatch = useDispatch();
@@ -25,19 +26,19 @@ const BaniLengthSelector = () => {
- {STRINGS.khalsa_sundar_gutka}
- {STRINGS.bani_length_message_1}
- {STRINGS.bani_length_message_2}
- {STRINGS.choose_your_preference}
+ {STRINGS.khalsa_sundar_gutka}
+ {STRINGS.bani_length_message_1}
+ {STRINGS.bani_length_message_2}
+ {STRINGS.choose_your_preference}
{baniLengths.map((buttonText) => (
handleOnpress(buttonText)}>
- {buttonText}
+ {buttonText}
))}
-
- {STRINGS.need_help_deciding}
- {STRINGS.click_more_info}
+
+ {STRINGS.need_help_deciding}
+ {STRINGS.click_more_info}
diff --git a/src/common/components/BaniLengthSelector/style.jsx b/src/common/components/BaniLengthSelector/style.jsx
index e202aadb..d19a034d 100644
--- a/src/common/components/BaniLengthSelector/style.jsx
+++ b/src/common/components/BaniLengthSelector/style.jsx
@@ -1,57 +1,55 @@
-import { StyleSheet } from "react-native";
-import colors from "../../colors";
-import constant from "../../constant";
-
-const styles = StyleSheet.create({
+const createStyles = (theme) => ({
heading: {
- color: colors.TOOLBAR_TINT,
- fontFamily: constant.GURBANI_AKHAR_THICK_TRUE,
+ color: theme.staticColors.WHITE_COLOR,
+ fontFamily: theme.typography.fonts.gurbaniThick,
textAlign: "center",
- fontSize: 52,
+ fontSize: theme.typography.sizes.massive + theme.spacing.xl,
},
viewWrapper: {
- margin: 10,
+ margin: theme.spacing.md,
},
wrapper: {
flex: 1,
- backgroundColor: colors.TOOLBAR_COLOR,
+ backgroundColor: theme.colors.primary,
},
baniLengthMessage: {
- marginTop: 15,
- color: colors.TOOLBAR_TINT,
- fontSize: 14,
+ marginTop: theme.spacing.lg,
+ color: theme.staticColors.WHITE_COLOR,
+ fontSize: theme.typography.sizes.md,
},
textPreferrence: {
- marginTop: 15,
- color: colors.TOOLBAR_COLOR_ALT,
- fontWeight: "bold",
- fontSize: 18,
+ marginTop: theme.spacing.lg,
+ color: theme.staticColors.WHITE_COLOR,
+ fontWeight: theme.typography.weights.bold,
+ fontSize: theme.typography.sizes.xl,
},
button: {
- backgroundColor: colors.WHITE_COLOR,
- color: colors.TOOLBAR_COLOR,
- padding: 15,
- marginTop: 15,
- fontSize: 24,
- fontWeight: "bold",
+ backgroundColor: theme.colors.surface,
+ color: theme.colors.primaryText,
+ padding: theme.spacing.lg,
+ marginTop: theme.spacing.lg,
+ fontSize: theme.typography.sizes.xxxl,
+ fontWeight: theme.typography.weights.bold,
textAlign: "center",
textTransform: "uppercase",
+ borderRadius: theme.components.button.borderRadius,
+ minHeight: theme.components.button.minHeight,
},
helpText: {
- color: colors.TOOLBAR_COLOR_ALT,
- fontWeight: "bold",
+ color: theme.colors.primaryVariant,
+ fontWeight: theme.typography.weights.bold,
fontStyle: "italic",
- fontSize: 12,
+ fontSize: theme.typography.sizes.sm,
},
moreInfo: {
- color: colors.TOOLBAR_TINT,
- fontWeight: "normal",
- fontSize: 12,
+ color: theme.colors.primaryText,
+ fontWeight: theme.typography.weights.normal,
+ fontSize: theme.typography.sizes.sm,
},
helpWrapper: {
flexDirection: "row",
alignItems: "center",
- marginTop: 15,
+ marginTop: theme.spacing.lg,
},
});
-export default styles;
+export default createStyles;
diff --git a/src/common/components/BaniList/BaniList.jsx b/src/common/components/BaniList/BaniList.jsx
index 63a555be..85bbb12e 100644
--- a/src/common/components/BaniList/BaniList.jsx
+++ b/src/common/components/BaniList/BaniList.jsx
@@ -1,17 +1,20 @@
import React, { useCallback, useEffect, useState } from "react";
import { FlatList, Dimensions, Platform } from "react-native";
+import { useSelector } from "react-redux";
import { ListItem, Avatar } from "@rneui/themed";
+import createStyles from "@settings/styles";
import PropTypes from "prop-types";
-import { useSelector } from "react-redux";
-import baseFontSize from "../../helpers";
-import colors from "../../colors";
-import { styles } from "../../../Settings/styles";
+import constant from "@common/constant";
+import useTheme from "@common/context";
+import useThemedStyles from "@common/hooks/useThemedStyles";
+import { convertToUnicode, baseFontSize, ListItemTitle } from "@common";
const BaniList = React.memo(({ data, onPress }) => {
+ const { theme } = useTheme();
+ const styles = useThemedStyles(createStyles);
const fontSize = useSelector((state) => state.fontSize);
const fontFace = useSelector((state) => state.fontFace);
const isTransliteration = useSelector((state) => state.isTransliteration);
- const isNightMode = useSelector((state) => state.isNightMode);
const [isPotrait, toggleIsPotrait] = useState(true);
const checkPotrait = () => {
@@ -24,13 +27,31 @@ const BaniList = React.memo(({ data, onPress }) => {
});
return () => subscription.remove();
}, []);
+ const isUnicode = fontFace === constant.BALOO_PAAJI;
+
+ const getBaniTuk = (row) => {
+ if (!row || !row.item) {
+ return "";
+ }
+ if (isTransliteration) {
+ return row.item.translit;
+ }
+ if (isUnicode) {
+ if (row?.item?.gurmukhiUni) {
+ return row.item.gurmukhiUni;
+ }
+ return convertToUnicode(row.item.gurmukhi);
+ }
+ return row.item.gurmukhi;
+ };
+
const renderBanis = useCallback(
(row) => {
return (
onPress(row)}
>
@@ -41,33 +62,31 @@ const BaniList = React.memo(({ data, onPress }) => {
/>
)}
-
- {isTransliteration ? row.item.translit : row.item.gurmukhi}
-
+ />
{row.item.tukGurmukhi && (
-
- {isTransliteration ? row.item.tukTranslit : row.item.tukGurmukhi}
-
+ />
)}
);
},
- [isNightMode, fontSize, fontFace, isTransliteration]
+ [theme, fontSize, fontFace, isTransliteration]
);
return (
diff --git a/src/common/components/BaniList/baniOrderHelper.js b/src/common/components/BaniList/baniOrderHelper.js
index 877b5420..0f83701c 100644
--- a/src/common/components/BaniList/baniOrderHelper.js
+++ b/src/common/components/BaniList/baniOrderHelper.js
@@ -6,6 +6,7 @@ const extractBaniDetails = (baniItem) => {
id: baniItem.id,
gurmukhi: baniItem.gurmukhi,
translit: baniItem.translit,
+ gurmukhiUni: baniItem.gurmukhiUni,
};
};
const orderedBani = (baniList, baniOrder) => {
@@ -34,7 +35,12 @@ const orderedBani = (baniList, baniOrder) => {
}, []);
return folder.length
- ? { gurmukhi: element.gurmukhi, translit: element.translit, folder }
+ ? {
+ gurmukhiUni: element.gurmukhiUni,
+ gurmukhi: element.gurmukhi,
+ translit: element.translit,
+ folder,
+ }
: null;
})
// Filter out any nulls in case an ID did not match
diff --git a/src/common/components/BottomNavigation/index.jsx b/src/common/components/BottomNavigation/index.jsx
new file mode 100644
index 00000000..dc293b2b
--- /dev/null
+++ b/src/common/components/BottomNavigation/index.jsx
@@ -0,0 +1,175 @@
+import React, { useState, useEffect, useCallback } from "react";
+import { View, Pressable } from "react-native";
+import { useDispatch, useSelector } from "react-redux";
+import { useNavigation } from "@react-navigation/native";
+import PropTypes from "prop-types";
+import useTheme from "@common/context";
+import useThemedStyles from "@common/hooks/useThemedStyles";
+import { HomeIcon, SettingsIcon, MusicIcon, ReadIcon } from "@common/icons";
+import { CustomText, actions, constant, STRINGS, SafeArea } from "@common";
+import createStyles from "./style";
+
+const BottomNavigation = ({ activeKey }) => {
+ const navigation = useNavigation();
+ const dispatch = useDispatch();
+ const { theme } = useTheme();
+ const styles = useThemedStyles(createStyles);
+ const isAudio = useSelector((state) => state.isAudio);
+ const [isSettings, setIsSettings] = useState(false);
+ const [previousRouteName, setPreviousRouteName] = useState(null);
+
+ // Helper function to get current route name
+ const getCurrentRouteName = useCallback(() => {
+ const navState = navigation.getState();
+ return navState?.routes[navState?.index]?.name;
+ }, [navigation]);
+
+ useEffect(() => {
+ const updateIsSettings = () => {
+ const state = navigation.getState?.();
+ if (!state) return;
+
+ const topRoute = state.routes[state.index];
+ let currentRouteName = topRoute?.name;
+
+ // Handle nested navigators just in case
+ if (topRoute?.state && typeof topRoute.state.index === "number") {
+ const nestedRoute = topRoute.state.routes[topRoute.state.index];
+ currentRouteName = nestedRoute?.name ?? currentRouteName;
+ }
+
+ // When entering Settings, check the previous route in navigation stack
+ if (currentRouteName === constant.SETTINGS) {
+ // Get the previous route from navigation state
+ if (state.index > 0) {
+ const prevRoute = state.routes[state.index - 1];
+ let prevRouteName = prevRoute?.name;
+ if (prevRoute?.state && typeof prevRoute.state.index === "number") {
+ const nestedRoute = prevRoute.state.routes[prevRoute.state.index];
+ prevRouteName = nestedRoute?.name ?? prevRouteName;
+ }
+ setPreviousRouteName(prevRouteName);
+ }
+ } else {
+ // Update previous route when not on Settings
+ setPreviousRouteName(currentRouteName);
+ }
+
+ setIsSettings(currentRouteName === constant.SETTINGS);
+ };
+
+ // Run once on mount
+ updateIsSettings();
+
+ // Subscribe to navigation state changes
+ const unsubscribe =
+ navigation.addListener?.("state", () => {
+ updateIsSettings();
+ }) || undefined;
+
+ return () => {
+ if (unsubscribe) {
+ unsubscribe();
+ }
+ };
+ }, [navigation]);
+
+ const navigationItems = [
+ {
+ key: "Home",
+ icon: HomeIcon,
+ handlePress: () => {
+ navigation.popToTop();
+ },
+ text: STRINGS.HOME,
+ },
+ {
+ key: "Read",
+ icon: ReadIcon,
+ handlePress: () => {
+ const currentNavRoute = getCurrentRouteName();
+
+ if (currentNavRoute === constant.SETTINGS) {
+ navigation.goBack();
+ }
+ if (isAudio) {
+ dispatch(actions.toggleAudio(false));
+ }
+ },
+ text: STRINGS.READ,
+ },
+ {
+ key: "Music",
+ icon: MusicIcon,
+ handlePress: () => {
+ const currentNavRoute = getCurrentRouteName();
+
+ if (currentNavRoute === constant.SETTINGS) {
+ navigation.goBack();
+ }
+
+ dispatch(actions.toggleAutoScroll(false));
+
+ // If coming from Settings and previous route was Reader, keep audio ON
+ if (currentNavRoute === constant.SETTINGS && isAudio) {
+ dispatch(actions.toggleAudio(true));
+ } else {
+ dispatch(actions.toggleAudio(!isAudio));
+ }
+ },
+ text: STRINGS.MUSIC,
+ },
+ {
+ key: "Settings",
+ icon: SettingsIcon,
+ handlePress: () => {
+ navigation.navigate(constant.SETTINGS);
+ },
+ text: STRINGS.SETTINGS,
+ },
+ ];
+
+ // Filter out Read and Music when on Settings page, but keep them if previous route was Read
+ const shouldHideReadAndMusic = isSettings && previousRouteName !== constant.READER;
+ const filteredNavigationItems = shouldHideReadAndMusic
+ ? navigationItems.filter((item) => item.key !== "Read" && item.key !== "Music")
+ : navigationItems;
+
+ return (
+
+
+
+ {filteredNavigationItems.map((item) => {
+ const IconComponent = item.icon;
+
+ return (
+
+
+ {activeKey !== item.key && (
+ {item.text}
+ )}
+
+ );
+ })}
+
+
+
+ );
+};
+
+BottomNavigation.propTypes = {
+ activeKey: PropTypes.string.isRequired,
+};
+
+export default BottomNavigation;
diff --git a/src/common/components/BottomNavigation/index.test.jsx b/src/common/components/BottomNavigation/index.test.jsx
new file mode 100644
index 00000000..25ea86f7
--- /dev/null
+++ b/src/common/components/BottomNavigation/index.test.jsx
@@ -0,0 +1,236 @@
+// BottomNavigation.test.jsx
+import React from "react";
+
+import { render, fireEvent } from "@testing-library/react-native";
+
+import { getMockDispatch, setMockState } from "@common/test-utils/mocks/react-redux";
+
+import BottomNavigation from "./index";
+
+// Mock styles module used by useThemedStyles (not strictly necessary because we mock the hook)
+jest.mock("./style", () => jest.fn());
+
+// Mock useNavigation hook
+let mockNavigation;
+const mockUseNavigation = jest.fn(() => mockNavigation);
+
+jest.mock("@react-navigation/native", () => ({
+ useNavigation: () => mockUseNavigation(),
+}));
+
+// --- Helpers ---
+
+const createNavigation = ({ currentRoute = "Home" } = {}) => {
+ const navigate = jest.fn();
+ const popToTop = jest.fn();
+ const goBack = jest.fn();
+ const addListener = jest.fn(() => jest.fn()); // Returns unsubscribe function
+ const routes = [{ name: "Home" }, { name: "Reader" }, { name: "Settings" }];
+ let index = 0;
+ if (currentRoute === "Reader") {
+ index = 1;
+ } else if (currentRoute === "Settings") {
+ index = 2;
+ }
+ const getState = jest.fn(() => ({
+ routes,
+ index,
+ }));
+ return { navigate, getState, popToTop, goBack, addListener };
+};
+
+describe("BottomNavigation", () => {
+ const mockDispatch = getMockDispatch();
+
+ beforeEach(() => {
+ jest.clearAllMocks();
+ setMockState({ isAudio: false });
+ mockNavigation = createNavigation();
+ mockUseNavigation.mockReturnValue(mockNavigation);
+ });
+
+ test("renders four buttons with correct accessibility labels", () => {
+ const { getByLabelText } = render( );
+
+ expect(getByLabelText("bottomnav-Home")).toBeTruthy();
+ expect(getByLabelText("bottomnav-Read")).toBeTruthy();
+ expect(getByLabelText("bottomnav-Music")).toBeTruthy();
+ expect(getByLabelText("bottomnav-Settings")).toBeTruthy();
+ });
+
+ test("shows labels for non-active items and hides label for the active item", () => {
+ const { queryByText } = render( );
+
+ // Active "Music" label should be hidden (component shows label only when NOT active)
+ expect(queryByText("Music")).toBeNull();
+
+ // Others should be visible
+ expect(queryByText("Home")).not.toBeNull();
+ expect(queryByText("Read")).not.toBeNull();
+ expect(queryByText("Settings")).not.toBeNull();
+ });
+
+ test("pressing Home navigates to Home", () => {
+ const { getByLabelText } = render( );
+
+ fireEvent.press(getByLabelText("bottomnav-Home"));
+
+ expect(mockNavigation.popToTop).toHaveBeenCalled();
+ });
+
+ test("pressing Read when audio is on toggles audio to false", () => {
+ setMockState({ isAudio: true });
+ mockNavigation = createNavigation({ currentRoute: "Home" });
+ mockUseNavigation.mockReturnValue(mockNavigation);
+
+ const { getByLabelText } = render( );
+
+ fireEvent.press(getByLabelText("bottomnav-Read"));
+
+ expect(mockDispatch).toHaveBeenCalledWith({ type: "TOGGLE_AUDIO", payload: false });
+ });
+
+ test("pressing Read when audio is off does not toggle audio", () => {
+ setMockState({ isAudio: false });
+ mockNavigation = createNavigation({ currentRoute: "Home" });
+ mockUseNavigation.mockReturnValue(mockNavigation);
+
+ const { getByLabelText } = render( );
+
+ fireEvent.press(getByLabelText("bottomnav-Read"));
+
+ expect(mockDispatch).not.toHaveBeenCalledWith({ type: "TOGGLE_AUDIO", payload: false });
+ });
+
+ test("pressing Read from Settings calls goBack", () => {
+ setMockState({ isAudio: false });
+ mockNavigation = createNavigation({ currentRoute: "Settings" });
+ mockUseNavigation.mockReturnValue(mockNavigation);
+
+ const { getByLabelText } = render( );
+
+ fireEvent.press(getByLabelText("bottomnav-Read"));
+
+ expect(mockNavigation.goBack).toHaveBeenCalled();
+ });
+
+ test("pressing Music when NOT on Reader or Settings dispatches actions", () => {
+ setMockState({ isAudio: false });
+ mockNavigation = createNavigation({ currentRoute: "Home" });
+ mockUseNavigation.mockReturnValue(mockNavigation);
+
+ const { getByLabelText } = render( );
+
+ fireEvent.press(getByLabelText("bottomnav-Music"));
+
+ // Dispatches: autoScroll=false, audio toggled from false -> true
+ expect(mockDispatch).toHaveBeenCalledWith({ type: "TOGGLE_AUTO_SCROLL", payload: false });
+ expect(mockDispatch).toHaveBeenCalledWith({ type: "TOGGLE_AUDIO", payload: true });
+ });
+
+ test("pressing Music when ALREADY on Reader dispatches actions", () => {
+ setMockState({ isAudio: false });
+ mockNavigation = createNavigation({ currentRoute: "Reader" });
+ mockUseNavigation.mockReturnValue(mockNavigation);
+
+ const { getByLabelText } = render( );
+
+ fireEvent.press(getByLabelText("bottomnav-Music"));
+
+ // Dispatches: autoScroll=false, audio toggled from false -> true
+ expect(mockDispatch).toHaveBeenCalledWith({ type: "TOGGLE_AUTO_SCROLL", payload: false });
+ expect(mockDispatch).toHaveBeenCalledWith({ type: "TOGGLE_AUDIO", payload: true });
+ });
+
+ test("pressing Music from Settings calls goBack and keeps audio ON if audio was already on", () => {
+ setMockState({ isAudio: true });
+ mockNavigation = createNavigation({ currentRoute: "Settings" });
+ mockUseNavigation.mockReturnValue(mockNavigation);
+
+ const { getByLabelText } = render( );
+
+ fireEvent.press(getByLabelText("bottomnav-Music"));
+
+ expect(mockNavigation.goBack).toHaveBeenCalled();
+ expect(mockDispatch).toHaveBeenCalledWith({ type: "TOGGLE_AUTO_SCROLL", payload: false });
+ expect(mockDispatch).toHaveBeenCalledWith({ type: "TOGGLE_AUDIO", payload: true });
+ });
+
+ test("pressing Music from Settings calls goBack and toggles audio if audio was off", () => {
+ setMockState({ isAudio: false });
+ mockNavigation = createNavigation({ currentRoute: "Settings" });
+ mockUseNavigation.mockReturnValue(mockNavigation);
+
+ const { getByLabelText } = render( );
+
+ fireEvent.press(getByLabelText("bottomnav-Music"));
+
+ expect(mockNavigation.goBack).toHaveBeenCalled();
+ expect(mockDispatch).toHaveBeenCalledWith({ type: "TOGGLE_AUTO_SCROLL", payload: false });
+ expect(mockDispatch).toHaveBeenCalledWith({ type: "TOGGLE_AUDIO", payload: true });
+ });
+
+ test("pressing Settings navigates to Settings", () => {
+ const { getByLabelText } = render( );
+
+ fireEvent.press(getByLabelText("bottomnav-Settings"));
+
+ expect(mockNavigation.navigate).toHaveBeenCalledWith("Settings");
+ });
+
+ test("pressing Music toggles audio based on current isAudio state", () => {
+ // Start with isAudio=true to verify toggle -> false
+ setMockState({ isAudio: true });
+ mockNavigation = createNavigation({ currentRoute: "Reader" });
+ mockUseNavigation.mockReturnValue(mockNavigation);
+
+ const { getByLabelText } = render( );
+
+ fireEvent.press(getByLabelText("bottomnav-Music"));
+
+ // toggleAutoScroll(false) always
+ expect(mockDispatch).toHaveBeenCalledWith({ type: "TOGGLE_AUTO_SCROLL", payload: false });
+ // toggled from true -> false
+ expect(mockDispatch).toHaveBeenCalledWith({ type: "TOGGLE_AUDIO", payload: false });
+ });
+
+ test("As a user entering Settings from Home I want irrelevant tabs hidden So that navigation isn't confusing", () => {
+ // Simulate coming from Home (not Reader)
+ mockNavigation = createNavigation({ currentRoute: "Settings" });
+ // Set up navigation state to have Home as previous route
+ mockNavigation.getState.mockReturnValue({
+ routes: [{ name: "Home" }, { name: "Settings" }],
+ index: 1,
+ });
+ mockUseNavigation.mockReturnValue(mockNavigation);
+
+ const { getByLabelText, queryByLabelText } = render( );
+
+ // Home and Settings should be visible
+ expect(getByLabelText("bottomnav-Home")).toBeTruthy();
+ expect(getByLabelText("bottomnav-Settings")).toBeTruthy();
+
+ // Read and Music should be hidden on Settings page when coming from Home
+ expect(queryByLabelText("bottomnav-Read")).toBeNull();
+ expect(queryByLabelText("bottomnav-Music")).toBeNull();
+ });
+
+ test("As a user entering Settings from Reader I want Read and Music tabs to stay visible", () => {
+ // Simulate coming from Reader
+ mockNavigation = createNavigation({ currentRoute: "Settings" });
+ // Set up navigation state to have Reader as previous route
+ mockNavigation.getState.mockReturnValue({
+ routes: [{ name: "Home" }, { name: "Reader" }, { name: "Settings" }],
+ index: 2,
+ });
+ mockUseNavigation.mockReturnValue(mockNavigation);
+
+ const { getByLabelText } = render( );
+
+ // All tabs should be visible when coming from Reader
+ expect(getByLabelText("bottomnav-Home")).toBeTruthy();
+ expect(getByLabelText("bottomnav-Read")).toBeTruthy();
+ expect(getByLabelText("bottomnav-Music")).toBeTruthy();
+ expect(getByLabelText("bottomnav-Settings")).toBeTruthy();
+ });
+});
diff --git a/src/common/components/BottomNavigation/style.js b/src/common/components/BottomNavigation/style.js
new file mode 100644
index 00000000..8d1c91df
--- /dev/null
+++ b/src/common/components/BottomNavigation/style.js
@@ -0,0 +1,34 @@
+const createStyles = (theme) => ({
+ container: {
+ width: "100%",
+ backgroundColor: theme.colors.primary,
+ height: theme.components.bottomNavigation.height,
+ justifyContent: "center",
+ },
+ navigationBar: {
+ flexDirection: "row",
+ justifyContent: "space-evenly",
+ width: "85%",
+ marginLeft: "auto",
+ marginRight: "auto",
+ gap: 25,
+ },
+ iconContainer: {
+ flexBasis: 50,
+ height: 50,
+ alignItems: "center",
+ justifyContent: "center",
+ },
+ activeIconContainer: {
+ backgroundColor: theme.staticColors.WHITE_COLOR,
+ borderRadius: 15,
+ padding: theme.spacing.lg,
+ },
+ iconText: {
+ fontSize: theme.typography.sizes.sm,
+ color: theme.staticColors.WHITE_COLOR,
+ fontFamily: theme.typography.fonts.balooPaaji,
+ },
+});
+
+export default createStyles;
diff --git a/src/common/components/CustomText/index.jsx b/src/common/components/CustomText/index.jsx
new file mode 100644
index 00000000..acd090eb
--- /dev/null
+++ b/src/common/components/CustomText/index.jsx
@@ -0,0 +1,41 @@
+import React from "react";
+import { Text } from "react-native";
+import PropTypes from "prop-types";
+import useTheme from "@common/context";
+
+const CustomText = ({ style, children, numberOfLines, onPress, onLongPress }) => {
+ const { theme } = useTheme();
+ const textStyle = Array.isArray(style)
+ ? [{ fontFamily: theme.typography.fonts.balooPaaji }, ...style]
+ : [{ fontFamily: theme.typography.fonts.balooPaaji }, style];
+
+ return (
+
+ {children}
+
+ );
+};
+
+CustomText.propTypes = {
+ style: PropTypes.oneOfType([PropTypes.object, PropTypes.array]),
+ children: PropTypes.node,
+ numberOfLines: PropTypes.number,
+ onPress: PropTypes.func,
+ onLongPress: PropTypes.func,
+};
+
+CustomText.defaultProps = {
+ style: null,
+ children: null,
+ numberOfLines: null,
+ onPress: null,
+ onLongPress: null,
+};
+
+export default CustomText;
diff --git a/src/common/components/FallbackComponent/index.jsx b/src/common/components/FallbackComponent/index.jsx
index 4b9634b7..3a9c6229 100644
--- a/src/common/components/FallbackComponent/index.jsx
+++ b/src/common/components/FallbackComponent/index.jsx
@@ -1,22 +1,27 @@
-import React from "react";
-import { Button, Text, View, Linking } from "react-native";
-import { SafeAreaProvider, SafeAreaView } from "react-native-safe-area-context";
+import React, { useEffect } from "react";
+import { Button, View, Linking } from "react-native";
import RNRestart from "react-native-restart";
-import STRINGS from "../../localization";
-import styles from "./styles";
-import useScreenAnalytics from "../../hooks/useScreenAnalytics";
+import { SafeAreaProvider, SafeAreaView } from "react-native-safe-area-context";
+import useThemedStyles from "@common/hooks/useThemedStyles";
+import { CustomText } from "@common";
import constant from "../../constant";
-import { logMessage } from "../../firebase/crashlytics";
+import { trackScreenView } from "../../firebase/analytics";
+import STRINGS from "../../localization";
+import createStyles from "./styles";
const FallBack = () => {
- logMessage(constant.FALLBACK);
+ const styles = useThemedStyles(createStyles);
const { container, title, text, btnWrap } = styles;
- useScreenAnalytics(constant.FALLBACK);
+
+ useEffect(() => {
+ // Track screen view when error fallback is shown
+ trackScreenView(constant.FALLBACK_SCREEN, null, "Error Boundary Fallback Screen");
+ }, []);
return (
- {STRINGS.errorTitle}
- {STRINGS.errorMessage}
+ {STRINGS.errorTitle}
+ {STRINGS.errorMessage}
RNRestart.Restart()} title={STRINGS.errorReload} />
({
container: {
flex: 1,
justifyContent: "center",
alignItems: "center",
- backgroundColor: colors.WHITE_COLOR,
- padding: 8,
+ backgroundColor: theme.colors.surface,
+ padding: theme.spacing.md,
textAlign: "center",
},
title: {
- fontSize: 18,
- fontWeight: "bold",
+ fontSize: theme.typography.sizes.xl,
+ fontWeight: theme.typography.weights.bold,
textAlign: "center",
+ color: theme.colors.primaryText,
+ marginBottom: theme.spacing.lg,
},
icon: {
- fontSize: 48,
+ fontSize: theme.typography.sizes.xxxl + theme.typography.sizes.xl,
+ color: theme.colors.primaryText,
+ marginBottom: theme.spacing.lg,
},
text: {
- marginVertical: 16,
+ marginVertical: theme.spacing.lg,
textAlign: "center",
width: 300,
+ fontSize: theme.typography.sizes.lg,
+ color: theme.colors.primaryText,
+ },
+ btnWrap: {
+ flexDirection: "row",
+ marginTop: theme.spacing.xl,
},
- btnWrap: { flexDirection: "row" },
});
-export default styles;
+export default createStyles;
diff --git a/src/common/components/ListItemTitle/index.jsx b/src/common/components/ListItemTitle/index.jsx
new file mode 100644
index 00000000..2d055710
--- /dev/null
+++ b/src/common/components/ListItemTitle/index.jsx
@@ -0,0 +1,28 @@
+import React from "react";
+import { ListItem } from "@rneui/themed";
+import PropTypes from "prop-types";
+import useTheme from "@common/context";
+
+const ListItemTitle = ({ title, style }) => {
+ const { theme } = useTheme();
+ const textStyle = Array.isArray(style)
+ ? [{ fontFamily: theme.typography.fonts.balooPaaji }, ...style]
+ : [{ fontFamily: theme.typography.fonts.balooPaaji }, style];
+
+ return (
+
+ {title}
+
+ );
+};
+
+ListItemTitle.propTypes = {
+ title: PropTypes.string.isRequired,
+ style: PropTypes.oneOfType([PropTypes.object, PropTypes.array]),
+};
+
+ListItemTitle.defaultProps = {
+ style: null,
+};
+
+export default ListItemTitle;
diff --git a/src/common/components/SafeArea/index.jsx b/src/common/components/SafeArea/index.jsx
index 5efa8cfb..1d7ec48d 100644
--- a/src/common/components/SafeArea/index.jsx
+++ b/src/common/components/SafeArea/index.jsx
@@ -1,25 +1,25 @@
import React from "react";
+import { SafeAreaView } from "react-native-safe-area-context";
import PropTypes from "prop-types";
-import { SafeAreaProvider, SafeAreaView } from "react-native-safe-area-context";
-const SafeArea = ({ children, backgroundColor, edges = ["top", "right", "bottom", "left"] }) => {
+const SafeArea = ({ children, backgroundColor, edges = [], flex = 1 }) => {
return (
-
-
- {children}
-
-
+
+ {children}
+
);
};
SafeArea.propTypes = {
children: PropTypes.node.isRequired,
backgroundColor: PropTypes.string.isRequired,
- edges: PropTypes.arrayOf(PropTypes.oneOf(["top", "right", "bottom", "left"])),
+ edges: PropTypes.arrayOf(PropTypes.oneOf(["top", "bottom", "left", "right"])),
+ flex: PropTypes.number,
};
SafeArea.defaultProps = {
- edges: ["top", "right", "bottom", "left"],
+ edges: ["left", "right"],
+ flex: 1,
};
export default SafeArea;
diff --git a/src/common/components/SettingsIconComponent/index.jsx b/src/common/components/SettingsIconComponent/index.jsx
new file mode 100644
index 00000000..a5aa1c06
--- /dev/null
+++ b/src/common/components/SettingsIconComponent/index.jsx
@@ -0,0 +1,27 @@
+import React from "react";
+import { Pressable } from "react-native";
+import PropTypes from "prop-types";
+import { SettingsIcon } from "@common/icons";
+
+const SettingsIconComponent = ({ size, handleSettingsPress, color }) => {
+ return (
+ {
+ handleSettingsPress();
+ }}
+ >
+
+
+ );
+};
+
+SettingsIconComponent.defaultProps = {
+ size: 25,
+};
+SettingsIconComponent.propTypes = {
+ size: PropTypes.number,
+ handleSettingsPress: PropTypes.func.isRequired,
+ color: PropTypes.string.isRequired,
+};
+
+export default SettingsIconComponent;
diff --git a/src/common/components/StatusBar/index.jsx b/src/common/components/StatusBar/index.jsx
index d431d749..2c22c9c4 100644
--- a/src/common/components/StatusBar/index.jsx
+++ b/src/common/components/StatusBar/index.jsx
@@ -1,26 +1,13 @@
import React from "react";
-import { StatusBar, Platform, View } from "react-native";
+import { StatusBar } from "react-native";
import { useSelector } from "react-redux";
import PropTypes from "prop-types";
+import useTheme from "@common/context";
const StatusBarComponent = ({ backgroundColor }) => {
const isStatusBar = useSelector((state) => state.isStatusBar);
- const isNightMode = useSelector((state) => state.isNightMode);
-
- const barStyle = isNightMode ? "light-content" : "dark-content";
-
- if (Platform.OS === "ios") {
- return (
-
-
-
- );
- }
+ const { theme } = useTheme();
+ const barStyle = theme.mode === "dark" ? "light-content" : "dark-content";
return ;
};
diff --git a/src/common/components/index.js b/src/common/components/index.js
index 60f6d72f..f78ce45f 100644
--- a/src/common/components/index.js
+++ b/src/common/components/index.js
@@ -1,5 +1,23 @@
-import FallBack from "./FallbackComponent";
-import BaniList from "./BaniList/BaniList";
+import BackIconComponent from "./BackIconComponent";
import BaniLengthSelector from "./BaniLengthSelector";
+import BaniList from "./BaniList/BaniList";
+import BottomNavigation from "./BottomNavigation";
+import CustomText from "./CustomText";
+import FallBack from "./FallbackComponent";
+import ListItemTitle from "./ListItemTitle";
+import SafeArea from "./SafeArea";
+import SettingsIconComponent from "./SettingsIconComponent";
+import StatusBarComponent from "./StatusBar";
-export { FallBack, BaniList, BaniLengthSelector };
+export {
+ FallBack,
+ BaniList,
+ BaniLengthSelector,
+ BottomNavigation,
+ SafeArea,
+ StatusBarComponent,
+ CustomText,
+ ListItemTitle,
+ BackIconComponent,
+ SettingsIconComponent,
+};
diff --git a/src/common/constant.js b/src/common/constant.js
index 15f95f37..c1cba051 100644
--- a/src/common/constant.js
+++ b/src/common/constant.js
@@ -7,6 +7,10 @@ export default {
IPA: "IPA",
GURBANI_AKHAR_TRUE: "GurbaniAkharTrue",
GURBANI_AKHAR_THICK_TRUE: "GurbaniAkharThickTrue",
+ BALOO_PAAJI: "BalooPaaji2-Regular",
+ BALOO_PAAJI_SEMI_BOLD: "BalooPaaji2-SemiBold",
+ GURBANI_AKHAR_HEAVY_TRUE: "GurbaniAkharHeavyTrue",
+ ANMOL_LIPI: "AnmolLipiSG",
READER: "Reader",
SETTINGS: "Settings",
EXTRA_SMALL: "EXTRA_SMALL",
@@ -31,7 +35,6 @@ export default {
EXISTS_TAKSAL: "existsTaksal",
EXISTS_MEDIUM: "existsMedium",
EXISTS_SGPS: "existsSGPC",
- Arial: "Arial",
GURMUKHI: "GURMUKHI",
TRANSLITERATION: "TRANSLITERATION",
TRANSLATION: "TRANSLATION",
@@ -72,6 +75,10 @@ export default {
STATUS_BAR: "statusBar",
PARAGRAPH: "paragraph",
AUTO_SCROLL: "autoScroll",
+ AUDIO: "audio",
+ AUDIO_AUTO_PLAY: "audioAutoPlay",
+ AUDIO_SYNC_SCROLL: "audioSyncScroll",
+ DEFAULT_AUDIO: "defaultAudio",
VISHRAAM: "vishraam",
VISHRAAM_OPTION: "vishraamOption",
VISHRAAM_SOURCE: "vishraamSource",
@@ -84,7 +91,18 @@ export default {
PORTRAIT: "PORTRAIT",
LANDSCAPE: "LANDSCAPE",
REMOTE_DB_URL: "https://banidb.blob.core.windows.net/database",
+ BASIC_AUTH_USERNAME: "admin",
+ BASIC_AUTH_PASSWORD: "",
+ REMOTE_AUDIO_API_URL:
+ "https://sttm-audio-api-v2.salmonriver-80392db4.eastus.azurecontainerapps.io/v1",
CHOPAYI_SAHIB_ID: 9,
REHRAAS_SAHIB_ID: 21,
MAST_SABH_MAST_TUKK: "smwpq msqu suB msqu",
+ MAST_SABH_MAST_TUKK_UNI: "เจธเจฎเจพเจชเจค เจฎเจธเจคเฉ เจธเฉเจญ เจฎเจธเจคเฉ",
+ MINIMUM_BOTTOM_PADDING: 35,
+ defaultBani: {
+ id: 1,
+ title: "gur mMqR",
+ titleUni: "เจเฉเจฐ เจฎเฉฐเจคเฉเจฐ",
+ },
};
diff --git a/src/common/context/ThemeContext.js b/src/common/context/ThemeContext.js
new file mode 100644
index 00000000..a77b0631
--- /dev/null
+++ b/src/common/context/ThemeContext.js
@@ -0,0 +1,13 @@
+import { createContext, useContext } from "react";
+
+const ThemeContext = createContext(null);
+
+export const useTheme = () => {
+ const context = useContext(ThemeContext);
+ if (!context) {
+ throw new Error("useTheme must be used within a ThemeProvider");
+ }
+ return context;
+};
+
+export default ThemeContext;
diff --git a/src/common/context/ThemeProvider.jsx b/src/common/context/ThemeProvider.jsx
new file mode 100644
index 00000000..f5860e66
--- /dev/null
+++ b/src/common/context/ThemeProvider.jsx
@@ -0,0 +1,45 @@
+import React, { useState, useEffect, useMemo } from "react";
+import { Appearance } from "react-native";
+import { useSelector, useDispatch } from "react-redux";
+import PropTypes from "prop-types";
+import { lightTheme, darkTheme } from "@theme";
+import { setTheme } from "../actions";
+import constant from "../constant";
+import ThemeContext from "./ThemeContext";
+
+const ThemeProvider = ({ children }) => {
+ const dispatch = useDispatch();
+ const themeMode = useSelector((state) => state.theme);
+ const [systemColorScheme, setSystemColorScheme] = useState(Appearance.getColorScheme());
+
+ useEffect(() => {
+ const subscription = Appearance.addChangeListener(({ colorScheme }) => {
+ setSystemColorScheme(colorScheme);
+ });
+ return () => subscription.remove();
+ }, []);
+
+ // Use useMemo to prevent infinite re-renders
+ const theme = useMemo(() => {
+ if (themeMode === constant.Default) {
+ return systemColorScheme === "dark" ? darkTheme : lightTheme;
+ }
+ if (themeMode === constant.Dark) {
+ return darkTheme;
+ }
+ return lightTheme;
+ }, [themeMode, systemColorScheme]);
+
+ const value = useMemo(
+ () => ({ theme, setThemeMode: (mode) => dispatch(setTheme(mode)) }),
+ [theme, dispatch]
+ );
+
+ return {children} ;
+};
+
+ThemeProvider.propTypes = {
+ children: PropTypes.node.isRequired,
+};
+
+export default ThemeProvider;
diff --git a/src/common/context/index.js b/src/common/context/index.js
new file mode 100644
index 00000000..f7fb56be
--- /dev/null
+++ b/src/common/context/index.js
@@ -0,0 +1,3 @@
+import { useTheme } from "./ThemeContext";
+
+export default useTheme;
diff --git a/src/common/firebase.js b/src/common/firebase.js
index 975dd972..8a067174 100644
--- a/src/common/firebase.js
+++ b/src/common/firebase.js
@@ -1,5 +1,5 @@
-import messaging from "@react-native-firebase/messaging";
import { Alert } from "react-native";
+import messaging from "@react-native-firebase/messaging";
const getFcmToken = async () => {
await messaging().getToken();
diff --git a/src/common/firebase/analytics.js b/src/common/firebase/analytics.js
index dedbed0e..03c824c8 100644
--- a/src/common/firebase/analytics.js
+++ b/src/common/firebase/analytics.js
@@ -1,44 +1,137 @@
import {
- getAppInstanceId,
- logScreenView,
logEvent,
getAnalytics,
setAnalyticsCollectionEnabled,
} from "@react-native-firebase/analytics";
import { getApp } from "@react-native-firebase/app";
+import { logError } from "./crashlytics";
+import { sanitizeName } from "./helper";
const app = getApp();
const analytics = getAnalytics(app);
-const trackEvent = (category, action, label) => {
- logEvent(analytics, category, {
- [action]: label,
- });
+
+const MAX_PARAM_LENGTH = 40;
+const MAX_VALUE_LENGTH = 100;
+const DISALLOWED_PREFIXES = ["firebase_", "google_", "ga_"];
+
+const sanitize = (value, fallback) => {
+ const sanitized = sanitizeName(value, MAX_PARAM_LENGTH, fallback);
+ const lower = sanitized.toLowerCase();
+ if (DISALLOWED_PREFIXES.some((prefix) => lower.startsWith(prefix))) {
+ return fallback;
+ }
+ return sanitized;
+};
+
+// Safely log events with error handling
+const trackEvent = async (category, action, label) => {
+ const eventName = sanitize(category, "custom_event");
+ const paramName = sanitize(action, "detail");
+ const paramValue = label == null ? "" : String(label).slice(0, MAX_VALUE_LENGTH);
+
+ try {
+ await logEvent(analytics, eventName, {
+ [paramName]: paramValue,
+ });
+ } catch (error) {
+ // Silently fail - analytics should never crash the app
+ logError(
+ new Error(
+ `Analytics tracking failed for category: ${eventName}, param: ${paramName} - ${
+ error?.message || "Unknown error"
+ }`
+ )
+ );
+ }
+};
+
+const allowTracking = async (isStatistics) => {
+ try {
+ await setAnalyticsCollectionEnabled(analytics, isStatistics);
+ } catch (error) {
+ // Silently fail - analytics initialization should never crash the app
+ logError(
+ new Error(`Failed to initialize analytics tracking - ${error?.message || "Unknown error"}`)
+ );
+ }
};
-const allowTracking = async () => {
- const appInstanceId = await getAppInstanceId(analytics);
- if (!appInstanceId) {
- await setAnalyticsCollectionEnabled(analytics, true);
+const trackScreenView = async (screenName, key, title) => {
+ try {
+ const params = {
+ screen_name: screenName || "unknown",
+ screen_class: (screenName || "unknown").replace(/\s+/g, ""),
+ };
+
+ // Only add optional parameters if they have values
+ if (key != null) {
+ params.key = String(key).slice(0, MAX_VALUE_LENGTH);
+ }
+ if (title != null) {
+ params.title = String(title).slice(0, MAX_VALUE_LENGTH);
+ }
+
+ await logEvent(analytics, "screen_view", params);
+ } catch (error) {
+ // Silently fail - screen tracking should never crash the app
+ logError(
+ new Error(`Failed to track screen view: ${screenName} - ${error?.message || "Unknown error"}`)
+ );
}
};
-const trackScreenView = (screenName, screenClass = screenName) => {
- logScreenView(analytics, {
- screen_name: screenName,
- screen_class: screenClass.replace(/\s+/g, ""),
- });
+const trackReaderEvent = async (action, label) => {
+ await trackEvent("reader", action, label);
+};
+
+const trackAudioEvent = async (action, label) => {
+ await trackEvent("audio", action, label);
+};
+
+const trackArtistListeningDuration = async (baniID, artist, duration) => {
+ const params = {
+ event: "artistListenDuration",
+ baniID,
+ artist,
+ duration,
+ };
+ await logEvent(analytics, "audio", params);
};
-const trackReaderEvent = (action, label) => {
- trackEvent("reader", action, label);
+const trackArtist = async (baniID, artist) => {
+ const params = {
+ event: "defaultArtist",
+ baniID,
+ artist,
+ };
+ await logEvent(analytics, "audio", params);
};
-const trackSettingEvent = (action, label) => {
- trackEvent("setting", action, label);
+const trackSettingEvent = async (action, label) => {
+ await trackEvent("setting", action, label);
};
-const trackReminderEvent = (action, label) => {
- trackEvent("reminder", action, label);
+const trackReminderEvent = async (action, label) => {
+ await trackEvent("reminder", action, label);
};
-export { allowTracking, trackReaderEvent, trackSettingEvent, trackScreenView, trackReminderEvent };
+// Wrapper to safely handle analytics operations
+export const safeAnalyticsOperation = (operation) => {
+ try {
+ return operation();
+ } catch (error) {
+ logError(new Error(`Safe analytics operation failed - ${error?.message || "Unknown error"}`));
+ return null;
+ }
+};
+
+export {
+ allowTracking,
+ trackReaderEvent,
+ trackSettingEvent,
+ trackReminderEvent,
+ trackScreenView,
+ trackAudioEvent,
+ trackArtistListeningDuration,
+ trackArtist,
+};
diff --git a/src/common/firebase/crashlytics.js b/src/common/firebase/crashlytics.js
index 6e550ba2..90dadaab 100644
--- a/src/common/firebase/crashlytics.js
+++ b/src/common/firebase/crashlytics.js
@@ -3,35 +3,69 @@ import {
setCrashlyticsCollectionEnabled,
crash,
setAttribute,
- setAttributes,
log,
recordError,
} from "@react-native-firebase/crashlytics";
+import { sanitizeName } from "./helper";
const crashlytics = getCrashlytics();
+// Crashlytics limits
+const MAX_KEY_LENGTH = 32; // Firebase doc limit
+const MAX_VALUE_LENGTH = 1024; // Firebase doc limit
+
+const normalizeValue = (value) => {
+ if (value == null) {
+ return "";
+ }
+ if (value instanceof Error) {
+ return value.message.slice(0, MAX_VALUE_LENGTH);
+ }
+ if (typeof value === "object") {
+ try {
+ return JSON.stringify(value).slice(0, MAX_VALUE_LENGTH);
+ } catch (err) {
+ return "[unserializable]";
+ }
+ }
+ return String(value).slice(0, MAX_VALUE_LENGTH);
+};
+
+const safeSetAttribute = (key, value) => {
+ const safeKey = sanitizeName(key, MAX_KEY_LENGTH);
+ if (!safeKey) return;
+ try {
+ setAttribute(crashlytics, safeKey, normalizeValue(value));
+ } catch {
+ // Swallow errors to avoid impacting app flow
+ }
+};
+
+const safeSetAttributes = (keyValues) => {
+ if (!keyValues || typeof keyValues !== "object") return;
+ const entries = Object.entries(keyValues).slice(0, 64); // Crashlytics supports up to 64 keys
+ entries.forEach(([key, value]) => safeSetAttribute(key, value));
+};
+
// Enable Crashlytics data collection
export const initializeCrashlytics = async () => {
- await setCrashlyticsCollectionEnabled(crashlytics, true);
-
- // Log a message to indicate successful initialization
- log(crashlytics, "Crashlytics initialized");
+ try {
+ await setCrashlyticsCollectionEnabled(crashlytics, true);
+ log(crashlytics, "Crashlytics initialized");
+ } catch {
+ // Do not block app startup if Crashlytics fails to init
+ }
};
// Set a custom key-value pair
export const setCustomKey = (keyOrValues, value = undefined) => {
if (typeof keyOrValues === "string" && value !== undefined) {
- setAttribute(crashlytics, keyOrValues, value);
- } else if (
- typeof keyOrValues === "object" &&
- keyOrValues !== null &&
- !Array.isArray(keyOrValues)
- ) {
- setAttributes(crashlytics, keyOrValues);
- } else {
- throw new Error(
- "Invalid arguments. Provide a string key and a value, or an object of key-value pairs."
- );
+ safeSetAttribute(keyOrValues, value);
+ return;
+ }
+
+ if (typeof keyOrValues === "object" && keyOrValues !== null && !Array.isArray(keyOrValues)) {
+ safeSetAttributes(keyOrValues);
}
};
@@ -42,11 +76,15 @@ export const logMessage = (message) => {
// Log a custom error
export const logError = (error) => {
- if (error instanceof Error) {
- recordError(crashlytics, error);
- } else {
- const newError = new Error(`Non-Error exception: ${error}`);
- recordError(crashlytics, newError);
+ try {
+ if (error instanceof Error) {
+ recordError(crashlytics, error);
+ } else {
+ const newError = new Error(`Non-Error exception: ${error}`);
+ recordError(crashlytics, newError);
+ }
+ } catch {
+ // Avoid surfacing errors from logging itself
}
};
diff --git a/src/common/firebase/helper.js b/src/common/firebase/helper.js
new file mode 100644
index 00000000..6a583b20
--- /dev/null
+++ b/src/common/firebase/helper.js
@@ -0,0 +1,11 @@
+// eslint-disable-next-line import/prefer-default-export
+export const sanitizeName = (value, maxLength, fallback = null) => {
+ if (!value) return fallback;
+ const normalized = String(value)
+ .trim()
+ .replace(/\s+/g, "_")
+ .replace(/[^a-zA-Z0-9_]/g, "")
+ .slice(0, maxLength);
+ if (!normalized) return fallback;
+ return normalized;
+};
diff --git a/src/common/hooks/useBackHandler/index.js b/src/common/hooks/useBackHandler/index.js
new file mode 100644
index 00000000..7ad282d6
--- /dev/null
+++ b/src/common/hooks/useBackHandler/index.js
@@ -0,0 +1,23 @@
+import { useEffect } from "react";
+import { BackHandler } from "react-native";
+import { useNavigation } from "@react-navigation/native";
+
+const useBackHandler = (handleBackPress) => {
+ const navigation = useNavigation();
+
+ useEffect(() => {
+ const handleBack = () => {
+ if (handleBackPress && typeof handleBackPress === "function") {
+ return handleBackPress();
+ }
+ // Default behavior: navigate back
+ navigation.goBack();
+ return true;
+ };
+
+ const backHandler = BackHandler.addEventListener("hardwareBackPress", handleBack);
+ return () => backHandler.remove();
+ }, [handleBackPress, navigation]);
+};
+
+export default useBackHandler;
diff --git a/src/common/hooks/useBackHandler/index.test.js b/src/common/hooks/useBackHandler/index.test.js
new file mode 100644
index 00000000..ecf3f4a2
--- /dev/null
+++ b/src/common/hooks/useBackHandler/index.test.js
@@ -0,0 +1,202 @@
+/* eslint-env jest */
+
+import React from "react";
+import { BackHandler } from "react-native";
+import { render, act } from "@testing-library/react-native";
+import useBackHandler from "./index";
+
+// Mock BackHandler
+let backPressHandler;
+const mockRemove = jest.fn();
+
+BackHandler.addEventListener = jest.fn((event, handler) => {
+ if (event === "hardwareBackPress") {
+ backPressHandler = handler;
+ }
+ return {
+ remove: mockRemove,
+ };
+});
+
+// Mock React Navigation
+const mockGoBack = jest.fn();
+const mockUseNavigation = jest.fn(() => ({
+ goBack: mockGoBack,
+}));
+
+jest.mock("@react-navigation/native", () => ({
+ useNavigation: () => mockUseNavigation(),
+}));
+
+// Test component that uses the hook
+const TestComponent = ({ handleBackPress }) => {
+ useBackHandler(handleBackPress);
+ return null;
+};
+
+describe("useBackHandler", () => {
+ beforeEach(() => {
+ jest.clearAllMocks();
+ backPressHandler = null;
+ mockUseNavigation.mockReturnValue({
+ goBack: mockGoBack,
+ });
+ });
+
+ it("should call navigation.goBack when handleBackPress is not provided", () => {
+ render( );
+
+ act(() => {
+ // Simulate hardware back press
+ if (backPressHandler) {
+ backPressHandler();
+ }
+ });
+
+ expect(mockGoBack).toHaveBeenCalledTimes(1);
+ });
+
+ it("should call navigation.goBack when handleBackPress is undefined", () => {
+ render( );
+
+ act(() => {
+ if (backPressHandler) {
+ backPressHandler();
+ }
+ });
+
+ expect(mockGoBack).toHaveBeenCalledTimes(1);
+ });
+
+ it("should call navigation.goBack when handleBackPress is null", () => {
+ render( );
+
+ act(() => {
+ if (backPressHandler) {
+ backPressHandler();
+ }
+ });
+
+ expect(mockGoBack).toHaveBeenCalledTimes(1);
+ });
+
+ it("should call custom handleBackPress when provided", () => {
+ const customHandler = jest.fn(() => true);
+
+ render( );
+
+ act(() => {
+ if (backPressHandler) {
+ backPressHandler();
+ }
+ });
+
+ expect(customHandler).toHaveBeenCalledTimes(1);
+ expect(mockGoBack).not.toHaveBeenCalled();
+ });
+
+ it("should call custom handleBackPress and respect its return value", () => {
+ const customHandler = jest.fn(() => false);
+
+ render( );
+
+ act(() => {
+ if (backPressHandler) {
+ backPressHandler();
+ }
+ });
+
+ expect(customHandler).toHaveBeenCalledTimes(1);
+ expect(mockGoBack).not.toHaveBeenCalled();
+ });
+
+ it("should not call navigation.goBack when handleBackPress is not a function", () => {
+ render( );
+
+ act(() => {
+ if (backPressHandler) {
+ backPressHandler();
+ }
+ });
+
+ expect(mockGoBack).toHaveBeenCalledTimes(1);
+ });
+
+ it("should not call navigation.goBack when handleBackPress is a number", () => {
+ render( );
+
+ act(() => {
+ if (backPressHandler) {
+ backPressHandler();
+ }
+ });
+
+ expect(mockGoBack).toHaveBeenCalledTimes(1);
+ });
+
+ it("should clean up event listener on unmount", () => {
+ const { unmount } = render( );
+
+ unmount();
+
+ expect(mockRemove).toHaveBeenCalledTimes(1);
+ });
+
+ it("should update handler when handleBackPress changes", () => {
+ const firstHandler = jest.fn(() => true);
+ const secondHandler = jest.fn(() => true);
+
+ const { rerender } = render( );
+
+ act(() => {
+ if (backPressHandler) {
+ backPressHandler();
+ }
+ });
+
+ expect(firstHandler).toHaveBeenCalledTimes(1);
+ expect(secondHandler).not.toHaveBeenCalled();
+
+ jest.clearAllMocks();
+
+ rerender( );
+
+ act(() => {
+ if (backPressHandler) {
+ backPressHandler();
+ }
+ });
+
+ expect(secondHandler).toHaveBeenCalledTimes(1);
+ expect(firstHandler).not.toHaveBeenCalled();
+ });
+
+ it("should handle navigation instance change", () => {
+ const firstGoBack = jest.fn();
+ const secondGoBack = jest.fn();
+
+ mockUseNavigation.mockReturnValueOnce({ goBack: firstGoBack });
+
+ const { rerender } = render( );
+
+ act(() => {
+ if (backPressHandler) {
+ backPressHandler();
+ }
+ });
+
+ expect(firstGoBack).toHaveBeenCalledTimes(1);
+
+ mockUseNavigation.mockReturnValueOnce({ goBack: secondGoBack });
+
+ rerender( );
+
+ act(() => {
+ if (backPressHandler) {
+ backPressHandler();
+ }
+ });
+
+ expect(secondGoBack).toHaveBeenCalledTimes(1);
+ });
+});
diff --git a/src/common/hooks/useScreenAnalytics.js b/src/common/hooks/useScreenAnalytics.js
deleted file mode 100644
index 2569f3f6..00000000
--- a/src/common/hooks/useScreenAnalytics.js
+++ /dev/null
@@ -1,10 +0,0 @@
-import { useEffect } from "react";
-import { trackScreenView } from "../firebase/analytics";
-
-const useScreenAnalytics = (screenName, screenClass) => {
- useEffect(() => {
- trackScreenView(screenName, screenClass);
- }, [screenName]);
-};
-
-export default useScreenAnalytics;
diff --git a/src/common/hooks/useThemedStyles.js b/src/common/hooks/useThemedStyles.js
new file mode 100644
index 00000000..9e78ca9e
--- /dev/null
+++ b/src/common/hooks/useThemedStyles.js
@@ -0,0 +1,9 @@
+import { useMemo } from "react";
+import { StyleSheet } from "react-native";
+import { useTheme } from "../context/ThemeContext";
+
+export default function useThemedStyles(create) {
+ const { theme } = useTheme();
+ // create(theme) should return a plain object of style rules
+ return useMemo(() => StyleSheet.create(create(theme)), [theme]);
+}
diff --git a/src/common/icons/ArrowRightIcon.jsx b/src/common/icons/ArrowRightIcon.jsx
new file mode 100644
index 00000000..95208317
--- /dev/null
+++ b/src/common/icons/ArrowRightIcon.jsx
@@ -0,0 +1,28 @@
+import React from "react";
+import { Svg, Path } from "react-native-svg";
+import PropTypes from "prop-types";
+
+const ArrowRightIcon = ({ size = 24, color }) => {
+ return (
+
+
+
+ );
+};
+
+ArrowRightIcon.propTypes = {
+ size: PropTypes.number.isRequired,
+ color: PropTypes.string.isRequired,
+};
+
+export default ArrowRightIcon;
diff --git a/src/common/icons/BackArrowIcon.jsx b/src/common/icons/BackArrowIcon.jsx
new file mode 100644
index 00000000..c6c5ea61
--- /dev/null
+++ b/src/common/icons/BackArrowIcon.jsx
@@ -0,0 +1,27 @@
+import React from "react";
+import Svg, { Path } from "react-native-svg";
+import PropTypes from "prop-types";
+import { colors } from "@common";
+
+const BackArrowIcon = ({ size = 30, color = colors.READER_HEADER_COLOR }) => (
+
+
+
+);
+
+BackArrowIcon.propTypes = {
+ size: PropTypes.number.isRequired,
+ color: PropTypes.string.isRequired,
+};
+
+export default BackArrowIcon;
diff --git a/src/common/icons/BookmarkIcon.jsx b/src/common/icons/BookmarkIcon.jsx
new file mode 100644
index 00000000..e192fec1
--- /dev/null
+++ b/src/common/icons/BookmarkIcon.jsx
@@ -0,0 +1,27 @@
+import React from "react";
+import Svg, { Path } from "react-native-svg";
+import PropTypes from "prop-types";
+import { colors } from "@common";
+
+const BookmarkIcon = ({ size = 24, color = colors.WHITE }) => (
+
+
+
+);
+
+BookmarkIcon.propTypes = {
+ size: PropTypes.number.isRequired,
+ color: PropTypes.string.isRequired,
+};
+
+export default BookmarkIcon;
diff --git a/src/common/icons/ChevronDownIcon.jsx b/src/common/icons/ChevronDownIcon.jsx
new file mode 100644
index 00000000..84299cd3
--- /dev/null
+++ b/src/common/icons/ChevronDownIcon.jsx
@@ -0,0 +1,34 @@
+import React from "react";
+import Svg, { Path } from "react-native-svg";
+import PropTypes from "prop-types";
+import { constant } from "@common";
+
+const ChevronDownIcon = ({ size = 24, color = constant.READER_HEADER_COLOR }) => {
+ return (
+
+
+
+ );
+};
+
+export default ChevronDownIcon;
+
+ChevronDownIcon.propTypes = {
+ size: PropTypes.number,
+ color: PropTypes.string,
+};
+
+ChevronDownIcon.defaultProps = {
+ size: 24,
+ color: constant.READER_HEADER_COLOR,
+};
diff --git a/src/common/icons/ChevronRight.jsx b/src/common/icons/ChevronRight.jsx
new file mode 100644
index 00000000..70b96e35
--- /dev/null
+++ b/src/common/icons/ChevronRight.jsx
@@ -0,0 +1,34 @@
+import React from "react";
+import { Svg, Path } from "react-native-svg";
+import PropTypes from "prop-types";
+import constant from "../constant";
+
+const ChevronRight = ({ size = 24, color = constant.READER_HEADER_COLOR }) => {
+ return (
+
+
+
+ );
+};
+
+export default ChevronRight;
+
+ChevronRight.defaultProps = {
+ size: 24,
+ color: constant.READER_HEADER_COLOR,
+};
+
+ChevronRight.propTypes = {
+ size: PropTypes.number,
+ color: PropTypes.string,
+};
diff --git a/src/common/icons/CircleIcon.jsx b/src/common/icons/CircleIcon.jsx
new file mode 100644
index 00000000..51ac12eb
--- /dev/null
+++ b/src/common/icons/CircleIcon.jsx
@@ -0,0 +1,27 @@
+import React from "react";
+import Svg, { Path } from "react-native-svg";
+import PropTypes from "prop-types";
+import { colors } from "@common";
+
+const CircleIcon = ({ size = 24, color = colors.READER_HEADER_COLOR }) => (
+
+
+
+);
+
+CircleIcon.propTypes = {
+ size: PropTypes.number.isRequired,
+ color: PropTypes.string.isRequired,
+};
+
+export default CircleIcon;
diff --git a/src/common/icons/CloseIcon.jsx b/src/common/icons/CloseIcon.jsx
new file mode 100644
index 00000000..13a42587
--- /dev/null
+++ b/src/common/icons/CloseIcon.jsx
@@ -0,0 +1,29 @@
+import React from "react";
+import Svg, { Path } from "react-native-svg";
+import PropTypes from "prop-types";
+import { colors } from "@common";
+
+const CloseIcon = ({ size = 24, color = colors.READER_HEADER_COLOR }) => {
+ return (
+
+
+
+ );
+};
+
+CloseIcon.propTypes = {
+ size: PropTypes.number.isRequired,
+ color: PropTypes.string.isRequired,
+};
+
+export default CloseIcon;
diff --git a/src/common/icons/DownloadIcon.jsx b/src/common/icons/DownloadIcon.jsx
new file mode 100644
index 00000000..1cc688d7
--- /dev/null
+++ b/src/common/icons/DownloadIcon.jsx
@@ -0,0 +1,32 @@
+import React from "react";
+import Svg, { Path } from "react-native-svg";
+import PropTypes from "prop-types";
+import { colors } from "@common";
+
+export const DownloadIcon = ({
+ size = 28,
+ color = colors.READER_HEADER_COLOR,
+ strokeWidth = 1,
+}) => (
+
+
+
+);
+
+DownloadIcon.propTypes = {
+ size: PropTypes.number.isRequired,
+ color: PropTypes.string.isRequired,
+ strokeWidth: PropTypes.number.isRequired,
+};
+
+export default DownloadIcon;
diff --git a/src/common/icons/ExpandCollapseIcon.jsx b/src/common/icons/ExpandCollapseIcon.jsx
new file mode 100644
index 00000000..a3056bbc
--- /dev/null
+++ b/src/common/icons/ExpandCollapseIcon.jsx
@@ -0,0 +1,38 @@
+import React from "react";
+import Svg, { Path } from "react-native-svg";
+import PropTypes from "prop-types";
+import { colors } from "@common";
+
+export const ResizeIcon = ({
+ size = 28,
+ color = colors.READER_HEADER_COLOR,
+ strokeWidth = 1.5,
+}) => (
+
+
+
+);
+
+ResizeIcon.propTypes = {
+ size: PropTypes.number,
+ color: PropTypes.string,
+ strokeWidth: PropTypes.number,
+};
+
+ResizeIcon.defaultProps = {
+ size: 28,
+ color: colors.READER_HEADER_COLOR,
+ strokeWidth: 2,
+};
+
+export default ResizeIcon;
diff --git a/src/common/icons/HomeIcon.jsx b/src/common/icons/HomeIcon.jsx
new file mode 100644
index 00000000..48fcffe2
--- /dev/null
+++ b/src/common/icons/HomeIcon.jsx
@@ -0,0 +1,28 @@
+import React from "react";
+import Svg, { Path } from "react-native-svg";
+import PropTypes from "prop-types";
+import { colors } from "@common";
+
+const HomeIcon = ({ size = 24, color = colors.WHITE }) => (
+
+
+
+
+);
+
+HomeIcon.propTypes = {
+ size: PropTypes.number.isRequired,
+ color: PropTypes.string.isRequired,
+};
+
+export default HomeIcon;
diff --git a/src/common/icons/MusicIcon.jsx b/src/common/icons/MusicIcon.jsx
new file mode 100644
index 00000000..c894ee2a
--- /dev/null
+++ b/src/common/icons/MusicIcon.jsx
@@ -0,0 +1,27 @@
+import React from "react";
+import Svg, { Path, Circle } from "react-native-svg";
+import PropTypes from "prop-types";
+import { colors } from "@common";
+
+const MusicIcon = ({ size = 24, color = colors.WHITE, isActive = false }) => (
+
+
+
+
+
+);
+
+MusicIcon.propTypes = {
+ size: PropTypes.number.isRequired,
+ color: PropTypes.string.isRequired,
+ isActive: PropTypes.bool.isRequired,
+};
+
+export default MusicIcon;
diff --git a/src/common/icons/MusicNoteIcon.jsx b/src/common/icons/MusicNoteIcon.jsx
new file mode 100644
index 00000000..49ff3d66
--- /dev/null
+++ b/src/common/icons/MusicNoteIcon.jsx
@@ -0,0 +1,29 @@
+import React from "react";
+import Svg, { Path } from "react-native-svg";
+import PropTypes from "prop-types";
+import { colors } from "@common";
+
+export const MusicIcon = ({ size = 28, color = colors.READER_HEADER_COLOR, strokeWidth = 1.5 }) => (
+
+
+
+
+);
+
+MusicIcon.propTypes = {
+ size: PropTypes.number.isRequired,
+ color: PropTypes.string.isRequired,
+ strokeWidth: PropTypes.number.isRequired,
+};
+
+export default MusicIcon;
diff --git a/src/common/icons/PauseIcon.jsx b/src/common/icons/PauseIcon.jsx
new file mode 100644
index 00000000..03162bd1
--- /dev/null
+++ b/src/common/icons/PauseIcon.jsx
@@ -0,0 +1,27 @@
+import React from "react";
+import Svg, { Path } from "react-native-svg";
+import PropTypes from "prop-types";
+import { colors } from "@common";
+
+const PauseIcon = ({ size = 24, color = colors.READER_HEADER_COLOR }) => (
+
+
+
+);
+
+PauseIcon.propTypes = {
+ size: PropTypes.number.isRequired,
+ color: PropTypes.string.isRequired,
+};
+
+export default PauseIcon;
diff --git a/src/common/icons/PlayIcon.jsx b/src/common/icons/PlayIcon.jsx
new file mode 100644
index 00000000..1ad6fa7d
--- /dev/null
+++ b/src/common/icons/PlayIcon.jsx
@@ -0,0 +1,28 @@
+import React from "react";
+import Svg, { Path } from "react-native-svg";
+import PropTypes from "prop-types";
+
+const PlayIcon = ({ size = 24, color }) => {
+ return (
+
+
+
+ );
+};
+
+PlayIcon.propTypes = {
+ size: PropTypes.number.isRequired,
+ color: PropTypes.string.isRequired,
+};
+
+export default PlayIcon;
diff --git a/src/common/icons/ReadIcon.jsx b/src/common/icons/ReadIcon.jsx
new file mode 100644
index 00000000..8c614c5c
--- /dev/null
+++ b/src/common/icons/ReadIcon.jsx
@@ -0,0 +1,32 @@
+import React from "react";
+import Svg, { Path } from "react-native-svg";
+import PropTypes from "prop-types";
+import { colors } from "@common";
+
+const ReadIcon = ({ size = 24, color = colors.READER_HEADER_COLOR }) => (
+
+
+
+);
+
+export default ReadIcon;
+
+ReadIcon.defaultProps = {
+ size: 24,
+ color: colors.READER_HEADER_COLOR,
+};
+
+ReadIcon.propTypes = {
+ size: PropTypes.number,
+ color: PropTypes.string,
+};
diff --git a/src/common/icons/RefreshIcon.jsx b/src/common/icons/RefreshIcon.jsx
new file mode 100644
index 00000000..cd0989f7
--- /dev/null
+++ b/src/common/icons/RefreshIcon.jsx
@@ -0,0 +1,27 @@
+import React from "react";
+import Svg, { Path } from "react-native-svg";
+import PropTypes from "prop-types";
+
+const RefreshIcon = ({ size = 30, color }) => (
+
+
+
+
+
+);
+
+RefreshIcon.propTypes = {
+ size: PropTypes.number.isRequired,
+ color: PropTypes.string.isRequired,
+};
+export default RefreshIcon;
diff --git a/src/common/icons/SettingsIcon.jsx b/src/common/icons/SettingsIcon.jsx
new file mode 100644
index 00000000..06baaf08
--- /dev/null
+++ b/src/common/icons/SettingsIcon.jsx
@@ -0,0 +1,35 @@
+import React from "react";
+import Svg, { Path } from "react-native-svg";
+import PropTypes from "prop-types";
+import { colors } from "@common";
+
+export const SettingsIcon = ({
+ size = 28,
+ color = colors.READER_HEADER_COLOR,
+ strokeWidth = 1.5,
+}) => {
+ return (
+
+
+
+
+ );
+};
+
+SettingsIcon.propTypes = {
+ size: PropTypes.number.isRequired,
+ color: PropTypes.string.isRequired,
+ strokeWidth: PropTypes.number.isRequired,
+};
+
+export default SettingsIcon;
diff --git a/src/common/icons/StopIcon.jsx b/src/common/icons/StopIcon.jsx
new file mode 100644
index 00000000..4fab5cd8
--- /dev/null
+++ b/src/common/icons/StopIcon.jsx
@@ -0,0 +1,27 @@
+import React from "react";
+import Svg, { Path } from "react-native-svg";
+import PropTypes from "prop-types";
+import { colors } from "@common";
+
+const StopIcon = ({ size = 24, color = colors.WHITE }) => (
+
+
+
+);
+
+StopIcon.propTypes = {
+ size: PropTypes.number.isRequired,
+ color: PropTypes.string.isRequired,
+};
+
+export default StopIcon;
diff --git a/src/common/icons/index.js b/src/common/icons/index.js
new file mode 100644
index 00000000..ff8de26e
--- /dev/null
+++ b/src/common/icons/index.js
@@ -0,0 +1,20 @@
+export { default as MusicNoteIcon } from "./MusicNoteIcon";
+export { default as SettingsIcon } from "./SettingsIcon";
+export { default as ExpandCollapseIcon } from "./ExpandCollapseIcon";
+export { default as CloseIcon } from "./CloseIcon";
+export { default as PlayIcon } from "./PlayIcon";
+export { default as DownloadIcon } from "./DownloadIcon";
+export { default as ArrowRightIcon } from "./ArrowRightIcon";
+export { default as CircleIcon } from "./CircleIcon";
+export { default as ChevronRight } from "./ChevronRight";
+export { default as PauseIcon } from "./PauseIcon";
+export { default as HomeIcon } from "./HomeIcon";
+export { default as MusicIcon } from "./MusicIcon";
+export { default as BookmarkIcon } from "./BookmarkIcon";
+export { default as BackArrowIcon } from "./BackArrowIcon";
+export { default as StopIcon } from "./StopIcon";
+export { default as PlusIcon } from "./plusIcon";
+export { default as MinusIcon } from "./minusIcon";
+export { default as ReadIcon } from "./ReadIcon";
+export { default as RefreshIcon } from "./RefreshIcon";
+export { default as ChevronDownIcon } from "./ChevronDownIcon";
diff --git a/src/common/icons/minusIcon.jsx b/src/common/icons/minusIcon.jsx
new file mode 100644
index 00000000..16d89e53
--- /dev/null
+++ b/src/common/icons/minusIcon.jsx
@@ -0,0 +1,28 @@
+import React from "react";
+import Svg, { Path } from "react-native-svg";
+import PropTypes from "prop-types";
+
+const MinusIcon = ({ size = 24, color }) => {
+ return (
+
+
+
+ );
+};
+
+MinusIcon.propTypes = {
+ size: PropTypes.number.isRequired,
+ color: PropTypes.string.isRequired,
+};
+
+export default MinusIcon;
diff --git a/src/common/icons/plusIcon.jsx b/src/common/icons/plusIcon.jsx
new file mode 100644
index 00000000..fa9a6aee
--- /dev/null
+++ b/src/common/icons/plusIcon.jsx
@@ -0,0 +1,28 @@
+import React from "react";
+import Svg, { Path } from "react-native-svg";
+import PropTypes from "prop-types";
+
+const PlusIcon = ({ size = 24, color }) => {
+ return (
+
+
+
+ );
+};
+
+PlusIcon.propTypes = {
+ size: PropTypes.number.isRequired,
+ color: PropTypes.string.isRequired,
+};
+
+export default PlusIcon;
diff --git a/src/common/index.js b/src/common/index.js
index 0613bd60..6a678a28 100644
--- a/src/common/index.js
+++ b/src/common/index.js
@@ -1,35 +1,46 @@
-import constant from "./constant";
-import colors from "./colors";
import * as actions from "./actions";
-import STRINGS from "./localization";
-import useScreenAnalytics from "./hooks/useScreenAnalytics";
-import { logError, initializeCrashlytics, setCustomKey, logMessage } from "./firebase/crashlytics";
+import colors from "./colors";
+import {
+ FallBack,
+ BaniLengthSelector,
+ BaniList,
+ CustomText,
+ ListItemTitle,
+ BottomNavigation,
+ SafeArea,
+ StatusBarComponent,
+} from "./components";
+import orderedBani from "./components/BaniList/baniOrderHelper";
+import constant from "./constant";
+import useTheme from "./context";
+import defaultBaniOrder from "./defaultBaniOrder";
import {
allowTracking,
- trackScreenView,
trackReaderEvent,
trackSettingEvent,
trackReminderEvent,
+ trackAudioEvent,
+ trackArtistListeningDuration,
+ trackArtist,
} from "./firebase/analytics";
+import { logError, initializeCrashlytics, setCustomKey, logMessage } from "./firebase/crashlytics";
import {
initializePerformanceMonitoring,
startPerformanceTrace,
stopTrace,
resetTrace,
} from "./firebase/performance";
+import baseFontSize, { validateBaniOrder } from "./helpers";
+import useKeepAwake from "./hooks/keepAwake";
+import useBackHandler from "./hooks/useBackHandler";
+import useThemedStyles from "./hooks/useThemedStyles";
+import STRINGS from "./localization";
import {
updateReminders,
cancelAllReminders,
checkPermissions,
resetBadgeCount,
} from "./notifications";
-import { FallBack, BaniLengthSelector, BaniList } from "./components";
-import useKeepAwake from "./hooks/keepAwake";
-import baseFontSize, { validateBaniOrder } from "./helpers";
-import { navigate, navigateTo, navigationRef } from "./rootNavigation";
-import orderedBani from "./components/BaniList/baniOrderHelper";
-import defaultBaniOrder from "./defaultBaniOrder";
-import createStore from "./store";
import {
ensureDbExists,
checkForBaniDBUpdate,
@@ -40,21 +51,22 @@ import {
revertMD5Hash,
getCurrentDBMD5Hash,
} from "./rnfs";
-import StatusBarComponent from "./components/StatusBar";
-import SafeArea from "./components/SafeArea";
+import { navigate, navigateTo, navigationRef } from "./rootNavigation";
+import createStore from "./store";
+import { showToast, showErrorToast, showSuccessToast, showInfoToast } from "./toast";
+import convertToUnicode from "./utils";
export {
colors,
constant,
actions,
- useScreenAnalytics,
STRINGS,
logError,
logMessage,
initializeCrashlytics,
allowTracking,
trackReaderEvent,
- trackScreenView,
+ trackAudioEvent,
trackReminderEvent,
trackSettingEvent,
updateReminders,
@@ -64,6 +76,7 @@ export {
BaniLengthSelector,
useKeepAwake,
BaniList,
+ CustomText,
baseFontSize,
resetBadgeCount,
createStore,
@@ -88,4 +101,16 @@ export {
getCurrentDBMD5Hash,
StatusBarComponent,
SafeArea,
+ showToast,
+ showErrorToast,
+ showSuccessToast,
+ showInfoToast,
+ convertToUnicode,
+ BottomNavigation,
+ useTheme,
+ useThemedStyles,
+ ListItemTitle,
+ useBackHandler,
+ trackArtistListeningDuration,
+ trackArtist,
};
diff --git a/src/common/localization.js b/src/common/localization.js
index fbed64ef..e46ec2d4 100644
--- a/src/common/localization.js
+++ b/src/common/localization.js
@@ -14,6 +14,10 @@ const STRINGS = new LocalizedStrings({
anmol_lipi: "Anmol Lipi",
APP_VERSION: "App Version",
AUTO_SCROLL: "Auto Scroll",
+ AUDIO: "Audio Player",
+ AUDIO_AUTO_PLAY: "Audio Auto Play",
+ AUDIO_SYNC_SCROLL: "Sync Scroll with Audio",
+ DEFAULT_AUDIO: "Default Audio",
bani_length: "Bani Length",
bani_length_alert_1:
"Throughout the past few centuries, there have been many different โsampardhasโ or โjathasโ that have been conceived from the core concepts of Sikhi and Gurmat. These sampardhas often have different opinions and thoughts about some aspects of Sikh history, Gurbani and Rehat, but still fall collectively under the united Khalsa Panth and, most importantly, the Akaal Takht. The Akaal Takht is the highest order and institution that all Sikhs adhere to.",
@@ -22,7 +26,7 @@ const STRINGS = new LocalizedStrings({
bani_length_alert_3:
"We therefore have created the option to select lengths of what Paath you do that apply to four of the main Banis read most often. These have been structured in relation to length but all have a minimum of the SGPC standard or fall under the Akaal Takht. We do not include any versions that are by sampardhas excommunicated by the Akaal Takht as a standard.",
bani_length_alert_4:
- "For those who may be confused about which version to begin reading these Banis, we recommend reading the longest Bani because the more we read and recite, the better it is for our souls. However, for beginners, we would suggest to start with the โshortโ setting and change to increase the length setting in future once you are comfortable, confident and have more time.",
+ "For those who may be confused about which version to begin reading these Banis, we recommend reading the longest Bani because the more we read and recite, the better it is for our souls. However, for beginners, we would suggest to start with the short setting and change to increase the length setting in future once you are comfortable, confident and have more time.",
bani_length_alert_5:
"Here is a breakdown of the lengths and which sampardhas typically use them:-",
bani_length_alert_6: "SHORT: This is the minimum SGPC/Akaal Takht standard.",
@@ -66,6 +70,7 @@ const STRINGS = new LocalizedStrings({
gurbani_akhar_default: "Gurbani Akhar (default)",
gurbani_akhar_heavy: "Gurbani Akhar Heavy",
gurbani_akhar_think: "Gurbani Akhar Thick",
+ baloo_paaji: "Baloo Paaji",
HIDE_STATUS_BAR: "Hide Status Bar",
hindi: "Hindi",
iGurbani: "iGurbani",
@@ -142,6 +147,36 @@ const STRINGS = new LocalizedStrings({
checkForUpdate: "Checking for BaniDB updates...",
upToDate: "Your BaniDB is up to date.",
baniDBBannerText: "A new BaniDB update is available. Tap to update now.",
+ MORE_TRACKS: "Audios",
+ AUDIO_SETTINGS: "Options",
+ info: "info",
+ welcome_to_sundar_gutka: "Welcome to Sundar Gutka Audio",
+ please_choose_a_track: "Please choose a track for",
+ PLAY: "Play",
+ DOWNLOAD: "Download",
+ DOWNLOADING: "Downloading",
+ NEXT: "Next",
+ HOME: "Home",
+ READ: "Read",
+ MUSIC: "Audio",
+ SETTINGS: "Settings",
+ PLAYBACK_SPEED: "Playback Speed",
+ UNABLE_TO_PLAY: "Unable to play audio.",
+ PLEASE_TRY_AGAIN: "Please try again.",
+ UNABLE_TO_SEEK: "Unable to seek audio.",
+ UNABLE_TO_SWITCH_TRACK: "Unable to switch audio track.",
+ PREPARING_AUDIO_PLAYER: "Preparing audio player...",
+ AUDIO_SYNC_UNAVAILABLE: "Audio sync unavailable. Playback will continue without auto-scroll.",
+ NETWORK_ERROR: "Network error. Audio features temporarily unavailable.",
+ COULD_NOT_LOAD_AUDIO_ARTISTS: "Could not load audio artists. Please check your connection.",
+ PERMISSION_ERROR: "Broadcast permission check failed โ missing or denied permission",
+ MAAFI_JI: "Maafi ji ๐๐ฝ",
+ WE_DO_NOT_HAVE_AUDIOS_FOR: "We do not have audios for",
+ REQUEST_AUDIO_FOR_THIS_PAATH: "Request audio for this paath.",
+ YET: "yet.",
+ SYNC_UNAVAILABLE: "Unavailable",
+ RETRY: "Please try again.",
+ INITIALIZING_AUDIO_PLAYER: "Weโre unable to start the Gurbani audio right now.",
},
hi: {
about: "เคนเคฎเคพเคฐเฅ เคฌเคพเคฐเฅ",
@@ -155,6 +190,10 @@ const STRINGS = new LocalizedStrings({
anmol_lipi: "เค
เคจเคฎเฅเคฒ เคฒเคฟเคชเคฟ",
APP_VERSION: "เคเคชเฅเคช เคตเคฐเฅเคเคจ",
AUTO_SCROLL: "เคเคเฅ เคธเฅเคเฅเคฐเฅเคฒ",
+ AUDIO: "เคเคกเคฟเคฏเฅ เคชเฅเคฒเฅเคฏเคฐ",
+ AUDIO_AUTO_PLAY: "เคเคกเคฟเคฏเฅ เคเคเฅ เคชเฅเคฒเฅ",
+ AUDIO_SYNC_SCROLL: "เคเคกเคฟเคฏเฅ เคเฅ เคธเคพเคฅ เคธเฅเคเฅเคฐเฅเคฒ เคธเคฟเคเค เคเคฐเฅเค",
+ DEFAULT_AUDIO: "เคกเคฟเคซเคผเฅเคฒเฅเค เคเคกเคฟเคฏเฅ",
bani_length: "เคฌเคพเคฃเฅ เคฒเคเคฌเคพเค",
bani_length_alert_1:
"เคชเคฟเคเคฒเฅ เคเฅเค เคธเคฆเคฟเคฏเฅเค เคฎเฅเค, เคเค เคธเคฎเฅเคชเฅเคฐเคฆเคพเคฏเฅเค เคเคฐ เคธเคเคเค เคจ เคชเฅเคฆเคพ เคนเฅเค เคนเฅเค, เคเฅ เคธเคฟเค เคธเคฟเคฆเฅเคงเคพเคเคค เคเคฐ เคเฅเคฐเฅเคฎเคค เคชเคฐ เคเฅเคเคฆเฅเคฐเคฟเคค เคนเฅเคเฅค เคนเคพเคฒเคพเคเคเคฟ เคเคจ เคธเคเคชเฅเคฐเคฆเคพเคฏเฅเค เคฎเฅเค เคธเคฟเค เคเคคเคฟเคนเคพเคธ, เคเฅเคฐเคฌเคพเคจเฅ เคเคฐ เค
เคจเฅเคชเคพเคฒเคจ เคชเคฐ เคฎเคคเคญเฅเคฆ เคฐเคนเฅ เคนเฅเค, เคชเคฐ เคตเฅ เคชเฅเคฐเฅเคฃ เคฐเฅเคช เคธเฅ เคเคพเคฒเคธเคพ เคชเคเคฅ เคเคพ เคนเคฟเคธเฅเคธเคพ เคฌเคจเฅ เคนเฅเค เคนเฅเค เคเคฐ เคเคเฅเคเคคเคฎ เค
เคเคพเคฒ เคคเคเฅเคค เคเฅ เคฌเฅเคจเคฐ เคคเคฒเฅ เคนเฅ เคฐเคน เคฐเคนเฅ เคนเฅเคเฅค",
@@ -206,6 +245,7 @@ const STRINGS = new LocalizedStrings({
gurbani_akhar_default: "เคเฅเคฐเคฌเคพเคฃเฅ เค
เคเฅเคทเคฐ (เคฎเฅเคฒ)",
gurbani_akhar_heavy: "เคเฅเคฐเคฌเคพเคฃเฅ เค
เคเฅเคทเคฐ เคญเคพเคฐเฅ ",
gurbani_akhar_think: "เคเฅเคฐเคฌเคพเคฃเฅ เค
เคเฅเคทเคฐ เคฎเฅเคเฅ",
+ baloo_paaji: "เคฌเคพเคฒเฅ เคชเคพเคเฅ",
HIDE_STATUS_BAR: "เคธเฅเคเฅเคเคธ เคฌเคพเคฐ เคเฅ เคเคฟเคชเคพ เคฆเฅเค",
hindi: "เคนเคฟเคเคฆเฅ",
iGurbani: "iGurbani",
@@ -282,6 +322,37 @@ const STRINGS = new LocalizedStrings({
checkForUpdate: "BaniDB เค
เคฆเฅเคฏเคคเคจ เคเฅ เคเคพเคเค เคเฅ เคเคพ เคฐเคนเฅ เคนเฅ...",
upToDate: "เคเคชเคเคพ BaniDB เค
เคฆเฅเคฏเคคเคฟเคค เคนเฅเฅค",
baniDBBannerText: "เคจเคฏเคพ BaniDB เค
เคชเคกเฅเค เคเคชเคฒเคฌเฅเคง เคนเฅเฅค เค
เคญเฅ เค
เคชเคกเฅเค เคเคฐเคจเฅ เคเฅ เคฒเคฟเค เคเฅเคช เคเคฐเฅเคเฅค",
+ MORE_TRACKS: "เคเคกเคฟเคฏเฅ",
+ AUDIO_SETTINGS: "เคตเคฟเคเคฒเฅเคช",
+ info: "เคเคพเคจเคเคพเคฐเฅ",
+ welcome_to_sundar_gutka: "เคธเฅเคเคฆเคฐ เคเฅเคเคเคพ เคเคกเคฟเคฏเฅ เคฎเฅเค เคเคชเคเคพ เคธเฅเคตเคพเคเคค เคนเฅ",
+ please_choose_a_track: "เคเฅเคชเคฏเคพ เคเค เคเฅเคฐเฅเค เคเฅเคจเฅเค",
+ PLAY: "เคเคฒเคพเคเค",
+ DOWNLOAD: "เคกเคพเคเคจเคฒเฅเคก",
+ DOWNLOADING: "เคกเคพเคเคจเคฒเฅเคก เคนเฅ เคฐเคนเคพ เคนเฅ",
+ NEXT: "เค
เคเคฒเคพ",
+ HOME: "เคนเฅเคฎ",
+ READ: "เคชเคขเคผเฅเค",
+ MUSIC: "เคเคกเคฟเคฏเฅ",
+ SETTINGS: "เคธเฅเคเคฟเคเคเฅเคธ",
+ PLAYBACK_SPEED: "เคชเฅเคฒเฅเคฌเฅเค เคธเฅเคชเฅเคก",
+ UNABLE_TO_PLAY: "เคเคกเคฟเคฏเฅ เคเคฒเคพเคจเฅ เคฎเฅเค เค
เคธเคฎเคฐเฅเคฅ",
+ PLEASE_TRY_AGAIN: "เคเฅเคชเคฏเคพ เคชเฅเคจเค เคชเฅเคฐเคฏเคพเคธ เคเคฐเฅเคเฅค",
+ UNABLE_TO_SEEK: "เคเคกเคฟเคฏเฅ เคเฅเคเคจเฅ เคฎเฅเค เค
เคธเคฎเคฐเฅเคฅ",
+ UNABLE_TO_SWITCH_TRACK: "เคเคกเคฟเคฏเฅ เคเฅเคฐเฅเค เคฌเคฆเคฒเคจเฅ เคฎเฅเค เค
เคธเคฎเคฐเฅเคฅ",
+ PREPARING_AUDIO_PLAYER: "เคเคกเคฟเคฏเฅ เคชเฅเคฒเฅเคฏเคฐ เคคเฅเคฏเคพเคฐ เคเคฟเคฏเคพ เคเคพ เคฐเคนเคพ เคนเฅ...",
+ AUDIO_SYNC_UNAVAILABLE: "เคเคกเคฟเคฏเฅ เคธเคฟเคเค เค
เคธเฅเคตเฅเคเฅเคค เคนเฅเฅค เคเคกเคฟเคฏเฅ เคเคเฅ เคธเฅเคเฅเคฐเฅเคฒ เคเฅ เคฌเคฟเคจเคพ เคเคฒเคพเคฏเคพ เคเคพเคเคเคพเฅค",
+ NETWORK_ERROR: "เคจเฅเคเคตเคฐเฅเค เคคเฅเคฐเฅเคเคฟเฅค เคเคกเคฟเคฏเฅ เคธเฅเคตเคฟเคงเคพเคเค เคเฅ เค
เคธเฅเคฅเคพเคฏเฅ เคฐเฅเคช เคธเฅ เค
เคธเฅเคตเฅเคเฅเคค เคเคฐ เคฆเคฟเคฏเคพ เคเคพเคเคเคพเฅค",
+ COULD_NOT_LOAD_AUDIO_ARTISTS:
+ "เคเคกเคฟเคฏเฅ เคเคฒเคพเคเคพเคฐเฅเค เคเฅ เคฒเฅเคก เคเคฐเคจเฅ เคฎเฅเค เค
เคธเคฎเคฐเฅเคฅเฅค เคเฅเคชเคฏเคพ เค
เคชเคจเคพ เคเคจเฅเคเฅเคถเคจ เคเคพเคเคเฅเคเฅค",
+ PERMISSION_ERROR: "เคชเฅเคฐเคธเคพเคฐเคฃ เค
เคจเฅเคฎเคคเคฟ เคเคพเคเค เคตเคฟเคซเคฒ - เค
เคจเฅเคฎเคคเคฟ เค
เคจเฅเคชเคฒเคฌเฅเคง เคฏเคพ เค
เคธเฅเคตเฅเคเฅเคค",
+ MAAFI_JI: "เคเฅเคทเคฎเคพ เคเคฐเฅเค เคเฅ ๐๐ฝ",
+ WE_DO_NOT_HAVE_AUDIOS_FOR: "เคนเคฎเคพเคฐเฅ เคชเคพเคธ เคเคกเคฟเคฏเฅ เคจเคนเฅเค เคนเฅ",
+ REQUEST_AUDIO_FOR_THIS_PAATH: "เคเคธ เคชเคพเค เคเฅ เคฒเคฟเค เคเคกเคฟเคฏเฅ เคเคพ เค
เคจเฅเคฐเฅเคง เคเคฐเฅเค",
+ YET: "เค
เคญเฅ เคคเคเฅค",
+ SYNC_UNAVAILABLE: "เค
เคจเฅเคชเคฒเคฌเฅเคง",
+ RETRY: "เคชเฅเคจเค เคชเฅเคฐเคฏเคพเคธ เคเคฐเฅเค",
+ INITIALIZING_AUDIO_PLAYER: "เคนเคฎ เค
เคญเฅ เคเฅเคฐเคฌเคพเคจเฅ เคเคกเคฟเคฏเฅ เคถเฅเคฐเฅ เคจเคนเฅเค เคเคฐ เคชเคพ เคฐเคนเฅ เคนเฅเคเฅค",
},
pa: {
@@ -297,6 +368,10 @@ const STRINGS = new LocalizedStrings({
anmol_lipi: "เจ
เจจเจฎเฉเจฒ เจฒเจฟเจชเฉ",
APP_VERSION: "เจเจช เจตเจฐเฉเจจ",
AUTO_SCROLL: "เจเจเฉ เจธเจเจฐเฉเจฒ",
+ AUDIO: "เจเจกเฉเจ เจชเจฒเฉเจ
เจฐ",
+ AUDIO_AUTO_PLAY: "เจเจกเฉเจ เจเจเฉ เจชเจฒเฉ",
+ AUDIO_SYNC_SCROLL: "เจเจกเฉเจ เจจเจพเจฒ เจธเจเจฐเฉเจฒ เจธเจฟเฉฐเจ เจเจฐเฉ",
+ DEFAULT_AUDIO: "เจกเจฟเจซเจพเจฒเจ เจเจกเฉเจ",
bani_length: "เจฌเจพเจฃเฉ เจฒเฉฐเจฌเจพเจ",
bani_length_alert_1:
"เจฌเฉเจคเฉเจเจ เจเฉเจ เจธเจฆเฉเจเจ เจฆเฉเจฐเจพเจจ เจ
เจจเฉเจเจพเจ เจ
เจเจฟเจนเฉเจเจ เจธเฉฐเจชเจฐเจฆเจพเจตเจพเจ เจคเฉ เจเฉฑเจฅเฉเจฌเฉฐเจฆเฉเจเจ เจชเฉเจฆเจพ เจนเฉเจเจเจ เจเจฟเจจเฉเจนเจพเจ เจฆเจพ เจงเฉเจฐเจพ เจธเจฟเฉฑเจเฉ เจธเจฟเจงเจพเจเจค เจ
เจคเฉ เจเฉเจฐเจฎเจคเจฟ เจฐเจฟเจนเจพเฅค เจญเจพเจตเฉเจ เจเจนเจจเจพเจ เจธเฉฐเจชเจฐเจฆเจพเจตเจพเจ เจตเจฟเฉฑเจ เจธเจฟเฉฑเจ เจเจคเจฟเจนเจพเจธ, เจเฉเจฐเจฌเจพเจฃเฉ เจ
เจคเฉ เจฐเจนเจฟเจค เจจเฉเฉฐ เจฒเฉ เจเฉ เจเจชเจธเฉ เจฎเจคเจญเฉเจฆ เจฐเจนเฉ เจนเจจ, เจชเจฐ เจธเจฎเฉเจน เจคเฉเจฐ เจคเฉ เจเจพเจฒเจธเจพ เจชเฉฐเจฅ เจฆเฉ เจนเจฟเฉฑเจธเฉ เจตเจเฉเจ เจนเฉ เจตเจฟเจเจฐเฉเจเจ เจนเจจ เจคเฉ เจธเฉฑเจญ เจคเฉเจ เจตเฉฑเจง เจเฉ เจธเจฐเจฌ เจเฉฑเจ เจ
เจเจพเจฒ เจคเจเจค เจฆเฉ เจเฉฐเจกเฉ เจนเฉเจ เจนเฉ เจฐเจนเฉเจเจ (เจฐเจนเจฟเฉฐเจฆเฉเจเจ) เจนเจจเฅค",
@@ -305,7 +380,7 @@ const STRINGS = new LocalizedStrings({
bani_length_alert_3:
"เจเจธเฉ เจฒเจ เจ
เจธเฉเจ เจเจธ เจเจช (เจเฉเจเจค) เจตเจฟเฉฑเจ เจเจฎ เจคเฉเจฐ เจคเฉ เจเจพเจฐ เจธเฉฑเจญ เจคเฉเจ เจตเฉฑเจง เจชเฉเฉเจนเฉเจเจ เจเจพเจฃ เจตเจพเจฒเฉเจเจ เจฌเจพเจฃเฉเจเจ เจฆเฉ เจฒเฉฐเจฌเจพเจ เจเฉเจฃเจจ เจฆเฉ เจธเจนเฉเจฒเจค เจฆเจฟเฉฑเจคเฉ เจนเฉเฅค เจเจนเจจเจพเจ เจเฉเจฃเจพเจ เจจเฉเฉฐ เจฌเจพเจฃเฉ เจฆเฉ เจฒเฉฐเจฌเจพเจ เจฆเฉ เจนเจฟเจธเจพเจฌ เจจเจพเจฒ เจคเจฐเจคเฉเจฌ เจฆเจฟเฉฑเจคเฉ เจเจ เจนเฉ เจชเจฐ เจธเจพเจฐเฉเจเจ เจนเฉ เจฌเจพเจฃเฉเจเจ เจ
เจเจพเจฒ เจคเจเจค เจธเจพเจนเจฌ เจตเฉฑเจฒเฉเจนเฉเจ เจชเฉเจฐเจตเจพเจจเจค เจฎเจฟเจเจฐเจพเจ เจ
เจจเฉเจธเจพเจฐ เจนเจจเฅค เจ
เจธเฉเจ เจ
เจเจพเจฒ เจคเจเจค เจคเฉเจ เจเฉเจเฉ เจนเฉเจ เจเจฟเจธเฉ เจตเฉ เจธเฉฐเจชเจฐเจฆเจพ เจตเฉฑเจฒเฉเจนเฉเจ เจตเจฐเจคเฉ เจเจพเจเจฆเฉ เจฌเจพเจฃเฉ เจฆเฉ เจเฉเจ เจตเฉ เจตเฉฐเจจเจเฉ เจเจธ เจเฉเจเจค เจตเจฟเฉฑเจ เจจเจนเฉเจ เจชเจพเจ",
bani_length_alert_4:
- "เจเฉ เจคเฉเจธเฉเจ เจเจธ เจเจฒเจเจฃ เจตเจฟเฉฑเจ เจนเฉ เจเฉ เจเจฟเจธ เจฌเจพเจฃเฉ เจคเฉเจ เจถเฉเจฐเฉ เจเจฐเฉเจ, เจธเจพเจกเฉ เจธเจฒเจพเจน เจนเฉ เจธเจญ เจคเฉเจ เจฒเฉฐเจฌเฉ เจฌเจพเจฃเฉ เจเจฟเจเจเจเจฟ เจเจฟเฉฐเจจเจพ เฉเจฟเจเจฆเจพ เจ
เจธเฉเจ เจฌเจพเจฃเฉ เจ
เจญเจฟเจเจธ เจเจฐเจพเจเจเฉ, เจธเจพเจกเฉ เจฐเฉเจน เจฒเจ เจเฉฐเจจเจพเจ เจนเฉ เจตเจงเฉเจ เจนเฉเฅค เจเฉ เจคเฉเจธเฉเจ เจฌเจฟเจฒเจเฉเจฒ เจจเจตเฉเจ เจนเฉ เจคเจพเจ เจคเฉเจธเฉเจ เจธเจญ เจคเฉเจ เจเฉเจเฉ เจฌเจพเจฃเฉ เจคเฉเจ เจตเฉ เจถเฉเจฐเฉ เจเจฐ เจธเจเจฆเฉ เจนเฉ เจคเฉ เจเจฟเจตเฉเจ เจเจฟเจตเฉเจ เจคเฉเจนเจพเจกเจพ เจจเจพเจฎ เจ
เจญเจฟเจเจธ เจตเฉฑเจง เจฆเจพ เจนเฉ, เจคเฉเจธเฉเจ เฉเจฟเจเจฆเจพ เจฌเจพเจฃเฉ เจชเฉเฉเจน เจธเจเจฆเฉ เจเฅค",
+ "เจเฉ เจคเฉเจธเฉเจ เจเจธ เจเจฒเจเจฃ เจตเจฟเฉฑเจ เจนเฉ เจเฉ เจเจฟเจธ เจฌเจพเจฃเฉ เจคเฉเจ เจถเฉเจฐเฉ เจเจฐเฉเจ, เจธเจพเจกเฉ เจธเจฒเจพเจน เจนเฉ เจธเจญ เจคเฉเจ เจฒเฉฐเจฌเฉ เจฌเจพเจฃเฉ เจเจฟเจเจเจเจฟ เจเจฟเฉฐเจจเจพ เฉเจฟเจเจฆเจพ เจ
เจธเฉเจ เจฌเจพเจฃเฉ เจ
เจญเจฟเจเจธ เจเจฐเจพเจเจเฉ, เจธเจพเจกเฉ เจฐเฉเจน เจฒเจ เจเฉฐเจจเจพเจ เจนเฉ เจตเจงเฉเจ เจนเฉเฅค เจเฉ เจคเฉเจธเฉเจ เจฌเจฟเจฒเจเฉเจฒ เจจเจตเฉเจ เจนเฉ เจคเจพเจ เจคเฉเจธเฉเจ เจธเจญ เจคเฉเจ เจเฉเจเฉ เจฌเจพเจฃเฉ เจคเฉเจ เจตเฉ เจถเฉเจฐเฉ เจเจฐ เจธเจเจฆเฉ เจนเฉ เจคเฉ เจเจฟเจตเฉเจ เจเจฟเจตเฉเจ เจคเฉเจนเจพเจกเจพ เจจเจพเจฎ เจ
เจญเจฟเจเจธ เจตเฉฑเจง เจฆเจพ เจนเฉ, เจคเฉเจธเฉเจ เฉเจฟเจเจฆเจพ เจฌเจพเจฃเฉ เจชเฉเฉเจน เจธเจเจฆเฉ เจเฅค",
bani_length_alert_5: "เจฌเจพเจฃเฉเจเจ เจฆเฉ เจคเจฐเจคเฉเจฌ เจเจจเฉเจนเจพเจ เจฆเฉ เจฒเฉฐเจฌเจพเจ เจ
เจคเฉ เจธเฉฐเจชเจฐเจฆเจพเจตเจพเจ เจฆเฉ เจนเจฟเจธเจพเจฌ เจจเจพเจฒ เจเฉเจคเฉ เจเจ เจนเฉเฅค",
bani_length_alert_6: "เจเฉเจเฉ - เจเจน เจฌเจพเจฃเฉ เจ
เจเจพเจฒ เจคเจเจค เจธเจพเจนเจฟเจฌ เจตเจฒเฉเจนเฉ เจเฉฑเจเฉ เจเฉฑเจ เจชเฉเจฐเจตเจพเจฃเจค เจฎเจฟเจเจฐเจพเจ เจ
เจจเฉเจธเจพเจฐ เจนเฉ",
bani_length_alert_7: "เจฎเฉฑเจงเจฎ - เจ
เจเฉฐเจก เจเฉเจฐเจคเจจเฉ เจเจฅเจพ เจ
เจคเฉ เจนเฉเจฐเฅค",
@@ -347,6 +422,7 @@ const STRINGS = new LocalizedStrings({
gurbani_akhar_default: "เจเฉเจฐเจฌเจพเจฃเฉ เจ
เฉฑเจเจฐ (เจฎเฉเจฒ)",
gurbani_akhar_heavy: "เจเฉเจฐเจฌเจพเจฃเฉ เจ
เฉฑเจเจฐ เจญเจพเจฐเฉ",
gurbani_akhar_think: "เจเฉเจฐเจฌเจพเจฃเฉ เจ
เฉฑเจเจฐ เจฎเฉเจเฉ",
+ baloo_paaji: "เจฌเจพเจฒเฉ เจชเจพเจเฉ",
HIDE_STATUS_BAR: "เจธเจเฉเจเจธ เจฌเจพเจฐ เจจเฉเฉฐ เจฒเจเฉ เจฆเจฟเจ",
hindi: "เจนเจฟเฉฐเจฆเฉ",
iGurbani: "iGurbani",
@@ -422,6 +498,38 @@ const STRINGS = new LocalizedStrings({
checkForUpdate: "BaniDB เจ
เฉฑเจชเจกเฉเจเจพเจ เจฆเฉ เจเจพเจเจ เจเฉเจคเฉ เจเจพ เจฐเจนเฉ เจนเฉ...",
upToDate: "เจคเฉเจนเจพเจกเจพ BaniDB เจ
เฉฑเจช เจเฉ เจกเฉเจ เจนเฉเฅค",
baniDBBannerText: "เจเฉฑเจ เจจเจตเจพเจ BaniDB เจ
เฉฑเจชเจกเฉเจ เจเจชเจฒเจฌเจง เจนเฉเฅค เจนเฉเจฃเฉ เจ
เฉฑเจชเจกเฉเจ เจเจฐเจจ เจฒเจ เจเฉเจช เจเจฐเฉเฅค",
+ MORE_TRACKS: "เจเจกเฉเจ",
+ AUDIO_SETTINGS: "เจตเจฟเจเจฒเจช",
+ info: "เจเจพเจฃเจเจพเจฐเฉ",
+ welcome_to_sundar_gutka: "เจธเฉเฉฐเจฆเจฐ เจเฉเจเจเจพ เจเจกเฉเจ เจตเจฟเฉฑเจ เจคเฉเจนเจพเจกเจพ เจธเจตเจพเจเจค เจนเฉ",
+ please_choose_a_track: "เจเจฟเจฐเจชเจพ เจเจฐเจเฉ เจเจฐเฉเจ เจเฉเจฃเฉ",
+ PLAY: "เจเจฒเจพเจ",
+ DOWNLOAD: "เจกเจพเจเจจเจฒเฉเจก",
+ DOWNLOADING: "เจกเจพเจเจจเจฒเฉเจก เจนเฉ เจฐเจนเจฟ เจฐเจนเจพ เจนเฉ",
+ NEXT: "เจ
เจเจฒเจพ",
+ HOME: "เจฎเฉเฉฑเจ เจชเฉเจ",
+ READ: "เจชเฉเฉเจนเฉ",
+ MUSIC: "เจเจกเฉเจ",
+ SETTINGS: "เจธเฉเจเจฟเฉฐเจเจพเจ",
+ PLAYBACK_SPEED: "เจชเจฒเฉเจฌเฉเจ เจธเจชเฉเจก",
+ UNABLE_TO_PLAY: "เจเจกเฉเจ เจเจฒเจพเจเจฃ เจฒเจ เจ
เจธเจฎเจฐเจฅ",
+ PLEASE_TRY_AGAIN: "เจเจฟเจฐเจชเจพ เจเจฐเจเฉ เจฆเฉเจฌเจพเจฐเจพ เจเฉเจธเจผเจฟเจธเจผ เจเจฐเฉเฅค",
+ UNABLE_TO_SEEK: "เจเจกเฉเจ เจฆเฉ เจธเจฅเจฟเจคเฉ เจฌเจฆเจฒเจฃ เจฒเจ เจ
เจธเจฎเจฐเจฅ",
+ UNABLE_TO_SWITCH_TRACK: "เจเจกเฉเจ เจเจฐเฉเจ เจฌเจฆเจฒเจฃ เจฒเจ เจ
เจธเจฎเจฐเจฅ",
+ PREPARING_AUDIO_PLAYER: "เจเจกเฉเจ เจชเจฒเฉเจ
เจฐ เจคเจฟเจเจฐ เจเฉเจคเจพ เจเจพ เจฐเจฟเจนเจพ เจนเฉ...",
+ AUDIO_SYNC_UNAVAILABLE: "เจเจกเฉเจ เจธเจฟเฉฐเจ เจ
เจธเจตเฉเจเจฐเจฟเจค เจนเฉเฅค เจเจกเฉเจ เจเจเฉ เจธเจเจฐเฉเจฒ เจฆเฉ เจฌเจฟเจจเจพเจ เจเจฒเจพเจ เจเจพเจตเฉเจเฉเฅค",
+ NETWORK_ERROR:
+ "เจจเฉเฉฑเจเจตเจฐเจ เจคเจฐเฉเฉฑเจเฉเฅค เจเจกเฉเจ เจธเฉฐเจธเจเจฐเจจเจพเจ เจจเฉเฉฐ เจ
เจธเจฅเจพเจ เจฐเฉเจช เจธเจเจฐเฉเจฒ เจฆเฉ เจฌเจฟเจจเจพเจ เจ
เจธเจตเฉเจเจฐเจฟเจค เจเจฐ เจฆเจฟเฉฑเจคเจพ เจเจพเจตเฉเจเจพเฅค",
+ COULD_NOT_LOAD_AUDIO_ARTISTS:
+ "เจเจกเฉเจ เจเจฒเจพเจเจพเจฐเจพเจ เจจเฉเฉฐ เจฒเฉเจก เจเจฐเจจ เจฒเจ เจ
เจธเจฎเจฐเจฅเฅค เจเจฟเจฐเจชเจพ เจเจฐเจเฉ เจเจชเจฃเจพ เจเจจเฉเจเจถเจจ เจเจพเจเจเฉเฅค",
+ PERMISSION_ERROR: "เจชเฉเจฐเจธเจพเจฐเจฃ เจเจเจพเจเจผเจค เจเจพเจเจ เจ
เจธเจซเจฒ เจฐเจนเฉ โ เจเจเจพเจเจผเจค เจเฉเฉฐเจฎ เจนเฉ เจเจพเจ เจ
เจธเจตเฉเจเจพเจฐ เจเฉเจคเฉ เจเจ เจนเฉ",
+ MAAFI_JI: "เจฎเจพเจซเจผ เจเจฐเจจเจพ เจเฉ ๐๐ฝ",
+ WE_DO_NOT_HAVE_AUDIOS_FOR: "เจธเจพเจกเฉ เจเฉเจฒ เจเจนเจจเจพเจ เจฒเจ เจเจกเฉเจ เจจเจนเฉเจ เจนเจจ",
+ REQUEST_AUDIO_FOR_THIS_PAATH: "เจเจธ เจชเจพเจ เจฒเจ เจเจกเฉเจ เจฆเฉ เจฌเฉเจจเจคเฉ เจเจฐเฉเฅค",
+ YET: "เจ
เจเฉ เจคเฉฑเจเฅค",
+ SYNC_UNAVAILABLE: "เจเจชเจฒเจฌเจง เจจเจนเฉเจ เจนเฉ",
+ RETRY: "เจฆเฉเจฌเจพเจฐเจพ เจเฉเจธเจผเจฟเจธเจผ เจเจฐเฉ",
+ INITIALIZING_AUDIO_PLAYER: "เจ
เจธเฉเจ เจเจธ เจตเฉเจฒเฉ เจเฉเจฐเจฌเจพเจฃเฉ เจเจกเฉเจ เจธเจผเฉเจฐเฉ เจจเจนเฉเจ เจเจฐ เจธเจเจฆเฉเฅค",
},
fr: {
about: "Infos",
@@ -437,6 +545,10 @@ const STRINGS = new LocalizedStrings({
anmol_lipi: "Anmol Leppi",
APP_VERSION: "Version",
AUTO_SCROLL: "Dรฉfilement Automatique",
+ AUDIO: "Lecteur Audio",
+ AUDIO_AUTO_PLAY: "Lecture Audio Automatique",
+ AUDIO_SYNC_SCROLL: "Synchroniser le dรฉfilement avec l'audio",
+ DEFAULT_AUDIO: "Audio par dรฉfaut",
bani_length: "Longueur de Bani",
bani_length_alert_1:
"Au cours des derniers siรจcles, il y a eu de nombreux ยซsampardhasยป ou ยซjathasยป diffรฉrents qui ont รฉtรฉ conรงus ร partir des concepts fondamentaux du Sikhi et du Gurmat. Ces sampardhas ont souvent des opinions diffรฉrentes sur certains aspects de l'histoire sikh, Gurbani et Rehat, mais relรจvent toujours collectivement du Khalsa Panth uni et, plus important encore, de l'Akal Takht. L'Akaal Takht est l'ordre le plus รฉlevรฉ et est la plus haute institution ร laquelle tous les Sikhs adhรจrent.",
@@ -490,6 +602,7 @@ const STRINGS = new LocalizedStrings({
gurbani_akhar_default: "Gourbani Akhar (dรฉfaut)",
gurbani_akhar_heavy: "Gourbani Akhar Lourde",
gurbani_akhar_think: "Gourbani Akhar รpaisse",
+ baloo_paaji: "Baloo Paaji",
HIDE_STATUS_BAR: "Cacher la barre d'รฉtat",
hindi: "Langue hindou",
iGurbani: "iGurbani",
@@ -556,18 +669,52 @@ const STRINGS = new LocalizedStrings({
baniDBHighlight1:
"La base de donnรฉes Gurbani la plus prรฉcise au monde : plus de 43 000 corrections et ce n'est pas fini.",
baniDBHighlight2:
- "Mรฉticuleusement vรฉrifiรฉ : les donnรฉes de Sri Guru Granth Sahib Ji ont fait lโobjet de nombreux examens.",
+ "Mรฉticuleusement vรฉrifiรฉ : les donnรฉes de Sri Guru Granth Sahib Ji ont fait l'objet de nombreux examens.",
baniDBHighlight3:
"Normalisation unique : se concentre sur la prรฉcision des lagamatras (orthographe) et des padh chhedh (sรฉparation des mots)",
baniDBHighlight4:
"Distinct des publications SGPC : la seule base de donnรฉes standardisรฉe indรฉpendamment des pothis Gurbani du SGPC.",
baniDBMistakeText:
- "Vous avez trouvรฉ une erreur en gurbaniย ? Vous souhaitez une meilleure traductionย ? Devenez contributeur ร BaniDBย ! Visitezย :",
+ "Vous avez trouvรฉ une erreur en gurbani ? Vous souhaitez une meilleure traduction ? Devenez contributeur ร BaniDB ! Visitez :",
baniDBSignUp: "Inscription ร BaniDB",
checkForUpdate: "Vรฉrification des mises ร jour de BaniDB...",
upToDate: "Votre BaniDB est ร jour.",
baniDBBannerText:
"Une nouvelle mise ร jour de BaniDB est disponible. Appuyez pour mettre ร jour maintenant.",
+ MORE_TRACKS: "Audios",
+ AUDIO_SETTINGS: "Options",
+ info: "info",
+ welcome_to_sundar_gutka: "Bienvenue dans Sundar Gutka Audio",
+ please_choose_a_track: "Veuillez choisir un piste pour",
+ PLAY: "Jouer",
+ DOWNLOAD: "Tรฉlรฉcharger",
+ DOWNLOADING: "Tรฉlรฉchargement en cours",
+ NEXT: "Suivant",
+ HOME: "Accueil",
+ READ: "Lecture",
+ MUSIC: "Audio",
+ SETTINGS: "Paramรจtres",
+ PLAYBACK_SPEED: "Vitesse de reproduction",
+ UNABLE_TO_PLAY: "Impossible de lire la piste audio.",
+ PLEASE_TRY_AGAIN: "Veuillez rรฉessayer.",
+ UNABLE_TO_SEEK: "Impossible de rechercher la piste audio.",
+ UNABLE_TO_SWITCH_TRACK: "Impossible de changer la piste audio.",
+ PREPARING_AUDIO_PLAYER: "Prรฉparation du lecteur audio...",
+ AUDIO_SYNC_UNAVAILABLE:
+ "Synchronisation audio non disponible. La lecture continuera sans dรฉfilement automatique.",
+ NETWORK_ERROR: "Erreur rรฉseau. Les fonctionnalitรฉs audio sont temporairement indisponibles.",
+ COULD_NOT_LOAD_AUDIO_ARTISTS:
+ "Impossible de charger les artistes audio. Veuillez vรฉrifier votre connexion.",
+ PERMISSION_ERROR:
+ "รchec de la vรฉrification de l'autorisation de diffusionย : autorisation manquante ou refusรฉe",
+ MAAFI_JI: "Dรฉsolรฉ JI๐๐ฝ",
+ WE_DO_NOT_HAVE_AUDIOS_FOR: "Nous n'avons pas d'audios pour",
+ REQUEST_AUDIO_FOR_THIS_PAATH: "Demander un audio pour ce chemin.",
+ YET: "encore.",
+ SYNC_UNAVAILABLE: "Non disponible.",
+ RETRY: "Riavvia",
+ INITIALIZING_AUDIO_PLAYER:
+ "Nous ne sommes pas en mesure de dรฉmarrer la lecture audio du Gurbani pour le moment.",
},
it: {
about: "Info",
@@ -583,15 +730,19 @@ const STRINGS = new LocalizedStrings({
anmol_lipi: "Anmรฒl Leppi",
APP_VERSION: "Versione",
AUTO_SCROLL: "Scorrimento Automatico",
+ AUDIO: "Lettore Audio",
+ AUDIO_AUTO_PLAY: "Riproduzione Audio Automatica",
+ AUDIO_SYNC_SCROLL: "Sincronizza scorrimento con audio",
+ DEFAULT_AUDIO: "Audio predefinito",
bani_length: "Lunghezza delle Bani",
bani_length_alert_1:
- "Nel corso degli ultimi secoli, ci sono stati molti โsampardhasโ o โjathasโ diversi che sono stati concepiti dai concetti fondamentali di Sikhi e della Gurmat. Questi esempi hanno spesso opinioni e pensieri diversi su alcuni aspetti della storia dei Sikh, Gurbani e Rehat. Cadono ancora sotto l'unione di Khalsa Panth e, soprattutto, di Akaal Takht. L'Akaal Takht รจ il piรน alto ordine e istituzione a cui tutti i sikh aderiscono.",
+ "Nel corso degli ultimi secoli, ci sono stati molti sampardhas o jathas diversi che sono stati concepiti dai concetti fondamentali di Sikhi e della Gurmat. Questi esempi hanno spesso opinioni e pensieri diversi su alcuni aspetti della storia dei Sikh, Gurbani e Rehat. Cadono ancora sotto l'unione di Khalsa Panth e, soprattutto, di Akaal Takht. L'Akaal Takht รจ il piรน alto ordine e istituzione a cui tutti i sikh aderiscono.",
bani_length_alert_2:
"Queste differenze hanno anche portato a diverse raccomandazioni a proposito delle Baani compilate e Paath (preghiere) che leggiamo quotidianamente, il che rende difficile creare una singola app Sundar Gutka con un'unica versione di Paath in grado di soddisfare tutti.",
bani_length_alert_3:
"Pertanto abbiamo offerto la possibilitร di selezionare le lunghezze di ciรฒ che fai Paath che si applica a quattro deile Bani principale lette piรน spesso. Queste sono state strutturate in relazione alla lunghezza ma tutte hanno un minimo dello standard SGPC o rientrane nell'Akaal Takht. Come standard, non includiamo alcuna versione di sampardhas che sono stati scomunicati dall'Akaal Takht.",
bani_length_alert_4:
- "Per coloro che potrebbero essere confusi su quale versione iniziare a leggere queste Banis, raccomandiamo di leggere la Bani piรน lunga perchรฉ piรน leggiamo e recitiamo, meglio รจ per le nostre anime. Tuttavia, per i principianti, consigliamo di iniziare con l'impostazione โbreveโ e modificare per aumentare l'impostazione della lunghezza in futuro una volta che si รจ a proprio agio, sicuri e hanno piรน tempo.",
+ "Per coloro che potrebbero essere confusi su quale versione iniziare a leggere queste Banis, raccomandiamo di leggere la Bani piรน lunga perchรฉ piรน leggiamo e recitiamo, meglio รจ per le nostre anime. Tuttavia, per i principianti, consigliamo di iniziare con l'impostazione breve e modificare per aumentare l'impostazione della lunghezza in futuro una volta che si รจ a proprio agio, sicuri e hanno piรน tempo.",
bani_length_alert_5: "Ecco qui le lunghezze e quali sampardhas le usano normalmente: -",
bani_length_alert_6: "BREVE: questo รจ lo standard minimo SGPC / Akaal Takht.",
bani_length_alert_7: "MEDIA: in genere letto dai seguaci di Akhand Kirtani Jatha e altri.",
@@ -634,6 +785,7 @@ const STRINGS = new LocalizedStrings({
gurbani_akhar_default: "Gurbani Akhar (default)",
gurbani_akhar_heavy: "Gourbani Akhar Grasso",
gurbani_akhar_think: "Gourbani Akhar Spesso",
+ baloo_paaji: "Baloo Paaji",
HIDE_STATUS_BAR: "Nascondere la barra di stato",
hindi: "Lingua hindi",
iGurbani: "iGurbani",
@@ -711,6 +863,33 @@ const STRINGS = new LocalizedStrings({
checkForUpdate: "Controllo degli aggiornamenti di BaniDB...",
upToDate: "Il tuo BaniDB รจ aggiornato.",
baniDBBannerText: "ร disponibile un nuovo aggiornamento di BaniDB. Tocca per aggiornare ora.",
+ MORE_TRACKS: "Audios",
+ AUDIO_SETTINGS: "Opzioni",
+ NEXT: "Prossimo",
+ HOME: "Home",
+ READ: "Lettura",
+ MUSIC: "Audio",
+ SETTINGS: "Impostazioni",
+ PLAYBACK_SPEED: "Velocidad de reproducciรณn",
+ UNABLE_TO_PLAY: "No se puede reproducir el audio.",
+ PLEASE_TRY_AGAIN: "Por favor, intรฉntelo de nuevo.",
+ UNABLE_TO_SEEK: "No se puede buscar el audio.",
+ UNABLE_TO_SWITCH_TRACK: "No se puede cambiar el audio.",
+ PREPARING_AUDIO_PLAYER: "Preparazione del lettore audio...",
+ AUDIO_SYNC_UNAVAILABLE:
+ "Sincronizaciรณn de audio no disponible. La reproducciรณn continuarรก sin desplazamiento automรกtico.",
+ NETWORK_ERROR: "Error de red. Las funciones de audio estรกn temporalmente indisponibles.",
+ COULD_NOT_LOAD_AUDIO_ARTISTS:
+ "No se pueden cargar los artistas de audio. Por favor, verifique su conexiรณn.",
+ PERMISSION_ERROR:
+ "Controllo dell'autorizzazione di trasmissione non riuscito: autorizzazione mancante o negata",
+ MAAFI_JI: "Mi dispiace Ji๐๐ฝ",
+ WE_DO_NOT_HAVE_AUDIOS_FOR: "Non abbiamo audio per",
+ REQUEST_AUDIO_FOR_THIS_PAATH: "Richiedi audio per questo percorso.",
+ YET: "ancora.",
+ SYNC_UNAVAILABLE: "Non disponibile.",
+ RETRY: "Riavvia",
+ INITIALIZING_AUDIO_PLAYER: "Al momento non รจ possibile avviare l'audio Gurbani.",
},
es: {
about: "Sobre Nosotros",
@@ -726,15 +905,19 @@ const STRINGS = new LocalizedStrings({
anmol_lipi: "Anmรณl Lippi ",
APP_VERSION: "Versiรณn ",
AUTO_SCROLL: "Desplazamiento automรกtico",
+ AUDIO: "Reproductor de Audio",
+ AUDIO_AUTO_PLAY: "Reproducciรณn Automรกtica de Audio",
+ AUDIO_SYNC_SCROLL: "Sincronizar desplazamiento con audio",
+ DEFAULT_AUDIO: "Audio predeterminado",
bani_length: "Largueza de las Bani",
bani_length_alert_1:
- "A lo largo de los รบltimos siglos, ha habido muchos โsampardhasโ o โjathasโ diferentes que se han concebido a partir de los conceptos centrales de Sikhi y Gurmat. Estos sampardhas a menudo tienen diferentes opiniones y pensamientos sobre algunos aspectos de la historia sikh, Gurbani y Rehat, pero aรบn caen colectivamente bajo el Khalsa Panth unido y, lo mรกs importante, el Akaal Takht.El Akaal Takht es el mรกs alto orden e instituciรณn a la que se adhieren todos los sikhs.",
+ "A lo largo de los รบltimos siglos, ha habido muchos sampardhas o jathas diferentes que se han concebido a partir de los conceptos centrales de Sikhi y Gurmat. Estos sampardhas a menudo tienen diferentes opiniones y pensamientos sobre algunos aspectos de la historia sikh, Gurbani y Rehat, pero aรบn caen colectivamente bajo el Khalsa Panth unido y, lo mรกs importante, el Akaal Takht.El Akaal Takht es el mรกs alto orden e instituciรณn a la que se adhieren todos los sikhs.",
bani_length_alert_2:
"Estas diferencias tambiรฉn han dado lugar a diferentes recomendaciones sobre Baanis y Paath (oraciones) compiladas que leemos a diario, lo que dificulta la creaciรณn de una sola aplicaciรณn Sundar Gutka con una รบnica versiรณn de Paath que satisfaga a todos.",
bani_length_alert_3:
"Por lo tanto, hemos creado la opciรณn de seleccionar longitudes de lo que hace Paath que se aplica a cuatro de los principales Banis leรญdos con mayor frecuencia. Estos se han estructurado en relaciรณn con la longitud, pero todos tienen un mรญnimo del estรกndar SGPC o caen bajo el Akaal Takht. Como estรกndar, no incluimos ninguna versiรณn que sea realizada por sampardhas excomulgada por el Akaal Takht.",
bani_length_alert_4:
- "For those who are confused about from which version to start reading these Banis, we recommend reading the longest Bani because the more we read and recite, the better it will be for our souls. However, for beginners, we suggest starting with the โshortโ setting and changing to increase length in the future once you are comfortable, confident, and have more time.",
+ "For those who are confused about from which version to start reading these Banis, we recommend reading the longest Bani because the more we read and recite, the better it will be for our souls. However, for beginners, we suggest starting with the short setting and changing to increase length in the future once you are comfortable, confident, and have more time.",
bani_length_alert_5:
"Aquรญ hay un desglose de las longitudes y quรฉ sampard las usa tรญpicamente:",
bani_length_alert_6: "CORTA: Este es el estรกndar mรญnimo SGPC / Akaal Takht.",
@@ -778,6 +961,7 @@ const STRINGS = new LocalizedStrings({
gurbani_akhar_default: "Gurbani Akhar (predeterminados)",
gurbani_akhar_heavy: "Gurbani Akhar Fuerte",
gurbani_akhar_think: "Gurbani Akhar Denso",
+ baloo_paaji: "Baloo Paaji",
HIDE_STATUS_BAR: "Ocultar la barra de estado",
hindi: "Idioma hindรบ",
iGurbani: "iGurbani",
@@ -855,7 +1039,40 @@ const STRINGS = new LocalizedStrings({
checkForUpdate: "Comprobando actualizaciones de BaniDB...",
upToDate: "Su BaniDB estรก actualizado.",
baniDBBannerText: "Hay una nueva actualizaciรณn de BaniDB disponible. Pulsa para actualizar.",
+ MORE_TRACKS: "Audios",
+ AUDIO_SETTINGS: "Opciones",
+ info: "info",
+ welcome_to_sundar_gutka: "Bienvenido a Sundar Gutka Audio",
+ please_choose_a_track: "Por favor, elija una pista para",
+ PLAY: "Reproducir",
+ DOWNLOAD: "Descargar",
+ DOWNLOADING: "Descargando",
+ NEXT: "Siguiente",
+ HOME: "Inicio",
+ READ: "Leer",
+ MUSIC: "Audio",
+ SETTINGS: "Configuraciรณn",
+ PLAYBACK_SPEED: "Velocidad de reproducciรณn",
+ UNABLE_TO_PLAY: "No se puede reproducir el audio.",
+ PLEASE_TRY_AGAIN: "Por favor, intรฉntelo de nuevo.",
+ UNABLE_TO_SEEK: "No se puede buscar el audio.",
+ UNABLE_TO_SWITCH_TRACK: "No se puede cambiar el audio.",
+ PREPARING_AUDIO_PLAYER: "Preparando reproductor de audio...",
+ AUDIO_SYNC_UNAVAILABLE:
+ "Sincronizaciรณn de audio no disponible. La reproducciรณn continuarรก sin desplazamiento automรกtico.",
+ PERMISSION_ERROR:
+ "Fallรณ la comprobaciรณn de permisos de transmisiรณn: permiso faltante o denegado.",
},
+ NETWORK_ERROR: "Error de red. Las funciones de audio estรกn temporalmente indisponibles.",
+ COULD_NOT_LOAD_AUDIO_ARTISTS:
+ "No se pueden cargar los artistas de audio. Por favor, verifique su conexiรณn.",
+ MAAFI_JI: "Lo siento Ji๐๐ฝ",
+ WE_DO_NOT_HAVE_AUDIOS_FOR: "No tenemos audios para",
+ REQUEST_AUDIO_FOR_THIS_PAATH: "Solicitar audio para esta ruta.",
+ YET: "aรบn.",
+ SYNC_UNAVAILABLE: "Indisponible",
+ RETRY: "Retry",
+ INITIALIZING_AUDIO_PLAYER: "No podemos iniciar el audio de Gurbani en este momento.",
});
export default STRINGS;
diff --git a/src/common/middleware/crashlytics.js b/src/common/middleware/crashlytics.js
index 9a85be28..f687a4dc 100644
--- a/src/common/middleware/crashlytics.js
+++ b/src/common/middleware/crashlytics.js
@@ -1,33 +1,40 @@
import { setCustomKey } from "../firebase/crashlytics";
+const MAX_STRING_LENGTH = 512;
+const MAX_STATE_KEYS = 10;
+
// Helper function to safely stringify values
const safeStringify = (value) => {
- // null or undefined
- if (value == null) {
- return "";
- }
- if (typeof value === "boolean" || typeof value === "number") {
- return value.toString();
- }
- if (typeof value === "string") {
- return value;
+ if (value == null) return "";
+ if (typeof value === "boolean" || typeof value === "number" || typeof value === "string") {
+ return String(value).slice(0, MAX_STRING_LENGTH);
}
- // array or object
if (typeof value === "object") {
- return JSON.stringify(value);
+ try {
+ return JSON.stringify(value).slice(0, MAX_STRING_LENGTH);
+ } catch {
+ return "[unserializable]";
+ }
}
- return String(value);
+ return String(value).slice(0, MAX_STRING_LENGTH);
};
-// Function to update Crashlytics with current state
-const updateCrashlyticsState = (state) => {
- const crashlyticsState = {};
- // Update each state value in Crashlytics
- Object.entries(state).forEach(([key, value]) => {
- const crashlyticsKey = key.replace(/([A-Z])/g, "-$1").toLowerCase();
- crashlyticsState[crashlyticsKey] = safeStringify(value);
- });
- setCustomKey(crashlyticsState);
+const summarizeState = (state) => {
+ const summary = {};
+ let count = 0;
+ // eslint-disable-next-line no-restricted-syntax
+ for (const [key, value] of Object.entries(state || {})) {
+ if (count >= MAX_STATE_KEYS) break;
+ // eslint-disable-next-line no-continue
+ if (value === undefined) continue;
+
+ if (typeof value === "string" || typeof value === "number" || typeof value === "boolean") {
+ const crashlyticsKey = key.replace(/([A-Z])/g, "-$1").toLowerCase();
+ summary[crashlyticsKey] = safeStringify(value);
+ count += 1;
+ }
+ }
+ return summary;
};
// Crashlytics middleware
@@ -38,16 +45,17 @@ const crashlyticsMiddleware = (store) => (next) => (action) => {
// Track action value/payload
const actionValue = action.value !== undefined ? action.value : action.payload;
if (actionValue !== undefined) {
- const actionKey = action.type.toLowerCase().replace(/_/g, "-");
- setCustomKey(actionKey, safeStringify(actionValue));
+ setCustomKey("last-action-value", safeStringify(actionValue));
}
// Let the action go through
const result = next(action);
- // After state update, track the entire state
- const newState = store.getState();
- updateCrashlyticsState(newState);
+ // After state update, track a small summary of the state to avoid overflow
+ const stateSummary = summarizeState(store.getState());
+ if (Object.keys(stateSummary).length > 0) {
+ setCustomKey(stateSummary);
+ }
return result;
};
diff --git a/src/common/notifications.js b/src/common/notifications.js
index 27782dc4..a816e982 100644
--- a/src/common/notifications.js
+++ b/src/common/notifications.js
@@ -6,21 +6,34 @@ import notifee, {
AuthorizationStatus,
} from "@notifee/react-native";
import moment from "moment";
+import { FallBack } from "./components";
import constant from "./constant";
import { logError, logMessage } from "./firebase/crashlytics";
-import { FallBack } from "./components";
+import STRINGS from "./localization";
+import { showErrorToast } from "./toast";
// Utility function to check if BROADCAST_CLOSE_SYSTEM_DIALOGS permission is available
const checkBroadcastPermission = async () => {
if (Platform.OS === "android") {
try {
- // This will throw an exception if the permission is not available
- const broadcastPermissionCheck = await PermissionsAndroid.check(
- PermissionsAndroid.PERMISSIONS.BROADCAST_CLOSE_SYSTEM_DIALOGS
- );
+ // Use the permission constant if available, otherwise use the string directly
+ const permission =
+ PermissionsAndroid.PERMISSIONS?.BROADCAST_CLOSE_SYSTEM_DIALOGS ??
+ "android.permission.BROADCAST_CLOSE_SYSTEM_DIALOGS";
+
+ // Check if permission is defined before calling check
+ if (!permission) {
+ logMessage("checkBroadcastPermission: Permission string is null/undefined");
+ return false;
+ }
+
+ const broadcastPermissionCheck = await PermissionsAndroid.check(permission);
return broadcastPermissionCheck;
} catch (error) {
// Permission not available on this device/API level
+ showErrorToast(STRINGS.PERMISSION_ERROR);
+ logError(error);
+ logMessage(STRINGS.PERMISSION_ERROR);
return false;
}
}
diff --git a/src/common/reducer.js b/src/common/reducer.js
index 310f0b8b..bdc74124 100644
--- a/src/common/reducer.js
+++ b/src/common/reducer.js
@@ -50,6 +50,22 @@ const isAutoScroll = createReducer(false, {
[actionTypes.TOGGLE_AUTO_SCROLL]: (state, action) => action.value,
});
+const isAudio = createReducer(false, {
+ [actionTypes.TOGGLE_AUDIO]: (state, action) => action.value,
+});
+
+const isAudioAutoPlay = createReducer(false, {
+ [actionTypes.TOGGLE_AUDIO_AUTO_PLAY]: (state, action) => action.value,
+});
+
+const isAudioSyncScroll = createReducer(false, {
+ [actionTypes.TOGGLE_AUDIO_SYNC_SCROLL]: (state, action) => action.value,
+});
+
+const audioPlaybackSpeed = createReducer(1.0, {
+ [actionTypes.SET_AUDIO_PLAYBACK_SPEED]: (state, action) => action.value,
+});
+
const baniLength = createReducer("", {
[actionTypes.SET_BANI_LENGTH]: (state, action) => action.value,
});
@@ -102,6 +118,10 @@ const bookmarkPosition = createReducer(0, {
[actionTypes.SET_BOOKMARK_POSITION]: (state, action) => action.value,
});
+const bookmarkSequenceString = createReducer(null, {
+ [actionTypes.SET_BOOKMARK_SEQUENCE_STRING]: (state, action) => action.value,
+});
+
const isReminders = createReducer(false, {
[actionTypes.TOGGLE_REMINDERS]: (state, action) => action.value,
});
@@ -122,6 +142,19 @@ const isDatabaseUpdateAvailable = createReducer(false, {
[actionTypes.TOGGLE_DATABASE_UPDATE_AVAILABLE]: (state, action) => action.value,
});
+// Audio Manifest reducer
+const audioManifest = (state = {}, action) => {
+ switch (action.type) {
+ case actionTypes.SET_AUDIO_MANIFEST:
+ return {
+ ...state,
+ [action.payload.baniId]: action.payload.tracks,
+ };
+ default:
+ return state;
+ }
+};
+
const autoScrollSpeedObj = (state = {}, action) => {
switch (action.type) {
case actionTypes.SET_AUTO_SCROLL_SPEED:
@@ -148,6 +181,16 @@ const baniList = (state = [], action) => {
return state;
}
};
+
+const defaultAudio = (state = {}, action) => {
+ switch (action.type) {
+ case actionTypes.SET_DEFAULT_AUDIO:
+ return { ...state, ...action.value };
+ default:
+ return state;
+ }
+};
+
const savePosition = (state = {}, action) => {
switch (action.type) {
case actionTypes.SET_SAVE_POSITION:
@@ -165,6 +208,34 @@ const scrollPosition = (state = 0, action) => {
}
};
+// Audio progress reducer - stores progress per bani: { [baniId]: { trackId, position, sequence } }
+const audioProgress = (state = {}, action) => {
+ switch (action.type) {
+ case actionTypes.SET_AUDIO_PROGRESS: {
+ const { baniId, trackId, position, sequence } = action.payload;
+ return {
+ ...state,
+ [baniId]: {
+ trackId,
+ position,
+ ...(sequence !== undefined && { sequence }),
+ },
+ };
+ }
+ case actionTypes.CLEAR_AUDIO_PROGRESS: {
+ const { baniId } = action.payload;
+ const newState = { ...state };
+ delete newState[baniId];
+ return newState;
+ }
+ default:
+ return state;
+ }
+};
+const currentBani = createReducer(null, {
+ [actionTypes.SET_CURRENT_BANI]: (state, action) => action.value,
+});
+
const rootReducer = combineReducers({
isNightMode,
fontSize,
@@ -174,6 +245,11 @@ const rootReducer = combineReducers({
isTransliteration,
theme,
isAutoScroll,
+ isAudio,
+ isAudioAutoPlay,
+ isAudioSyncScroll,
+ audioPlaybackSpeed,
+ defaultAudio,
isScreenAwake,
isStatusBar,
baniLength,
@@ -189,6 +265,7 @@ const rootReducer = combineReducers({
isPunjabiTranslation,
isSpanishTranslation,
bookmarkPosition,
+ bookmarkSequenceString,
isReminders,
reminderBanis,
reminderSound,
@@ -199,5 +276,8 @@ const rootReducer = combineReducers({
scrollPosition,
isHeaderFooter,
isDatabaseUpdateAvailable,
+ audioManifest,
+ audioProgress,
+ currentBani,
});
export default rootReducer;
diff --git a/src/common/rnfs.js b/src/common/rnfs.js
index ac6d6fed..20831c12 100644
--- a/src/common/rnfs.js
+++ b/src/common/rnfs.js
@@ -22,10 +22,9 @@ export const REMOTE_DB_URL = `${constant.REMOTE_DB_URL}/${constant.DB}.db`;
export const listDocumentDirectory = async () => {
try {
const files = await readDir(DocumentDirectoryPath);
- console.log("Files in Document Directory:", files);
return files;
} catch (error) {
- console.error("Error reading directory:", error);
+ logError("Error in listDocumentDirectory:", error);
return [];
}
};
diff --git a/src/common/rootNavigation.js b/src/common/rootNavigation.js
index 5ddff536..cb5f4eb1 100644
--- a/src/common/rootNavigation.js
+++ b/src/common/rootNavigation.js
@@ -1,5 +1,7 @@
import { createNavigationContainerRef } from "@react-navigation/native";
+import { getBaniByID } from "@database";
import constant from "./constant";
+import { logError } from "./firebase/crashlytics";
export const navigationRef = createNavigationContainerRef();
@@ -8,8 +10,35 @@ export const navigate = (name, params) => {
navigationRef.navigate(name, params);
}
};
-export const navigateTo = (incoming) => {
+
+export const navigateTo = async (incoming) => {
+ if (!incoming?.notification?.data) {
+ logError("navigateTo called with invalid notification data");
+ return;
+ }
+
const { data } = incoming.notification;
- const params = { key: `Reader-${data.id}`, params: { id: data.id, title: data.gurmukhi } };
+
+ // Try to fetch titleUni from database if not provided in notification
+ let titleUni = data.gurmukhiUni;
+ if (!titleUni && data.id) {
+ try {
+ const baniData = await getBaniByID(data.id);
+ if (baniData) {
+ titleUni = baniData.gurmukhiUni;
+ }
+ } catch (error) {
+ // If fetching fails, continue without titleUni - it will fallback to title
+ }
+ }
+
+ const params = {
+ key: `Reader-${data.id}`,
+ params: {
+ id: data.id,
+ title: data.gurmukhi,
+ ...(titleUni && { titleUni }),
+ },
+ };
navigate(constant.READER, params);
};
diff --git a/src/common/store.js b/src/common/store.js
index f9caad33..9820c896 100644
--- a/src/common/store.js
+++ b/src/common/store.js
@@ -1,8 +1,8 @@
+import AsyncStorage from "@react-native-async-storage/async-storage";
import { configureStore } from "@reduxjs/toolkit";
import { persistReducer, persistStore } from "redux-persist";
-import AsyncStorage from "@react-native-async-storage/async-storage";
-import reducer from "./reducer";
import crashlyticsMiddleware from "./middleware/crashlytics";
+import reducer from "./reducer";
const persistConfig = { key: "root", storage: AsyncStorage, blacklist: ["navigation", "baniList"] };
const persistedReducer = persistReducer(persistConfig, reducer);
diff --git a/src/common/test-utils/README.md b/src/common/test-utils/README.md
new file mode 100644
index 00000000..8bc4b395
--- /dev/null
+++ b/src/common/test-utils/README.md
@@ -0,0 +1,162 @@
+# Test Utilities
+
+This directory contains reusable mocks and test utilities for testing React Native components.
+
+## Usage
+
+### Basic Usage
+
+Import the mock factories and use them in your `jest.mock()` calls:
+
+```javascript
+import { getMockDispatch, getMockState, setMockState } from "@common/test-utils/mocks/react-redux";
+import { createContextMock } from "@common/test-utils/mocks/context";
+import { createUseThemedStylesMock } from "@common/test-utils/mocks/useThemedStyles";
+import { createIconsMock } from "@common/test-utils/mocks/icons";
+import { createCommonMock } from "@common/test-utils/mocks/common";
+
+// Mock react-redux hooks
+jest.mock("react-redux", () => {
+ const { createReactReduxMock } = require("@common/test-utils/mocks/react-redux");
+ return createReactReduxMock().mock;
+});
+
+// Mock theme/context
+jest.mock("@common/context", () => {
+ const { createContextMock } = require("@common/test-utils/mocks/context");
+ return createContextMock();
+});
+
+// Mock useThemedStyles
+jest.mock("@common/hooks/useThemedStyles", () => {
+ const { createUseThemedStylesMock } = require("@common/test-utils/mocks/useThemedStyles");
+ return createUseThemedStylesMock();
+});
+
+// Mock icons
+jest.mock("@common/icons", () => {
+ const { createIconsMock } = require("@common/test-utils/mocks/icons");
+ return createIconsMock();
+});
+
+// Mock @common exports
+jest.mock("@common", () => {
+ const { createCommonMock } = require("@common/test-utils/mocks/common");
+ return createCommonMock();
+});
+
+// In your tests, use the utilities to access mock state/dispatch
+const mockDispatch = getMockDispatch();
+
+beforeEach(() => {
+ setMockState({ isAudio: false, currentBani: { id: 123 } });
+});
+```
+
+### Available Mocks
+
+#### `react-redux` Mock
+
+- **Factory**: `createReactReduxMock(initialState?)`
+- **Utilities**: `getMockDispatch()`, `getMockState()`, `setMockState(newState)`
+
+```javascript
+import {
+ createReactReduxMock,
+ getMockDispatch,
+ setMockState,
+} from "@common/test-utils/mocks/react-redux";
+
+jest.mock("react-redux", () => {
+ const { createReactReduxMock } = require("@common/test-utils/mocks/react-redux");
+ return createReactReduxMock({ isAudio: true }).mock;
+});
+
+// In tests
+const mockDispatch = getMockDispatch();
+setMockState({ isAudio: false, currentBani: { id: 456 } });
+```
+
+#### `@common/context` Mock
+
+- **Factory**: `createContextMock(themeOverrides?)`
+
+```javascript
+import { createContextMock } from "@common/test-utils/mocks/context";
+
+jest.mock("@common/context", () => {
+ const { createContextMock } = require("@common/test-utils/mocks/context");
+ return createContextMock({
+ colors: { primary: "#FF0000" },
+ staticColors: { WHITE_COLOR: "#FFFFFF" },
+ });
+});
+```
+
+#### `useThemedStyles` Mock
+
+- **Factory**: `createUseThemedStylesMock(stylesOverrides?)`
+
+```javascript
+import { createUseThemedStylesMock } from "@common/test-utils/mocks/useThemedStyles";
+
+jest.mock("@common/hooks/useThemedStyles", () => {
+ const { createUseThemedStylesMock } = require("@common/test-utils/mocks/useThemedStyles");
+ return createUseThemedStylesMock({
+ container: { backgroundColor: "red" },
+ });
+});
+```
+
+#### `@common/icons` Mock
+
+- **Factory**: `createIconsMock(iconOverrides?)`
+
+```javascript
+import { createIconsMock } from "@common/test-utils/mocks/icons";
+
+jest.mock("@common/icons", () => {
+ const { createIconsMock } = require("@common/test-utils/mocks/icons");
+ return createIconsMock({
+ CustomIcon: () => ,
+ });
+});
+```
+
+#### `@common` Mock
+
+- **Factory**: `createCommonMock(overrides?)`
+
+```javascript
+import { createCommonMock } from "@common/test-utils/mocks/common";
+
+jest.mock("@common", () => {
+ const { createCommonMock } = require("@common/test-utils/mocks/common");
+ return createCommonMock({
+ STRINGS: {
+ HOME: "Custom Home",
+ READ: "Custom Read",
+ },
+ });
+});
+```
+
+## File Structure
+
+```
+test-utils/
+โโโ mocks/
+โ โโโ react-redux.js # React Redux hooks mock
+โ โโโ context.js # Theme context mock
+โ โโโ useThemedStyles.js # useThemedStyles hook mock
+โ โโโ icons.js # Icons mock
+โ โโโ common.js # @common exports mock
+โ โโโ index.js # Centralized exports
+โโโ README.md # This file
+```
+
+## Notes
+
+- All `jest.mock()` calls are hoisted, so factory functions must be called inside the mock factory function using `require()`
+- Use `getMockDispatch()`, `getMockState()`, and `setMockState()` to access and modify mock state in your tests
+- Mock factories accept optional overrides to customize the mock behavior
diff --git a/src/common/test-utils/mocks/common.js b/src/common/test-utils/mocks/common.js
new file mode 100644
index 00000000..1e9b1899
--- /dev/null
+++ b/src/common/test-utils/mocks/common.js
@@ -0,0 +1,42 @@
+// Mock factory for @common exports
+// Usage in test file:
+// import { createCommonMock } from '@common/test-utils/mocks/common';
+// jest.mock("@common", () => createCommonMock());
+
+export const createCommonMock = (overrides = {}) => {
+ const React = require("react");
+ const RN = require("react-native");
+ const toggleAutoScrollAction = (val) => ({ type: "TOGGLE_AUTO_SCROLL", payload: val });
+ const toggleAudioAction = (val) => ({ type: "TOGGLE_AUDIO", payload: val });
+ const CustomText = ({ children, ...rest }) => {
+ const { Text } = RN;
+ return React.createElement(Text, rest, children);
+ };
+ const SafeArea = ({ children, ...rest }) => {
+ const { View } = RN;
+ return React.createElement(View, rest, children);
+ };
+ return {
+ CustomText,
+ SafeArea,
+ actions: {
+ toggleAutoScroll: jest.fn(toggleAutoScrollAction),
+ toggleAudio: jest.fn(toggleAudioAction),
+ ...overrides.actions,
+ },
+ constant: {
+ READER: "Reader",
+ SETTINGS: "Settings",
+ defaultBani: { id: "default" },
+ ...overrides.constant,
+ },
+ STRINGS: {
+ HOME: "Home",
+ READ: "Read",
+ MUSIC: "Audio",
+ SETTINGS: "Settings",
+ ...overrides.STRINGS,
+ },
+ ...overrides,
+ };
+};
diff --git a/src/common/test-utils/mocks/context.js b/src/common/test-utils/mocks/context.js
new file mode 100644
index 00000000..d00093ea
--- /dev/null
+++ b/src/common/test-utils/mocks/context.js
@@ -0,0 +1,14 @@
+// Mock factory for @common/context
+// Usage in test file:
+// import { createContextMock } from '@common/test-utils/mocks/context';
+// jest.mock("@common/context", () => createContextMock());
+
+export const createContextMock = (themeOverrides = {}) => ({
+ __esModule: true,
+ default: () => ({
+ theme: {
+ colors: { primary: "#00A", ...themeOverrides.colors },
+ staticColors: { WHITE_COLOR: "#FFF", ...themeOverrides.staticColors },
+ },
+ }),
+});
diff --git a/src/common/test-utils/mocks/icons.js b/src/common/test-utils/mocks/icons.js
new file mode 100644
index 00000000..be57883c
--- /dev/null
+++ b/src/common/test-utils/mocks/icons.js
@@ -0,0 +1,27 @@
+// Mock factory for @common/icons
+// Usage in test file:
+// import { createIconsMock } from '@common/test-utils/mocks/icons';
+// jest.mock("@common/icons", () => createIconsMock());
+
+export const createIconsMock = (iconOverrides = {}) => ({
+ HomeIcon: () => null,
+ SettingsIcon: () => null,
+ MusicIcon: () => null,
+ ReadIcon: () => null,
+ ArrowRightIcon: () => null,
+ BackArrowIcon: () => null,
+ BookmarkIcon: () => null,
+ ChevronRight: () => null,
+ CircleIcon: () => null,
+ CloseIcon: () => null,
+ DownloadIcon: () => null,
+ ExpandCollapseIcon: () => null,
+ minusIcon: () => null,
+ MusicNoteIcon: () => null,
+ PauseIcon: () => null,
+ PlayIcon: () => null,
+ plusIcon: () => null,
+ RefreshIcon: () => null,
+ StopIcon: () => null,
+ ...iconOverrides,
+});
diff --git a/src/common/test-utils/mocks/index.js b/src/common/test-utils/mocks/index.js
new file mode 100644
index 00000000..7712541f
--- /dev/null
+++ b/src/common/test-utils/mocks/index.js
@@ -0,0 +1,9 @@
+// Centralized mocks - export all mock factories
+// Usage: Import individual factories from this file or from their specific files
+// See README.md for detailed usage instructions
+
+export { createReactReduxMock, getMockDispatch, getMockState, setMockState } from "./react-redux";
+export { createContextMock } from "./context";
+export { createUseThemedStylesMock } from "./useThemedStyles";
+export { createIconsMock } from "./icons";
+export { createCommonMock } from "./common";
diff --git a/src/common/test-utils/mocks/react-redux.js b/src/common/test-utils/mocks/react-redux.js
new file mode 100644
index 00000000..8e1bcc63
--- /dev/null
+++ b/src/common/test-utils/mocks/react-redux.js
@@ -0,0 +1,35 @@
+// Mock factory for react-redux
+// Usage in test file:
+// import { createReactReduxMock, getMockDispatch, getMockState, setMockState } from '@common/test-utils/mocks/react-redux';
+// const reactReduxMock = createReactReduxMock();
+// jest.mock("react-redux", () => reactReduxMock.mock);
+// Then use getMockDispatch(), getMockState(), setMockState() in your tests
+
+let mockDispatch = jest.fn();
+let mockState = { isAudio: false, currentBani: { id: 123 } };
+
+export const createReactReduxMock = (initialState = {}) => {
+ mockDispatch = jest.fn();
+ mockState = { isAudio: false, currentBani: { id: 123 }, ...initialState };
+
+ return {
+ mockDispatch,
+ mockState,
+ setMockState: (newState) => {
+ mockState = { ...mockState, ...newState };
+ },
+ mock: {
+ useDispatch: () => mockDispatch,
+ useSelector: (selector) => selector(mockState),
+ Provider: ({ children }) => children,
+ connect: (_mapStateToProps, _mapDispatchToProps) => (Component) => Component,
+ },
+ };
+};
+
+// Get current mock state/dispatch (for tests that need to access them)
+export const getMockDispatch = () => mockDispatch;
+export const getMockState = () => mockState;
+export const setMockState = (newState) => {
+ mockState = { ...mockState, ...newState };
+};
diff --git a/src/common/test-utils/mocks/useThemedStyles.js b/src/common/test-utils/mocks/useThemedStyles.js
new file mode 100644
index 00000000..2b2f364e
--- /dev/null
+++ b/src/common/test-utils/mocks/useThemedStyles.js
@@ -0,0 +1,16 @@
+// Mock factory for @common/hooks/useThemedStyles
+// Usage in test file:
+// import { createUseThemedStylesMock } from '@common/test-utils/mocks/useThemedStyles';
+// jest.mock("@common/hooks/useThemedStyles", () => createUseThemedStylesMock());
+
+export const createUseThemedStylesMock = (stylesOverrides = {}) => ({
+ __esModule: true,
+ default: (_createStyles) => () => ({
+ container: { padding: 0 },
+ navigationBar: { flexDirection: "row" },
+ iconContainer: { padding: 8 },
+ activeIconContainer: { borderWidth: 1 },
+ iconText: { fontSize: 10 },
+ ...stylesOverrides,
+ }),
+});
diff --git a/src/common/toast.js b/src/common/toast.js
new file mode 100644
index 00000000..7df70ee3
--- /dev/null
+++ b/src/common/toast.js
@@ -0,0 +1,64 @@
+import Toast from "react-native-toast-message";
+
+/**
+ * Show a toast message to the user
+ * @param {string} message - The message to display
+ * @param {string} type - 'success', 'error', or 'info' (default: 'info')
+ * @param {number} duration - Duration in milliseconds (default: 3000)
+ */
+export const showToast = (message, type = "info", duration = 3000) => {
+ Toast.show({
+ type,
+ text1: message,
+ position: "bottom",
+ visibilityTime: duration,
+ autoHide: true,
+ topOffset: 30,
+ bottomOffset: 40,
+ });
+};
+
+/**
+ * Show an error toast message
+ * @param {string} message - The error message to display
+ */
+export const showErrorToast = (message) => {
+ Toast.show({
+ type: "error",
+ text1: message,
+ position: "bottom",
+ visibilityTime: 3500,
+ autoHide: true,
+ bottomOffset: 40,
+ });
+};
+
+/**
+ * Show a success toast message
+ * @param {string} message - The success message to display
+ */
+export const showSuccessToast = (message) => {
+ Toast.show({
+ type: "success",
+ text1: message,
+ position: "bottom",
+ visibilityTime: 3000,
+ autoHide: true,
+ bottomOffset: 40,
+ });
+};
+
+/**
+ * Show an info toast message
+ * @param {string} message - The info message to display
+ */
+export const showInfoToast = (message) => {
+ Toast.show({
+ type: "info",
+ text1: message,
+ position: "bottom",
+ visibilityTime: 3000,
+ autoHide: true,
+ bottomOffset: 40,
+ });
+};
diff --git a/src/common/utils.js b/src/common/utils.js
new file mode 100644
index 00000000..11c7516c
--- /dev/null
+++ b/src/common/utils.js
@@ -0,0 +1,29 @@
+import { unicode } from "anvaad-js";
+
+const convertToUnicode = (word) => {
+ try {
+ // Check if word is provided and is a string
+ if (!word) {
+ return "";
+ }
+
+ if (typeof word !== "string") {
+ return String(word);
+ }
+
+ // Convert to unicode
+ const result = unicode(word);
+
+ // Check if conversion was successful
+ if (!result) {
+ return word; // Return original word if conversion fails
+ }
+
+ return result;
+ } catch (error) {
+ // Return original word as fallback in case of any error
+ return word;
+ }
+};
+
+export default convertToUnicode;
diff --git a/src/database/db.js b/src/database/db.js
index eb31ea3a..4c02feca 100644
--- a/src/database/db.js
+++ b/src/database/db.js
@@ -8,18 +8,19 @@ export const getBaniList = (language) => {
.then((db) => {
db.transaction((tx) => {
tx.executeSql(
- "SELECT ID, Gurmukhi, Transliterations FROM Banis",
+ "SELECT ID, Gurmukhi, GurmukhiUni, Transliterations FROM Banis",
[],
(_tx, results) => {
const { rows } = results;
const count = rows.length;
const totalResults = [];
for (let i = 0; i < count; i += 1) {
- const { ID, Gurmukhi, Transliterations } = rows.item(i);
+ const { ID, Gurmukhi, GurmukhiUni, Transliterations } = rows.item(i);
totalResults.push({
id: ID,
gurmukhi: Gurmukhi,
+ gurmukhiUni: GurmukhiUni,
translit: getTranslitText(Transliterations, language),
});
}
@@ -41,6 +42,39 @@ export const getBaniList = (language) => {
});
};
+export const getBaniByID = async (baniId) => {
+ try {
+ const db = await initDB();
+ return new Promise((resolve, reject) => {
+ db.transaction((tx) => {
+ tx.executeSql(
+ "SELECT ID, Gurmukhi, GurmukhiUni FROM Banis WHERE ID = ?",
+ [baniId],
+ (_tx, results) => {
+ if (results.rows.length > 0) {
+ const row = results.rows.item(0);
+ resolve({
+ id: row.ID,
+ gurmukhi: row.Gurmukhi,
+ gurmukhiUni: row.GurmukhiUni,
+ });
+ } else {
+ resolve(null);
+ }
+ },
+ (error) => {
+ logError("Error fetching bani by ID:", error);
+ reject(error);
+ }
+ );
+ });
+ });
+ } catch (error) {
+ logError("Error in getBaniByID:", error);
+ return null;
+ }
+};
+
export const getShabadFromID = async (
shabadID,
length,
@@ -72,7 +106,7 @@ export const getShabadFromID = async (
return new Promise((resolve, reject) => {
db.transaction((tx) => {
tx.executeSql(
- `SELECT ID, Seq, header, Paragraph, Gurmukhi, Visraam, Transliterations, Translations
+ `SELECT ID, Seq, header, Paragraph,Gurmukhi, GurmukhiUni, Visraam, Transliterations, Translations
FROM mv_Banis_Shabad
WHERE Bani = ? AND ${baniLength} = 1 AND (MangalPosition IS NULL OR MangalPosition = 'current')
ORDER BY Seq ASC;`,
@@ -86,10 +120,12 @@ export const getShabadFromID = async (
let paragraphHeader = null;
let prevParagraph = null;
let gurmukhi = "";
+ let gurmukhiUni = "";
let transliteration = "";
let englishTranslation = "";
let punjabiTranslation = "";
let spanishTranslation = "";
+ let paragraphSequences = [];
for (let i = 0; i < len; i += 1) {
const row = results.rows.item(i);
@@ -98,12 +134,15 @@ export const getShabadFromID = async (
GurmukhiBisram,
Visraam,
Gurmukhi,
+ GurmukhiUni,
Paragraph,
header,
Transliterations,
Translations,
+ Seq,
} = row;
const gurmukhiLine = Visraam && GurmukhiBisram ? GurmukhiBisram : Gurmukhi;
+ const gurmukhiUniLine = Visraam && GurmukhiBisram ? GurmukhiBisram : GurmukhiUni;
const vishraamPositions = parseVishraamPositions(
JSON.parse(Visraam),
vishraamSource
@@ -114,6 +153,16 @@ export const getShabadFromID = async (
isLarivar,
isLarivarAssist,
});
+ let curGurmukhiUni = createFormattedText(
+ gurmukhiUniLine.split(" "),
+ vishraamPositions,
+ {
+ isVishraam,
+ vishraamOption,
+ isLarivar,
+ isLarivarAssist,
+ }
+ );
const translationJson = JSON.parse(Translations) || {};
const getTranslation = (lang, field) => {
@@ -138,6 +187,17 @@ export const getShabadFromID = async (
);
curGurmukhi = replaced;
}
+ if (
+ (shabadID === constant.CHOPAYI_SAHIB_ID ||
+ shabadID === constant.REHRAAS_SAHIB_ID) &&
+ padcched === constant.MAST_SABH_MAST
+ ) {
+ const replaced = curGurmukhiUni.replace(
+ /เจธเจฎเจพเจชเจคเจฎ เจธเจคเฉ เจธเฉเจญเจฎ เจธเจคเฉ/g,
+ constant.MAST_SABH_MAST_TUKK_UNI
+ );
+ curGurmukhiUni = replaced;
+ }
if (isParagraphMode) {
const isParagraphChange = prevParagraph !== Paragraph;
@@ -148,51 +208,61 @@ export const getShabadFromID = async (
paragraphResults.push({
id: `${paragraphId}`,
gurmukhi,
+ gurmukhiUni,
translit: transliteration,
englishTranslations: englishTranslation,
punjabiTranslations: punjabiTranslation,
spanishTranslations: spanishTranslation,
header: paragraphHeader,
+ sequences: paragraphSequences, // Add sequence information
});
}
paragraphId = ID;
paragraphHeader = header;
gurmukhi = curGurmukhi;
+ gurmukhiUni = curGurmukhiUni;
transliteration = translit;
englishTranslation = English;
punjabiTranslation = Punjabi;
spanishTranslation = Spanish;
prevParagraph = Paragraph;
+ paragraphSequences = [Seq]; // Start new sequence array with first verse ID
} else {
const space = isLarivar ? "" : " ";
gurmukhi += `${space}${curGurmukhi}`;
+ gurmukhiUni += `${space}${curGurmukhiUni}`;
transliteration += ` ${translit}`;
englishTranslation += ` ${English}`;
punjabiTranslation += ` ${Punjabi}`;
spanishTranslation += ` ${Spanish}`;
+ paragraphSequences.push(Seq); // Add this verse's ID to sequences
}
if (isLastIteration) {
paragraphResults.push({
id: `${paragraphId}`,
gurmukhi,
+ gurmukhiUni,
translit: transliteration,
englishTranslations: englishTranslation,
punjabiTranslations: punjabiTranslation,
spanishTranslations: spanishTranslation,
header: paragraphHeader,
+ sequences: paragraphSequences, // Add sequence information
});
}
} else {
totalResults.push({
id: `${ID}`,
gurmukhi: curGurmukhi,
+ gurmukhiUni: curGurmukhiUni,
translit,
englishTranslations: English,
punjabiTranslations: Punjabi,
spanishTranslations: Spanish,
header,
+ sequence: Seq,
});
}
}
@@ -254,7 +324,7 @@ export const getBookmarksForID = (baniId, length, language) => {
.then((db) => {
db.transaction((tx) => {
tx.executeSql(
- `SELECT ID, BaniShabadID, Seq, Gurmukhi, Transliterations, TukGurmukhi, TukTransliterations FROM Banis_Bookmarks WHERE Bani = ${baniId} AND BaniShabadID in (SELECT ID from mv_Banis_Shabad where Bani = ${baniId} AND ${baniLength} = 1)` +
+ `SELECT ID, BaniShabadID, Seq, Gurmukhi, GurmukhiUni, Transliterations, TukGurmukhi, TukGurmukhiUni, TukTransliterations FROM Banis_Bookmarks WHERE Bani = ${baniId} AND BaniShabadID in (SELECT ID from mv_Banis_Shabad where Bani = ${baniId} AND ${baniLength} = 1)` +
` ORDER BY Seq ASC;`,
[],
(_tx, results) => {
@@ -264,6 +334,8 @@ export const getBookmarksForID = (baniId, length, language) => {
return {
shabadID: row.BaniShabadID,
gurmukhi: row.Gurmukhi,
+ gurmukhiUni: row.GurmukhiUni,
+ tukGurmukhiUni: row.TukGurmukhiUni,
tukGurmukhi: row.TukGurmukhi,
translit: getTranslitText(row.Transliterations, language),
tukTranslit: row.TukTransliterations
diff --git a/src/navigation/index.jsx b/src/navigation/index.jsx
index ed3f11ae..f9597bee 100644
--- a/src/navigation/index.jsx
+++ b/src/navigation/index.jsx
@@ -1,50 +1,68 @@
-import React from "react";
+import React, { useRef } from "react";
import { NavigationContainer } from "@react-navigation/native";
import { createNativeStackNavigator } from "@react-navigation/native-stack";
-import { useSelector } from "react-redux";
-import { Icon } from "@rneui/themed";
-import { colors, navigationRef, stopTrace, resetTrace, startPerformanceTrace } from "@common";
-import HomeScreen from "../HomeScreen";
-import Reader from "../ReaderScreen";
-import Settings from "../Settings";
+import { navigationRef, stopTrace, resetTrace, startPerformanceTrace, logError } from "@common";
import AboutScreen from "../AboutScreen";
-import EditBaniOrder from "../EditBaniOrder";
import Bookmarks from "../Bookmarks";
-import ReminderOptions from "../Settings/components/reminders/ReminderOptions";
-import FolderScreen from "../FolderScreen";
-import { SettingsStyle } from "./style";
+import { trackScreenView } from "../common/firebase/analytics";
import DatabaseUpdateScreen from "../DatabaseUpdate";
+import EditBaniOrder from "../EditBaniOrder";
+import FolderScreen from "../FolderScreen";
+import HomeScreen from "../HomeScreen";
+import ReaderScreen from "../ReaderScreen";
+import Settings from "../Settings";
+import ReminderOptions from "../Settings/components/reminders/ReminderOptions";
const Stack = createNativeStackNavigator();
-const headerLeft = (navigation, isNightMode) => (
- navigation.goBack()}
- color={isNightMode ? colors.TOOLBAR_TINT : colors.TOOLBAR_TINT_DARK}
- />
-);
const Navigation = () => {
- const trace = React.useRef(null);
- const isNightMode = useSelector((state) => state.isNightMode);
- const settingsStyle = SettingsStyle(isNightMode);
- const { headerTitleStyle, headerStyle } = settingsStyle;
+ const routeNameRef = useRef();
+ const trace = useRef(null);
const handlingStateChange = async (state) => {
- if (trace.current) {
- await stopTrace(trace.current);
+ try {
+ if (trace.current) {
+ await stopTrace(trace.current);
+ trace.current = resetTrace();
+ }
+ const currentRouteName = state.routes[state.index].name;
+ trace.current = await startPerformanceTrace(currentRouteName);
+ } catch (error) {
+ // Silently fail - performance monitoring should never crash the app
+ logError(
+ new Error(
+ `Performance trace failed for route: ${state.routes[state.index]?.name || "unknown"} - ${
+ error?.message || "Unknown error"
+ }`
+ )
+ );
trace.current = resetTrace();
}
- const currentRouteName = state.routes[state.index].name;
- trace.current = await startPerformanceTrace(currentRouteName);
+ };
+
+ const handleStateChange = async (state) => {
+ await handlingStateChange(state);
+ const previousRouteName = routeNameRef.current;
+ const currentRouteName = navigationRef.current.getCurrentRoute().name;
+ const currentRoute = navigationRef.current.getCurrentRoute();
+ if (previousRouteName !== currentRouteName) {
+ await trackScreenView(
+ currentRouteName,
+ currentRoute?.params?.key,
+ currentRoute?.params?.params?.title
+ );
+ }
+ routeNameRef.current = currentRouteName;
};
return (
{
- handlingStateChange(state);
+ onReady={() => {
+ routeNameRef.current = navigationRef.current.getCurrentRoute().name;
+ }}
+ onStateChange={async (state) => {
+ await handleStateChange(state);
}}
>
{
name="Home"
component={HomeScreen}
/>
-
- ({
- headerLeft: () => headerLeft(navigation, isNightMode),
- headerTitleStyle,
- headerStyle,
- })}
- name="Settings"
- component={Settings}
- />
+
+
({
+const SettingsStyle = (theme) => ({
headerTitleStyle: {
- color: !isNightMode ? colors.NIGHT_BLACK : colors.WHITE_COLOR,
+ color: theme.colors.primaryText,
fontWeight: "normal",
},
headerStyle: {
- backgroundColor: !isNightMode ? colors.TOOLBAR_COLOR_ALT : colors.TOOLBAR_COLOR_ALT_NIGHT_MODE,
+ backgroundColor: theme.colors.surface,
},
});
+
+export default SettingsStyle;
diff --git a/src/navigation/util.js b/src/navigation/util.js
deleted file mode 100644
index 636ddf7d..00000000
--- a/src/navigation/util.js
+++ /dev/null
@@ -1,16 +0,0 @@
-import { createNavigationContainerRef } from "@react-navigation/native";
-import { constant } from "@common";
-
-export const navigationRef = createNavigationContainerRef();
-
-export const navigate = (name, params) => {
- if (navigationRef.isReady()) {
- navigationRef.navigate(name, params);
- }
-};
-
-export const navigateTo = (incoming) => {
- const { data } = incoming.notification;
- const params = { key: `Reader-${data.id}`, params: { id: data.id, title: data.gurmukhi } };
- navigate(constant.READER, params);
-};
diff --git a/src/services/TrackPlayerService.js b/src/services/TrackPlayerService.js
new file mode 100644
index 00000000..f95cbf90
--- /dev/null
+++ b/src/services/TrackPlayerService.js
@@ -0,0 +1,5 @@
+module.exports = async function () {
+ // This service needs to be registered for the module to work
+ // but it will be used to handle remote-control center commands
+ // or other player events
+};
diff --git a/src/services/audioApi.js b/src/services/audioApi.js
new file mode 100644
index 00000000..9e23d7db
--- /dev/null
+++ b/src/services/audioApi.js
@@ -0,0 +1,68 @@
+import { constant, showErrorToast, STRINGS } from "@common";
+
+// Common API configuration
+const getApiConfig = () => {
+ const { BASIC_AUTH_USERNAME, BASIC_AUTH_PASSWORD, REMOTE_AUDIO_API_URL } = constant;
+ const credentials = btoa(`${BASIC_AUTH_USERNAME}:${BASIC_AUTH_PASSWORD}`);
+ return {
+ baseUrl: REMOTE_AUDIO_API_URL,
+ headers: {
+ Authorization: `Basic ${credentials}`,
+ "Content-Type": "application/json",
+ },
+ };
+};
+
+// Generic API request function
+const makeApiRequest = async (endpoint, options = {}) => {
+ try {
+ const config = getApiConfig();
+ const fullUrl = `${config.baseUrl}${endpoint}`;
+
+ const response = await fetch(fullUrl, {
+ method: "GET",
+ headers: config.headers,
+ timeout: 15000, // 15 second timeout for slow networks
+ ...options,
+ });
+
+ if (!response.ok) {
+ return null;
+ }
+
+ const data = await response.json();
+ return data;
+ } catch (error) {
+ // Network error - show toast and continue without audio features
+ showErrorToast(STRINGS.NETWORK_ERROR);
+ return null;
+ }
+};
+
+// Artist data mapper
+const mapArtistData = (artist) => ({
+ key: artist.artist_id.toString(),
+ title: artist.display_name,
+ artist_id: artist.artist_id,
+ display_name: artist.display_name,
+ description: artist.description,
+});
+
+export const fetchManifest = async (baniId) => {
+ const data = await makeApiRequest(`/banis/${baniId}`);
+
+ if (!data?.data?.length) {
+ return null;
+ }
+ return data;
+};
+
+export const fetchArtists = async () => {
+ const data = await makeApiRequest("/artists");
+
+ if (data?.status === "success" && data.data) {
+ return data.data.map(mapArtistData);
+ }
+ showErrorToast(STRINGS.COULD_NOT_LOAD_AUDIO_ARTISTS);
+ return null;
+};
diff --git a/src/services/dummyData.js b/src/services/dummyData.js
new file mode 100644
index 00000000..23142eec
--- /dev/null
+++ b/src/services/dummyData.js
@@ -0,0 +1,198 @@
+/*
+2. Japji sahib baani
+3. Shabad hajarey
+4. Jaap Sahib
+6. Savaiye
+9. chaupai sahib
+10. Anand Sahib
+21. Rehras Sahib
+23. Sohila Sahib
+ */
+const bhajiAmarjeetSingh = { display_name: "Bhai Amarjeet Singh", artist_id: 2 };
+const bhajiPreetamSingh = { display_name: "Bhai Preetam Singh Ji Anjaan", artist_id: 3 };
+const bhaiJarnailSingh = { display_name: "Bhai Jarnail Singh Ji", artist_id: 4 };
+const bhaiSatnamSinghZira = { display_name: "Bhai Satnam Singh Zira", artist_id: 5 };
+const bibiJaspreetKaur = { display_name: "Bibi Jaspreet Kaur Patiala", artist_id: 6 };
+const bhaiHarpreetSingh = { display_name: "Bhai Harpreet Singh Ji Sangrur", artist_id: 7 };
+
+const dummyArtists = [
+ bhajiAmarjeetSingh,
+ bhajiPreetamSingh,
+ bhaiJarnailSingh,
+ bhaiSatnamSinghZira,
+ bibiJaspreetKaur,
+ bhaiHarpreetSingh,
+];
+
+const dummyData = {
+ 2: [
+ // Japji sahib baani
+
+ {
+ bani_id: 2,
+ track_id: 5,
+ track_url:
+ "https://raw.githubusercontent.com/amitojsingh/SG_audio/main//BhaiJarnailSingh/japji-sahib.mp3",
+ track_length_seconds: 1709,
+ track_size_mb: "27.50",
+ artist_name: bhaiJarnailSingh.display_name,
+ artist_id: bhaiJarnailSingh.artist_id,
+ },
+ {
+ bani_id: 2,
+ track_id: 6,
+ track_url:
+ "https://raw.githubusercontent.com/amitojsingh/SG_audio/main//BhaiHarpreetSinghSangrur/JapjiSahib.mp3",
+ track_length_seconds: 1709,
+ track_size_mb: "27.50",
+ artist_name: bhaiHarpreetSingh.display_name,
+ artist_id: bhaiHarpreetSingh.artist_id,
+ },
+ {
+ bani_id: 2,
+ track_id: 7,
+ track_url:
+ "https://raw.githubusercontent.com/amitojsingh/SG_audio/main//BhaiSatnamSinghZira/JapjiSahib.mp3",
+ track_length_seconds: 1709,
+ track_size_mb: "27.50",
+ artist_name: bhaiSatnamSinghZira.display_name,
+ artist_id: bhaiSatnamSinghZira.artist_id,
+ },
+ {
+ bani_id: 2,
+ track_id: 8,
+ track_url:
+ "https://raw.githubusercontent.com/amitojsingh/SG_audio/main//BibiJaspreetKaur/JapjiSahib.mp3",
+ track_length_seconds: 1709,
+ track_size_mb: "27.50",
+ artist_name: bibiJaspreetKaur.display_name,
+ artist_id: bibiJaspreetKaur.artist_id,
+ },
+ {
+ bani_id: 2,
+ track_id: 2,
+ track_url: "https://raw.githubusercontent.com/amitojsingh/SG_audio/main//amarjeetSingh/2.mp3",
+ track_length_seconds: 918,
+ track_size_mb: "3.70",
+ artist_name: bhajiAmarjeetSingh.display_name,
+ artist_id: bhajiAmarjeetSingh.artist_id,
+ },
+ {
+ bani_id: 2,
+ track_id: 3,
+ track_url: "https://raw.githubusercontent.com/amitojsingh/SG_audio/main//preetamSingh/2.mp3",
+ track_length_seconds: 1709,
+ track_size_mb: "27.50",
+ artist_name: bhajiPreetamSingh.display_name,
+ artist_id: bhajiPreetamSingh.artist_id,
+ },
+ ],
+ 3: [
+ // Shabad hajarey
+ {
+ bani_id: 3,
+ track_id: 3,
+ track_url: "https://raw.githubusercontent.com/amitojsingh/SG_audio/main//preetamSingh/3.mp3",
+ track_length_seconds: 1709,
+ track_size_mb: "27.50",
+ artist_name: "Bhai Preetam Singh Ji Anjaan",
+ artist_id: 3,
+ },
+ ],
+ 4: [
+ // Jaap Sahib
+ {
+ bani_id: 4,
+ track_id: 9,
+ track_url:
+ "https://raw.githubusercontent.com/amitojsingh/SG_audio/main//BhaiJarnailSingh/jaap-sahib.mp3",
+ track_length_seconds: 1709,
+ track_size_mb: "27.50",
+ artist_name: bhaiJarnailSingh.display_name,
+ artist_id: bhaiJarnailSingh.artist_id,
+ },
+ {
+ bani_id: 4,
+ track_id: 10,
+ track_url:
+ "https://raw.githubusercontent.com/amitojsingh/SG_audio/main//BhaiHarpreetSinghSangrur/JaapSahib.mp3",
+ track_length_seconds: 1709,
+ track_size_mb: "27.50",
+ artist_name: bhaiHarpreetSingh.display_name,
+ artist_id: bhaiHarpreetSingh.artist_id,
+ },
+ {
+ bani_id: 4,
+ track_id: 11,
+ track_url:
+ "https://raw.githubusercontent.com/amitojsingh/SG_audio/main//BhaiSatnamSinghZira/JaapSahib.mp3",
+ track_length_seconds: 1709,
+ track_size_mb: "27.50",
+ artist_name: bhaiSatnamSinghZira.display_name,
+ artist_id: bhaiSatnamSinghZira.artist_id,
+ },
+ ],
+ 9: [
+ // Chaupai Sahib
+ {
+ bani_id: 9,
+ track_id: 12,
+ track_url:
+ "https://raw.githubusercontent.com/amitojsingh/SG_audio/main//BhaiJarnailSingh/chopai-sahib.mp3",
+ track_length_seconds: 1709,
+ track_size_mb: "27.50",
+ artist_name: bhaiJarnailSingh.display_name,
+ artist_id: bhaiJarnailSingh.artist_id,
+ },
+ ],
+ 10: [
+ // Anand Sahib
+ {
+ bani_id: 10,
+ track_id: 13,
+ track_url:
+ "https://raw.githubusercontent.com/amitojsingh/SG_audio/main//BhaiJarnailSingh/anand-sahib.mp3",
+ track_length_seconds: 1709,
+ track_size_mb: "27.50",
+ artist_name: bhaiJarnailSingh.display_name,
+ artist_id: bhaiJarnailSingh.artist_id,
+ },
+ ],
+ 21: [
+ // Rehras Sahib
+ {
+ bani_id: 21,
+ track_id: 14,
+ track_url:
+ "https://raw.githubusercontent.com/amitojsingh/SG_audio/main//BhaiJarnailSingh/Rehras-sahib.mp3",
+ track_length_seconds: 1709,
+ track_size_mb: "27.50",
+ artist_name: bhaiJarnailSingh.display_name,
+ artist_id: bhaiJarnailSingh.artist_id,
+ },
+ {
+ bani_id: 21,
+ track_id: 15,
+ track_url:
+ "https://raw.githubusercontent.com/amitojsingh/SG_audio/main//BibiJaspreetKaur/RehrasSahib.mp3",
+ track_length_seconds: 1709,
+ track_size_mb: "27.50",
+ artist_name: bibiJaspreetKaur.display_name,
+ artist_id: bibiJaspreetKaur.artist_id,
+ },
+ ],
+ 23: [
+ // Sohila Sahib
+ {
+ bani_id: 23,
+ track_id: 16,
+ track_url:
+ "https://raw.githubusercontent.com/amitojsingh/SG_audio/main//BhaiJarnailSingh/kirtan-sohaila.mp3",
+ track_length_seconds: 1709,
+ track_size_mb: "27.50",
+ artist_name: bhaiJarnailSingh.display_name,
+ artist_id: bhaiJarnailSingh.artist_id,
+ },
+ ],
+};
+export { dummyData, dummyArtists };
diff --git a/src/services/index.js b/src/services/index.js
new file mode 100644
index 00000000..a0d25799
--- /dev/null
+++ b/src/services/index.js
@@ -0,0 +1,3 @@
+import { fetchArtists, fetchManifest } from "./audioApi";
+
+export { fetchArtists, fetchManifest };
diff --git a/src/setupTests.js b/src/setupTests.js
new file mode 100644
index 00000000..73633ba5
--- /dev/null
+++ b/src/setupTests.js
@@ -0,0 +1,48 @@
+/* eslint-env jest */
+// Jest setup file - runs before all tests
+// This centralizes common mocks so you don't have to repeat them in every test file
+
+// Mock react-redux hooks (factory functions are called inside jest.mock)
+jest.mock("react-redux", () => {
+ const { createReactReduxMock } = require("@common/test-utils/mocks/react-redux");
+ return createReactReduxMock().mock;
+});
+
+// Mock theme/context - useTheme is the default export
+jest.mock("@common/context", () => {
+ const { createContextMock } = require("@common/test-utils/mocks/context");
+ return createContextMock();
+});
+
+// Mock useThemedStyles to return a stable style object
+jest.mock("@common/hooks/useThemedStyles", () => {
+ const { createUseThemedStylesMock } = require("@common/test-utils/mocks/useThemedStyles");
+ return createUseThemedStylesMock();
+});
+
+// Mock icons to simple components
+jest.mock("@common/icons", () => {
+ const { createIconsMock } = require("@common/test-utils/mocks/icons");
+ return createIconsMock();
+});
+
+// Mock @common exports
+jest.mock("@common", () => {
+ const { createCommonMock } = require("@common/test-utils/mocks/common");
+ return createCommonMock();
+});
+
+// Mock react-native-fs to avoid TypeScript parsing issues
+jest.mock("react-native-fs", () => ({
+ DocumentDirectoryPath: "/test/documents",
+ exists: jest.fn(() => Promise.resolve(false)),
+ readFile: jest.fn(() => Promise.resolve("")),
+ writeFile: jest.fn(() => Promise.resolve()),
+ unlink: jest.fn(() => Promise.resolve()),
+ mkdir: jest.fn(() => Promise.resolve()),
+ downloadFile: jest.fn(() => Promise.resolve({ statusCode: 200 })),
+ moveFile: jest.fn(() => Promise.resolve()),
+ copyFile: jest.fn(() => Promise.resolve()),
+ readDir: jest.fn(() => Promise.resolve([])),
+ stat: jest.fn(() => Promise.resolve({ isFile: () => true, size: 0 })),
+}));
diff --git a/src/theme/borderRadius.js b/src/theme/borderRadius.js
new file mode 100644
index 00000000..ba9d9b6e
--- /dev/null
+++ b/src/theme/borderRadius.js
@@ -0,0 +1,8 @@
+const borderRadius = {
+ sm: 15,
+ md: 20,
+ lg: 30,
+ xl: 40,
+};
+
+export default borderRadius;
diff --git a/src/theme/components.js b/src/theme/components.js
new file mode 100644
index 00000000..377f263c
--- /dev/null
+++ b/src/theme/components.js
@@ -0,0 +1,40 @@
+import spacing from "./spacing";
+
+const components = {
+ header: {
+ height: 56,
+ paddingHorizontal: spacing.lg,
+ paddingVertical: spacing.md,
+ },
+ button: {
+ paddingHorizontal: spacing.lg,
+ paddingVertical: spacing.md,
+ borderRadius: 8,
+ minHeight: 44,
+ },
+ card: {
+ padding: spacing.lg,
+ marginVertical: spacing.sm,
+ marginHorizontal: spacing.md,
+ borderRadius: 12,
+ },
+ list: {
+ itemPadding: spacing.lg,
+ itemMargin: spacing.sm,
+ sectionHeaderPadding: spacing.md,
+ },
+ modal: {
+ padding: spacing.xl,
+ borderRadius: 16,
+ },
+ input: {
+ paddingHorizontal: spacing.md,
+ paddingVertical: spacing.md,
+ borderRadius: 8,
+ minHeight: 44,
+ },
+ bottomNavigation: {
+ height: 65,
+ },
+};
+export default components;
diff --git a/src/theme/darkTheme.js b/src/theme/darkTheme.js
new file mode 100644
index 00000000..50c72191
--- /dev/null
+++ b/src/theme/darkTheme.js
@@ -0,0 +1,55 @@
+import borderRadius from "./borderRadius";
+import components from "./components";
+import spacing from "./spacing";
+import staticColors from "./staticColors";
+import typography from "./typography";
+
+const darkTheme = {
+ mode: "dark",
+ colors: {
+ primary: "#113979",
+ surface: "rgba(18, 18, 18, 1)",
+ primaryText: "#faf9f6",
+ primaryVariant: "#99852c",
+ surfaceGrey: "#464646",
+ textDisabled: "#faf9f6",
+ underlayColor: "#009bff",
+ headerVariant: "#003436",
+ baniDB: "#eaa040",
+ shadow: "#fff",
+ highlightTuk: "#77baff",
+ activeView: "#2d2d2d",
+ inactiveView: "#232323",
+ componentColor: "#fefefe",
+ enabledText: "#2581df",
+ disabledText: "#a3a3a3",
+ primaryHeader: "#121212",
+ primaryHeaderVariant: "#faf9f6",
+ actionButton: "#121F35",
+ audioPlayer: "#BED2F2",
+ overlay: staticColors.NIGHT_BLACK,
+ audioTitleText: "#BED2F2",
+ trackBorderColor: "#464646",
+ trackBackgroundColor: "rgba(37, 105, 214, 0.2)",
+ controlBarBackgroundColor: "#000000",
+ separator: "rgba(190, 210, 242, 0.23)",
+ transparentOverlay: "rgba(18, 18, 18, 0.95)",
+ audioSettingsModalText: "#faf9f6",
+ },
+ typography,
+ spacing,
+ components,
+ staticColors,
+ radius: {
+ sm: 6,
+ md: 10,
+ lg: 16,
+ },
+ images: {
+ khalisLogo: require("../../images/khalislogo150white.png"),
+ baniDBLogo: require("../../images/banidblogo.png"),
+ },
+ borderRadius,
+};
+
+export default darkTheme;
diff --git a/src/theme/index.js b/src/theme/index.js
new file mode 100644
index 00000000..75d7a932
--- /dev/null
+++ b/src/theme/index.js
@@ -0,0 +1,2 @@
+export { default as lightTheme } from "./lightTheme";
+export { default as darkTheme } from "./darkTheme";
diff --git a/src/theme/lightTheme.js b/src/theme/lightTheme.js
new file mode 100644
index 00000000..83753ffd
--- /dev/null
+++ b/src/theme/lightTheme.js
@@ -0,0 +1,55 @@
+import borderRadius from "./borderRadius";
+import components from "./components";
+import spacing from "./spacing";
+import staticColors from "./staticColors";
+import typography from "./typography";
+
+const lightTheme = {
+ mode: "light",
+ colors: {
+ primary: "#113979",
+ surface: "rgba(255, 255, 255, 1)",
+ primaryText: "#121212",
+ primaryVariant: "#DEBB0A",
+ surfaceGrey: "#faf9f6",
+ textDisabled: "#a3a3a3",
+ underlayColor: "#009bff",
+ headerVariant: "#003436",
+ baniDB: "#eaa040",
+ shadow: "#000",
+ highlightTuk: "#0066ff",
+ activeView: "#C7C7D7",
+ inactiveView: "#e9e9ee",
+ componentColor: "#232323",
+ enabledText: "#0066ff",
+ disabledText: "#a3a3a3",
+ primaryHeader: "#113979",
+ primaryHeaderVariant: "#113979",
+ actionButton: "#D3E1F7",
+ audioPlayer: "rgba(17, 57, 121, 0.5)",
+ overlay: staticColors.SEMI_TRANSPARENT,
+ audioTitleText: "#113979",
+ trackBorderColor: staticColors.TRACK_COLOR,
+ trackBackgroundColor: staticColors.TRACK_COLOR,
+ controlBarBackgroundColor: "#ffffff",
+ separator: "#eeeeee",
+ transparentOverlay: "rgba(255, 255, 255, 0.95)",
+ audioSettingsModalText: "#666666",
+ },
+ staticColors,
+ typography,
+ spacing,
+ components,
+ radius: {
+ sm: 6,
+ md: 10,
+ lg: 16,
+ },
+ images: {
+ khalisLogo: require("../../images/khalislogo150.png"),
+ baniDBLogo: require("../../images/banidblogo.png"),
+ },
+ borderRadius,
+};
+
+export default lightTheme;
diff --git a/src/theme/spacing.js b/src/theme/spacing.js
new file mode 100644
index 00000000..1f1770d0
--- /dev/null
+++ b/src/theme/spacing.js
@@ -0,0 +1,14 @@
+const spacing = {
+ xs: 2,
+ sm: 4,
+ md: 8,
+ md_12: 12,
+ lg: 16,
+ xl: 24,
+ xl_20: 20,
+ xxl: 32,
+ xxxl: 48,
+ huge: 64,
+};
+
+export default spacing;
diff --git a/src/theme/staticColors.js b/src/theme/staticColors.js
new file mode 100644
index 00000000..db8ae6e0
--- /dev/null
+++ b/src/theme/staticColors.js
@@ -0,0 +1,15 @@
+const staticColors = {
+ WHITE_COLOR: "#faf9f6", // used on Home Screen Header Ik ongkar and title
+ NIGHT_BLACK: "#121212",
+ NIGHT_OPACITY_BLACK: "rgba(0, 0, 0, 0.5)",
+ TERTIARY_COLOR: "#EEEEEE",
+ SEMI_TRANSPARENT: "rgba(255, 255, 255, 0.6)",
+ LIGHT_GRAY: "#e0e0e0",
+ TRACK_COLOR: "rgba(17, 57, 121, 0.1)",
+ SLIDER_TRACK_COLOR: "rgba(238, 238, 238, 0.93)",
+ HIGHLIGHT_COLOR: "rgba(69, 133, 235, 0.1)",
+ SWITCH_BACKGROUND_COLOR: "#3e3e3e",
+ SWITCH_THUMB_COLOR: "#767577",
+};
+
+export default staticColors;
diff --git a/src/theme/typography.js b/src/theme/typography.js
new file mode 100644
index 00000000..5226ee50
--- /dev/null
+++ b/src/theme/typography.js
@@ -0,0 +1,46 @@
+import { constant } from "@common";
+
+const typography = {
+ // Font sizes
+ sizes: {
+ xs: 10,
+ sm: 12,
+ md: 14,
+ lg: 16,
+ xl: 18,
+ xxl: 20,
+ xxxl: 24,
+ huge: 28,
+ massive: 32,
+ // Legacy sizes for backward compatibility
+ heading: 20,
+ subheading: 18,
+ body: 16,
+ caption: 12,
+ },
+ // Line heights
+ lineHeights: {
+ tight: 1.2,
+ normal: 1.4,
+ relaxed: 1.6,
+ loose: 1.8,
+ },
+ // Font weights
+ weights: {
+ light: "300",
+ normal: "400",
+ medium: "500",
+ semibold: "600",
+ bold: "700",
+ },
+ // Font families
+ fonts: {
+ gurbaniPrimary: constant.GURBANI_AKHAR_TRUE,
+ gurbaniThick: constant.GURBANI_AKHAR_THICK_TRUE,
+ balooPaaji: constant.BALOO_PAAJI,
+ balooPaajiSemiBold: constant.BALOO_PAAJI_SEMI_BOLD,
+ // Additional fonts can be added as constants are defined
+ },
+};
+
+export default typography;
diff --git a/yarn.lock b/yarn.lock
index c05efe65..ffe9e3a8 100644
--- a/yarn.lock
+++ b/yarn.lock
@@ -1101,15 +1101,26 @@
resolved "https://registry.yarnpkg.com/@eslint/js/-/js-9.29.0.tgz#dc6fd117c19825f8430867a662531da36320fe56"
integrity sha512-3PIF4cBw/y+1u2EazflInpV+lYsSG0aByVIQzAgb1m1MhHFSbqTyNqtBKHgWf/9Ykud+DhILS9EGkmekVhbKoQ==
-"@firebase/analytics-compat@0.2.17":
- version "0.2.17"
- resolved "https://registry.yarnpkg.com/@firebase/analytics-compat/-/analytics-compat-0.2.17.tgz#c3cfc8ffb863d574ec26d86f9c8344d752832995"
- integrity sha512-SJNVOeTvzdqZQvXFzj7yAirXnYcLDxh57wBFROfeowq/kRN1AqOw1tG6U4OiFOEhqi7s3xLze/LMkZatk2IEww==
+"@firebase/ai@2.1.0":
+ version "2.1.0"
+ resolved "https://registry.yarnpkg.com/@firebase/ai/-/ai-2.1.0.tgz#5da826e6a17889b8a8f896470e34450e8d22fa3a"
+ integrity sha512-4HvFr4YIzNFh0MowJLahOjJDezYSTjQar0XYVu/sAycoxQ+kBsfXuTPRLVXCYfMR5oNwQgYe4Q2gAOYKKqsOyA==
+ dependencies:
+ "@firebase/app-check-interop-types" "0.3.3"
+ "@firebase/component" "0.7.0"
+ "@firebase/logger" "0.5.0"
+ "@firebase/util" "1.13.0"
+ tslib "^2.1.0"
+
+"@firebase/analytics-compat@0.2.24":
+ version "0.2.24"
+ resolved "https://registry.yarnpkg.com/@firebase/analytics-compat/-/analytics-compat-0.2.24.tgz#806c34ddd5c4869006eead08bfde575972d73ce2"
+ integrity sha512-jE+kJnPG86XSqGQGhXXYt1tpTbCTED8OQJ/PQ90SEw14CuxRxx/H+lFbWA1rlFtFSsTCptAJtgyRBwr/f00vsw==
dependencies:
- "@firebase/analytics" "0.10.11"
+ "@firebase/analytics" "0.10.18"
"@firebase/analytics-types" "0.8.3"
- "@firebase/component" "0.6.12"
- "@firebase/util" "1.10.3"
+ "@firebase/component" "0.7.0"
+ "@firebase/util" "1.13.0"
tslib "^2.1.0"
"@firebase/analytics-types@0.8.3":
@@ -1117,27 +1128,27 @@
resolved "https://registry.yarnpkg.com/@firebase/analytics-types/-/analytics-types-0.8.3.tgz#d08cd39a6209693ca2039ba7a81570dfa6c1518f"
integrity sha512-VrIp/d8iq2g501qO46uGz3hjbDb8xzYMrbu8Tp0ovzIzrvJZ2fvmj649gTjge/b7cCCcjT0H37g1gVtlNhnkbg==
-"@firebase/analytics@0.10.11":
- version "0.10.11"
- resolved "https://registry.yarnpkg.com/@firebase/analytics/-/analytics-0.10.11.tgz#6896413e92613573af775c45050af889a43676da"
- integrity sha512-zwuPiRE0+hgcS95JZbJ6DFQN4xYFO8IyGxpeePTV51YJMwCf3lkBa6FnZ/iXIqDKcBPMgMuuEZozI0BJWaLEYg==
+"@firebase/analytics@0.10.18":
+ version "0.10.18"
+ resolved "https://registry.yarnpkg.com/@firebase/analytics/-/analytics-0.10.18.tgz#930d43504a02fe0128a8d82f8c5361911b0dbd04"
+ integrity sha512-iN7IgLvM06iFk8BeFoWqvVpRFW3Z70f+Qe2PfCJ7vPIgLPjHXDE774DhCT5Y2/ZU/ZbXPDPD60x/XPWEoZLNdg==
dependencies:
- "@firebase/component" "0.6.12"
- "@firebase/installations" "0.6.12"
- "@firebase/logger" "0.4.4"
- "@firebase/util" "1.10.3"
+ "@firebase/component" "0.7.0"
+ "@firebase/installations" "0.6.19"
+ "@firebase/logger" "0.5.0"
+ "@firebase/util" "1.13.0"
tslib "^2.1.0"
-"@firebase/app-check-compat@0.3.18":
- version "0.3.18"
- resolved "https://registry.yarnpkg.com/@firebase/app-check-compat/-/app-check-compat-0.3.18.tgz#abe63858fca86b61ea431e0d9e58ccb8bac1b275"
- integrity sha512-qjozwnwYmAIdrsVGrJk+hnF1WBois54IhZR6gO0wtZQoTvWL/GtiA2F31TIgAhF0ayUiZhztOv1RfC7YyrZGDQ==
+"@firebase/app-check-compat@0.4.0":
+ version "0.4.0"
+ resolved "https://registry.yarnpkg.com/@firebase/app-check-compat/-/app-check-compat-0.4.0.tgz#94ac0cf9f66cab1d81a7b14e0c151dcc2684bc95"
+ integrity sha512-UfK2Q8RJNjYM/8MFORltZRG9lJj11k0nW84rrffiKvcJxLf1jf6IEjCIkCamykHE73C6BwqhVfhIBs69GXQV0g==
dependencies:
- "@firebase/app-check" "0.8.11"
+ "@firebase/app-check" "0.11.0"
"@firebase/app-check-types" "0.5.3"
- "@firebase/component" "0.6.12"
- "@firebase/logger" "0.4.4"
- "@firebase/util" "1.10.3"
+ "@firebase/component" "0.7.0"
+ "@firebase/logger" "0.5.0"
+ "@firebase/util" "1.13.0"
tslib "^2.1.0"
"@firebase/app-check-interop-types@0.3.3":
@@ -1150,25 +1161,25 @@
resolved "https://registry.yarnpkg.com/@firebase/app-check-types/-/app-check-types-0.5.3.tgz#38ba954acf4bffe451581a32fffa20337f11d8e5"
integrity sha512-hyl5rKSj0QmwPdsAxrI5x1otDlByQ7bvNvVt8G/XPO2CSwE++rmSVf3VEhaeOR4J8ZFaF0Z0NDSmLejPweZ3ng==
-"@firebase/app-check@0.8.11":
- version "0.8.11"
- resolved "https://registry.yarnpkg.com/@firebase/app-check/-/app-check-0.8.11.tgz#3c67148046fea0a0a9a1eecf1a17fdc31a76eda7"
- integrity sha512-42zIfRI08/7bQqczAy7sY2JqZYEv3a1eNa4fLFdtJ54vNevbBIRSEA3fZgRqWFNHalh5ohsBXdrYgFqaRIuCcQ==
+"@firebase/app-check@0.11.0":
+ version "0.11.0"
+ resolved "https://registry.yarnpkg.com/@firebase/app-check/-/app-check-0.11.0.tgz#a7e1d1e3f5ae36eabed1455db937114fe869ce8f"
+ integrity sha512-XAvALQayUMBJo58U/rxW02IhsesaxxfWVmVkauZvGEz3vOAjMEQnzFlyblqkc2iAaO82uJ2ZVyZv9XzPfxjJ6w==
dependencies:
- "@firebase/component" "0.6.12"
- "@firebase/logger" "0.4.4"
- "@firebase/util" "1.10.3"
+ "@firebase/component" "0.7.0"
+ "@firebase/logger" "0.5.0"
+ "@firebase/util" "1.13.0"
tslib "^2.1.0"
-"@firebase/app-compat@0.2.50":
- version "0.2.50"
- resolved "https://registry.yarnpkg.com/@firebase/app-compat/-/app-compat-0.2.50.tgz#665b6db432414229b96763e6b1cf5e38463c58d0"
- integrity sha512-7yD362icKgjoNvFxwth420TNZgqCfuTJ28yQCdpyjC2fXyaZHhAbxVKnHEXGTAaUKSHWxsIy46lBKGi/x/Mflw==
+"@firebase/app-compat@0.5.1":
+ version "0.5.1"
+ resolved "https://registry.yarnpkg.com/@firebase/app-compat/-/app-compat-0.5.1.tgz#38deea05daca4d5903a77e638f016aca0d2990e1"
+ integrity sha512-BEy1L6Ufd85ZSP79HDIv0//T9p7d5Bepwy+2mKYkgdXBGKTbFm2e2KxyF1nq4zSQ6RRBxWi0IY0zFVmoBTZlUA==
dependencies:
- "@firebase/app" "0.11.1"
- "@firebase/component" "0.6.12"
- "@firebase/logger" "0.4.4"
- "@firebase/util" "1.10.3"
+ "@firebase/app" "0.14.1"
+ "@firebase/component" "0.7.0"
+ "@firebase/logger" "0.5.0"
+ "@firebase/util" "1.13.0"
tslib "^2.1.0"
"@firebase/app-types@0.9.3":
@@ -1176,26 +1187,26 @@
resolved "https://registry.yarnpkg.com/@firebase/app-types/-/app-types-0.9.3.tgz#8408219eae9b1fb74f86c24e7150a148460414ad"
integrity sha512-kRVpIl4vVGJ4baogMDINbyrIOtOxqhkZQg4jTq3l8Lw6WSk0xfpEYzezFu+Kl4ve4fbPl79dvwRtaFqAC/ucCw==
-"@firebase/app@0.11.1":
- version "0.11.1"
- resolved "https://registry.yarnpkg.com/@firebase/app/-/app-0.11.1.tgz#20663522be71307c8e5c74828bb621cdcdd3f5b4"
- integrity sha512-Vz4DrNLPfDx3RwQf+4klXtu7OUYDO6xz2hlRyFawWskS7YqdtNzkDDxrqH20KDfjCF1lib46/NgchIj1+8h4wQ==
+"@firebase/app@0.14.1":
+ version "0.14.1"
+ resolved "https://registry.yarnpkg.com/@firebase/app/-/app-0.14.1.tgz#7840b9374f11b2f6585e8bc118870271b6928e39"
+ integrity sha512-jxTrDbxnGoX7cGz7aP9E7v9iKvBbQfZ8Gz4TH3SfrrkcyIojJM3+hJnlbGnGxHrABts844AxRcg00arMZEyA6Q==
dependencies:
- "@firebase/component" "0.6.12"
- "@firebase/logger" "0.4.4"
- "@firebase/util" "1.10.3"
+ "@firebase/component" "0.7.0"
+ "@firebase/logger" "0.5.0"
+ "@firebase/util" "1.13.0"
idb "7.1.1"
tslib "^2.1.0"
-"@firebase/auth-compat@0.5.18":
- version "0.5.18"
- resolved "https://registry.yarnpkg.com/@firebase/auth-compat/-/auth-compat-0.5.18.tgz#ba1674287e6df4f95675866d6f960a9fc4a9abfc"
- integrity sha512-dFBev8AMNb2AgIt9afwf/Ku4/0Wq9R9OFSeBB/xjyJt+RfQ9PnNWqU2oFphews23byLg6jle8twRA7iOYfRGRw==
+"@firebase/auth-compat@0.6.0":
+ version "0.6.0"
+ resolved "https://registry.yarnpkg.com/@firebase/auth-compat/-/auth-compat-0.6.0.tgz#1464ea6049b2ad0aae83b4fdcd5e5e5aba6b1c50"
+ integrity sha512-J0lGSxXlG/lYVi45wbpPhcWiWUMXevY4fvLZsN1GHh+po7TZVng+figdHBVhFheaiipU8HZyc7ljw1jNojM2nw==
dependencies:
- "@firebase/auth" "1.9.0"
+ "@firebase/auth" "1.11.0"
"@firebase/auth-types" "0.13.0"
- "@firebase/component" "0.6.12"
- "@firebase/util" "1.10.3"
+ "@firebase/component" "0.7.0"
+ "@firebase/util" "1.13.0"
tslib "^2.1.0"
"@firebase/auth-interop-types@0.2.4":
@@ -1208,77 +1219,77 @@
resolved "https://registry.yarnpkg.com/@firebase/auth-types/-/auth-types-0.13.0.tgz#ae6e0015e3bd4bfe18edd0942b48a0a118a098d9"
integrity sha512-S/PuIjni0AQRLF+l9ck0YpsMOdE8GO2KU6ubmBB7P+7TJUCQDa3R1dlgYm9UzGbbePMZsp0xzB93f2b/CgxMOg==
-"@firebase/auth@1.9.0":
- version "1.9.0"
- resolved "https://registry.yarnpkg.com/@firebase/auth/-/auth-1.9.0.tgz#eea1ab78fd3d68db3cdef69a0d7fba3663a940c5"
- integrity sha512-Xz2mbEYauF689qXG/4HppS2+/yGo9R7B6eNUBh3H2+XpAZTGdx8d8TFsW/BMTAK9Q95NB0pb1Bbvfx0lwofq8Q==
+"@firebase/auth@1.11.0":
+ version "1.11.0"
+ resolved "https://registry.yarnpkg.com/@firebase/auth/-/auth-1.11.0.tgz#81a4f77b16d97c502e493b2a14a97443e243a2a0"
+ integrity sha512-5j7+ua93X+IRcJ1oMDTClTo85l7Xe40WSkoJ+shzPrX7OISlVWLdE1mKC57PSD+/LfAbdhJmvKixINBw2ESK6w==
dependencies:
- "@firebase/component" "0.6.12"
- "@firebase/logger" "0.4.4"
- "@firebase/util" "1.10.3"
+ "@firebase/component" "0.7.0"
+ "@firebase/logger" "0.5.0"
+ "@firebase/util" "1.13.0"
tslib "^2.1.0"
-"@firebase/component@0.6.12":
- version "0.6.12"
- resolved "https://registry.yarnpkg.com/@firebase/component/-/component-0.6.12.tgz#08905a534e9b769164e7e1b1e80f6e7611eb67f3"
- integrity sha512-YnxqjtohLbnb7raXt2YuA44cC1wA9GiehM/cmxrsoxKlFxBLy2V0OkRSj9gpngAE0UoJ421Wlav9ycO7lTPAUw==
+"@firebase/component@0.7.0":
+ version "0.7.0"
+ resolved "https://registry.yarnpkg.com/@firebase/component/-/component-0.7.0.tgz#3736644fdb6d3572dceae7fdc1c35a8bd3819adc"
+ integrity sha512-wR9En2A+WESUHexjmRHkqtaVH94WLNKt6rmeqZhSLBybg4Wyf0Umk04SZsS6sBq4102ZsDBFwoqMqJYj2IoDSg==
dependencies:
- "@firebase/util" "1.10.3"
+ "@firebase/util" "1.13.0"
tslib "^2.1.0"
-"@firebase/data-connect@0.3.0":
- version "0.3.0"
- resolved "https://registry.yarnpkg.com/@firebase/data-connect/-/data-connect-0.3.0.tgz#5602986c28e2ac94df2499a7cf68ad622957089e"
- integrity sha512-inbLq0JyQD/d02Al3Lso0Hc8z1BVpB3dYSMFcQkeKhYyjn5bspLczLdasPbCOEUp8MOkLblLZhJuRs7Q/spFnw==
+"@firebase/data-connect@0.3.11":
+ version "0.3.11"
+ resolved "https://registry.yarnpkg.com/@firebase/data-connect/-/data-connect-0.3.11.tgz#60a7a9649e4aedd005546032466ef9abc0a544c1"
+ integrity sha512-G258eLzAD6im9Bsw+Qm1Z+P4x0PGNQ45yeUuuqe5M9B1rn0RJvvsQCRHXgE52Z+n9+WX1OJd/crcuunvOGc7Vw==
dependencies:
"@firebase/auth-interop-types" "0.2.4"
- "@firebase/component" "0.6.12"
- "@firebase/logger" "0.4.4"
- "@firebase/util" "1.10.3"
+ "@firebase/component" "0.7.0"
+ "@firebase/logger" "0.5.0"
+ "@firebase/util" "1.13.0"
tslib "^2.1.0"
-"@firebase/database-compat@2.0.3":
- version "2.0.3"
- resolved "https://registry.yarnpkg.com/@firebase/database-compat/-/database-compat-2.0.3.tgz#87f18e814c06d62fea4bfb10d3b833f4259345ca"
- integrity sha512-uHGQrSUeJvsDfA+IyHW5O4vdRPsCksEzv4T4Jins+bmQgYy20ZESU4x01xrQCn/nzqKHuQMEW99CoCO7D+5NiQ==
- dependencies:
- "@firebase/component" "0.6.12"
- "@firebase/database" "1.0.12"
- "@firebase/database-types" "1.0.8"
- "@firebase/logger" "0.4.4"
- "@firebase/util" "1.10.3"
+"@firebase/database-compat@2.1.0":
+ version "2.1.0"
+ resolved "https://registry.yarnpkg.com/@firebase/database-compat/-/database-compat-2.1.0.tgz#c64488d741c6da2ed8dcf02f2e433089dae2f590"
+ integrity sha512-8nYc43RqxScsePVd1qe1xxvWNf0OBnbwHxmXJ7MHSuuTVYFO3eLyLW3PiCKJ9fHnmIz4p4LbieXwz+qtr9PZDg==
+ dependencies:
+ "@firebase/component" "0.7.0"
+ "@firebase/database" "1.1.0"
+ "@firebase/database-types" "1.0.16"
+ "@firebase/logger" "0.5.0"
+ "@firebase/util" "1.13.0"
tslib "^2.1.0"
-"@firebase/database-types@1.0.8":
- version "1.0.8"
- resolved "https://registry.yarnpkg.com/@firebase/database-types/-/database-types-1.0.8.tgz#eddcce594be118bf9aebb043b5a6d51cfb6de620"
- integrity sha512-6lPWIGeufhUq1heofZULyVvWFhD01TUrkkB9vyhmksjZ4XF7NaivQp9rICMk7QNhqwa+uDCaj4j+Q8qqcSVZ9g==
+"@firebase/database-types@1.0.16":
+ version "1.0.16"
+ resolved "https://registry.yarnpkg.com/@firebase/database-types/-/database-types-1.0.16.tgz#262f54b8dbebbc46259757b3ba384224fb2ede48"
+ integrity sha512-xkQLQfU5De7+SPhEGAXFBnDryUWhhlFXelEg2YeZOQMCdoe7dL64DDAd77SQsR+6uoXIZY5MB4y/inCs4GTfcw==
dependencies:
"@firebase/app-types" "0.9.3"
- "@firebase/util" "1.10.3"
+ "@firebase/util" "1.13.0"
-"@firebase/database@1.0.12":
- version "1.0.12"
- resolved "https://registry.yarnpkg.com/@firebase/database/-/database-1.0.12.tgz#4e1807b82dc734df8596eac44d7766ff96c2de24"
- integrity sha512-psFl5t6rSFHq3i3fnU1QQlc4BB9Hnhh8TgEqvQlPPm8kDLw8gYxvjqYw3c5CZW0+zKR837nwT6im/wtJUivMKw==
+"@firebase/database@1.1.0":
+ version "1.1.0"
+ resolved "https://registry.yarnpkg.com/@firebase/database/-/database-1.1.0.tgz#bdf60f1605079a87ceb2b5e30d90846e0bde294b"
+ integrity sha512-gM6MJFae3pTyNLoc9VcJNuaUDej0ctdjn3cVtILo3D5lpp0dmUHHLFN/pUKe7ImyeB1KAvRlEYxvIHNF04Filg==
dependencies:
"@firebase/app-check-interop-types" "0.3.3"
"@firebase/auth-interop-types" "0.2.4"
- "@firebase/component" "0.6.12"
- "@firebase/logger" "0.4.4"
- "@firebase/util" "1.10.3"
+ "@firebase/component" "0.7.0"
+ "@firebase/logger" "0.5.0"
+ "@firebase/util" "1.13.0"
faye-websocket "0.11.4"
tslib "^2.1.0"
-"@firebase/firestore-compat@0.3.43":
- version "0.3.43"
- resolved "https://registry.yarnpkg.com/@firebase/firestore-compat/-/firestore-compat-0.3.43.tgz#c62994b9b26d011a96265d6aa524a0d6b45a2c1b"
- integrity sha512-zxg7YS07XQnTetGs3GADM/eA6HB4vWUp+Av4iugmTbft0fQxuTSnGm7ifctaYuR7VMTPckU9CW+oFC9QUNSYvg==
+"@firebase/firestore-compat@0.4.0":
+ version "0.4.0"
+ resolved "https://registry.yarnpkg.com/@firebase/firestore-compat/-/firestore-compat-0.4.0.tgz#911cf956e489fa8335ed2f2ace14a74909bcd94d"
+ integrity sha512-4O7v4VFeSEwAZtLjsaj33YrMHMRjplOIYC2CiYsF6o/MboOhrhe01VrTt8iY9Y5EwjRHuRz4pS6jMBT8LfQYJA==
dependencies:
- "@firebase/component" "0.6.12"
- "@firebase/firestore" "4.7.8"
+ "@firebase/component" "0.7.0"
+ "@firebase/firestore" "4.9.0"
"@firebase/firestore-types" "3.0.3"
- "@firebase/util" "1.10.3"
+ "@firebase/util" "1.13.0"
tslib "^2.1.0"
"@firebase/firestore-types@3.0.3":
@@ -1286,28 +1297,28 @@
resolved "https://registry.yarnpkg.com/@firebase/firestore-types/-/firestore-types-3.0.3.tgz#7d0c3dd8850c0193d8f5ee0cc8f11961407742c1"
integrity sha512-hD2jGdiWRxB/eZWF89xcK9gF8wvENDJkzpVFb4aGkzfEaKxVRD1kjz1t1Wj8VZEp2LCB53Yx1zD8mrhQu87R6Q==
-"@firebase/firestore@4.7.8":
- version "4.7.8"
- resolved "https://registry.yarnpkg.com/@firebase/firestore/-/firestore-4.7.8.tgz#38b124a4d50a390934f70d916a84546376abdf4e"
- integrity sha512-eDvVJ/I5vSmIdGmLHJAK1OcviigIxjjia6i5/AkMFq6vZMt7CBXA0B5Xz9pGRCZ7WewFcsCbK1ZUQoYJ91+Cew==
+"@firebase/firestore@4.9.0":
+ version "4.9.0"
+ resolved "https://registry.yarnpkg.com/@firebase/firestore/-/firestore-4.9.0.tgz#753d73c002b4c0ae639437b049ef0086791a0cf3"
+ integrity sha512-5zl0+/h1GvlCSLt06RMwqFsd7uqRtnNZt4sW99k2rKRd6k/ECObIWlEnvthm2cuOSnUmwZknFqtmd1qyYSLUuQ==
dependencies:
- "@firebase/component" "0.6.12"
- "@firebase/logger" "0.4.4"
- "@firebase/util" "1.10.3"
- "@firebase/webchannel-wrapper" "1.0.3"
+ "@firebase/component" "0.7.0"
+ "@firebase/logger" "0.5.0"
+ "@firebase/util" "1.13.0"
+ "@firebase/webchannel-wrapper" "1.0.4"
"@grpc/grpc-js" "~1.9.0"
"@grpc/proto-loader" "^0.7.8"
tslib "^2.1.0"
-"@firebase/functions-compat@0.3.19":
- version "0.3.19"
- resolved "https://registry.yarnpkg.com/@firebase/functions-compat/-/functions-compat-0.3.19.tgz#f1d1ce51674a6ee8d5449b721374d35243dc3002"
- integrity sha512-uw4tR8NcJCDu86UD63Za8A8SgFgmAVFb1XsGlkuBY7gpLyZWEFavWnwRkZ/8cUwpqUhp/SptXFZ1WFJSnOokLw==
+"@firebase/functions-compat@0.4.0":
+ version "0.4.0"
+ resolved "https://registry.yarnpkg.com/@firebase/functions-compat/-/functions-compat-0.4.0.tgz#aa63dea248053e9c06904605704662ea550e50ed"
+ integrity sha512-VPgtvoGFywWbQqtvgJnVWIDFSHV1WE6Hmyi5EGI+P+56EskiGkmnw6lEqc/MEUfGpPGdvmc4I9XMU81uj766/g==
dependencies:
- "@firebase/component" "0.6.12"
- "@firebase/functions" "0.12.2"
+ "@firebase/component" "0.7.0"
+ "@firebase/functions" "0.13.0"
"@firebase/functions-types" "0.6.3"
- "@firebase/util" "1.10.3"
+ "@firebase/util" "1.13.0"
tslib "^2.1.0"
"@firebase/functions-types@0.6.3":
@@ -1315,27 +1326,27 @@
resolved "https://registry.yarnpkg.com/@firebase/functions-types/-/functions-types-0.6.3.tgz#f5faf770248b13f45d256f614230da6a11bfb654"
integrity sha512-EZoDKQLUHFKNx6VLipQwrSMh01A1SaL3Wg6Hpi//x6/fJ6Ee4hrAeswK99I5Ht8roiniKHw4iO0B1Oxj5I4plg==
-"@firebase/functions@0.12.2":
- version "0.12.2"
- resolved "https://registry.yarnpkg.com/@firebase/functions/-/functions-0.12.2.tgz#bea33b35437278228be563dfc02520d8623d43f4"
- integrity sha512-iKpFDoCYk/Qm+Qwv5ynRb9/yq64QOt0A0+t9NuekyAZnSoV56kSNq/PmsVmBauar5SlmEjhHk6QKdMBP9S0gXA==
+"@firebase/functions@0.13.0":
+ version "0.13.0"
+ resolved "https://registry.yarnpkg.com/@firebase/functions/-/functions-0.13.0.tgz#91685a59589b3a00f6c48faf383acd28a35800c2"
+ integrity sha512-2/LH5xIbD8aaLOWSFHAwwAybgSzHIM0dB5oVOL0zZnxFG1LctX2bc1NIAaPk1T+Zo9aVkLKUlB5fTXTkVUQprQ==
dependencies:
"@firebase/app-check-interop-types" "0.3.3"
"@firebase/auth-interop-types" "0.2.4"
- "@firebase/component" "0.6.12"
+ "@firebase/component" "0.7.0"
"@firebase/messaging-interop-types" "0.2.3"
- "@firebase/util" "1.10.3"
+ "@firebase/util" "1.13.0"
tslib "^2.1.0"
-"@firebase/installations-compat@0.2.12":
- version "0.2.12"
- resolved "https://registry.yarnpkg.com/@firebase/installations-compat/-/installations-compat-0.2.12.tgz#ee6396f3cc787c0dd4fc5dd87fec1db9dbb40c97"
- integrity sha512-RhcGknkxmFu92F6Jb3rXxv6a4sytPjJGifRZj8MSURPuv2Xu+/AispCXEfY1ZraobhEHTG5HLGsP6R4l9qB5aA==
+"@firebase/installations-compat@0.2.19":
+ version "0.2.19"
+ resolved "https://registry.yarnpkg.com/@firebase/installations-compat/-/installations-compat-0.2.19.tgz#4bc57c8c57d241eeca95900ff3033d6ec3dbcc7c"
+ integrity sha512-khfzIY3EI5LePePo7vT19/VEIH1E3iYsHknI/6ek9T8QCozAZshWT9CjlwOzZrKvTHMeNcbpo/VSOSIWDSjWdQ==
dependencies:
- "@firebase/component" "0.6.12"
- "@firebase/installations" "0.6.12"
+ "@firebase/component" "0.7.0"
+ "@firebase/installations" "0.6.19"
"@firebase/installations-types" "0.5.3"
- "@firebase/util" "1.10.3"
+ "@firebase/util" "1.13.0"
tslib "^2.1.0"
"@firebase/installations-types@0.5.3":
@@ -1343,31 +1354,31 @@
resolved "https://registry.yarnpkg.com/@firebase/installations-types/-/installations-types-0.5.3.tgz#cac8a14dd49f09174da9df8ae453f9b359c3ef2f"
integrity sha512-2FJI7gkLqIE0iYsNQ1P751lO3hER+Umykel+TkLwHj6plzWVxqvfclPUZhcKFVQObqloEBTmpi2Ozn7EkCABAA==
-"@firebase/installations@0.6.12":
- version "0.6.12"
- resolved "https://registry.yarnpkg.com/@firebase/installations/-/installations-0.6.12.tgz#6d9ad14e60caa8fae4ec0120c0e46ceb9d6fbdae"
- integrity sha512-ES/WpuAV2k2YtBTvdaknEo7IY8vaGjIjS3zhnHSAIvY9KwTR8XZFXOJoZ3nSkjN1A5R4MtEh+07drnzPDg9vaw==
+"@firebase/installations@0.6.19":
+ version "0.6.19"
+ resolved "https://registry.yarnpkg.com/@firebase/installations/-/installations-0.6.19.tgz#93c569321f6fb399f4f1a197efc0053ce6452c7c"
+ integrity sha512-nGDmiwKLI1lerhwfwSHvMR9RZuIH5/8E3kgUWnVRqqL7kGVSktjLTWEMva7oh5yxQ3zXfIlIwJwMcaM5bK5j8Q==
dependencies:
- "@firebase/component" "0.6.12"
- "@firebase/util" "1.10.3"
+ "@firebase/component" "0.7.0"
+ "@firebase/util" "1.13.0"
idb "7.1.1"
tslib "^2.1.0"
-"@firebase/logger@0.4.4":
- version "0.4.4"
- resolved "https://registry.yarnpkg.com/@firebase/logger/-/logger-0.4.4.tgz#29e8379d20fd1149349a195ee6deee4573a86f48"
- integrity sha512-mH0PEh1zoXGnaR8gD1DeGeNZtWFKbnz9hDO91dIml3iou1gpOnLqXQ2dJfB71dj6dpmUjcQ6phY3ZZJbjErr9g==
+"@firebase/logger@0.5.0":
+ version "0.5.0"
+ resolved "https://registry.yarnpkg.com/@firebase/logger/-/logger-0.5.0.tgz#a9e55b1c669a0983dc67127fa4a5964ce8ed5e1b"
+ integrity sha512-cGskaAvkrnh42b3BA3doDWeBmuHFO/Mx5A83rbRDYakPjO9bJtRL3dX7javzc2Rr/JHZf4HlterTW2lUkfeN4g==
dependencies:
tslib "^2.1.0"
-"@firebase/messaging-compat@0.2.16":
- version "0.2.16"
- resolved "https://registry.yarnpkg.com/@firebase/messaging-compat/-/messaging-compat-0.2.16.tgz#533af4542a54b932146d175d5687aedd428be972"
- integrity sha512-9HZZ88Ig3zQ0ok/Pwt4gQcNsOhoEy8hDHoGsV1am6ulgMuGuDVD2gl11Lere2ksL+msM12Lddi2x/7TCqmODZw==
+"@firebase/messaging-compat@0.2.23":
+ version "0.2.23"
+ resolved "https://registry.yarnpkg.com/@firebase/messaging-compat/-/messaging-compat-0.2.23.tgz#2ca6b36ea238fae4dff53bf85442c4a2af516224"
+ integrity sha512-SN857v/kBUvlQ9X/UjAqBoQ2FEaL1ZozpnmL1ByTe57iXkmnVVFm9KqAsTfmf+OEwWI4kJJe9NObtN/w22lUgg==
dependencies:
- "@firebase/component" "0.6.12"
- "@firebase/messaging" "0.12.16"
- "@firebase/util" "1.10.3"
+ "@firebase/component" "0.7.0"
+ "@firebase/messaging" "0.12.23"
+ "@firebase/util" "1.13.0"
tslib "^2.1.0"
"@firebase/messaging-interop-types@0.2.3":
@@ -1375,28 +1386,28 @@
resolved "https://registry.yarnpkg.com/@firebase/messaging-interop-types/-/messaging-interop-types-0.2.3.tgz#e647c9cd1beecfe6a6e82018a6eec37555e4da3e"
integrity sha512-xfzFaJpzcmtDjycpDeCUj0Ge10ATFi/VHVIvEEjDNc3hodVBQADZ7BWQU7CuFpjSHE+eLuBI13z5F/9xOoGX8Q==
-"@firebase/messaging@0.12.16":
- version "0.12.16"
- resolved "https://registry.yarnpkg.com/@firebase/messaging/-/messaging-0.12.16.tgz#bd8a768274bdc4368396bd9eaa356bffb998bef2"
- integrity sha512-VJ8sCEIeP3+XkfbJA7410WhYGHdloYFZXoHe/vt+vNVDGw8JQPTQSVTRvjrUprEf5I4Tbcnpr2H34lS6zhCHSA==
+"@firebase/messaging@0.12.23":
+ version "0.12.23"
+ resolved "https://registry.yarnpkg.com/@firebase/messaging/-/messaging-0.12.23.tgz#71f932a521ac39d9f036175672e37897531010eb"
+ integrity sha512-cfuzv47XxqW4HH/OcR5rM+AlQd1xL/VhuaeW/wzMW1LFrsFcTn0GND/hak1vkQc2th8UisBcrkVcQAnOnKwYxg==
dependencies:
- "@firebase/component" "0.6.12"
- "@firebase/installations" "0.6.12"
+ "@firebase/component" "0.7.0"
+ "@firebase/installations" "0.6.19"
"@firebase/messaging-interop-types" "0.2.3"
- "@firebase/util" "1.10.3"
+ "@firebase/util" "1.13.0"
idb "7.1.1"
tslib "^2.1.0"
-"@firebase/performance-compat@0.2.13":
- version "0.2.13"
- resolved "https://registry.yarnpkg.com/@firebase/performance-compat/-/performance-compat-0.2.13.tgz#29bb94909c10553b40ca97e7f7d0e163bad8a77d"
- integrity sha512-pB0SMQj2TLQ6roDcX0YQDWvUnVgsVOl0VnUvyT/VBdCUuQYDHobZsPEuQsoEqmPA44KS/Gl0oyKqf+I8UPtRgw==
+"@firebase/performance-compat@0.2.22":
+ version "0.2.22"
+ resolved "https://registry.yarnpkg.com/@firebase/performance-compat/-/performance-compat-0.2.22.tgz#1c24ea360b03cfef831bdf379b4fc7080f412741"
+ integrity sha512-xLKxaSAl/FVi10wDX/CHIYEUP13jXUjinL+UaNXT9ByIvxII5Ne5150mx6IgM8G6Q3V+sPiw9C8/kygkyHUVxg==
dependencies:
- "@firebase/component" "0.6.12"
- "@firebase/logger" "0.4.4"
- "@firebase/performance" "0.7.0"
+ "@firebase/component" "0.7.0"
+ "@firebase/logger" "0.5.0"
+ "@firebase/performance" "0.7.9"
"@firebase/performance-types" "0.2.3"
- "@firebase/util" "1.10.3"
+ "@firebase/util" "1.13.0"
tslib "^2.1.0"
"@firebase/performance-types@0.2.3":
@@ -1404,28 +1415,28 @@
resolved "https://registry.yarnpkg.com/@firebase/performance-types/-/performance-types-0.2.3.tgz#5ce64e90fa20ab5561f8b62a305010cf9fab86fb"
integrity sha512-IgkyTz6QZVPAq8GSkLYJvwSLr3LS9+V6vNPQr0x4YozZJiLF5jYixj0amDtATf1X0EtYHqoPO48a9ija8GocxQ==
-"@firebase/performance@0.7.0":
- version "0.7.0"
- resolved "https://registry.yarnpkg.com/@firebase/performance/-/performance-0.7.0.tgz#1cd82039f7e06e0f059287dfa21705c68ec9a691"
- integrity sha512-L91PwYuiJdKXKSRqsWNicvTppAJVzKjye03UlegeD6TkpKjb93T8AmJ9B0Mt0bcWHCNtnnRBCdSCvD2U9GZDjw==
+"@firebase/performance@0.7.9":
+ version "0.7.9"
+ resolved "https://registry.yarnpkg.com/@firebase/performance/-/performance-0.7.9.tgz#7e3a072b1542f0df3f502684a38a0516b0d72cab"
+ integrity sha512-UzybENl1EdM2I1sjYm74xGt/0JzRnU/0VmfMAKo2LSpHJzaj77FCLZXmYQ4oOuE+Pxtt8Wy2BVJEENiZkaZAzQ==
dependencies:
- "@firebase/component" "0.6.12"
- "@firebase/installations" "0.6.12"
- "@firebase/logger" "0.4.4"
- "@firebase/util" "1.10.3"
+ "@firebase/component" "0.7.0"
+ "@firebase/installations" "0.6.19"
+ "@firebase/logger" "0.5.0"
+ "@firebase/util" "1.13.0"
tslib "^2.1.0"
web-vitals "^4.2.4"
-"@firebase/remote-config-compat@0.2.12":
- version "0.2.12"
- resolved "https://registry.yarnpkg.com/@firebase/remote-config-compat/-/remote-config-compat-0.2.12.tgz#ae0b597b3228deef0e3c6b2c6e631f19213eca4c"
- integrity sha512-91jLWPtubIuPBngg9SzwvNCWzhMLcyBccmt7TNZP+y1cuYFNOWWHKUXQ3IrxCLB7WwLqQaEu7fTDAjHsTyBsSw==
+"@firebase/remote-config-compat@0.2.19":
+ version "0.2.19"
+ resolved "https://registry.yarnpkg.com/@firebase/remote-config-compat/-/remote-config-compat-0.2.19.tgz#10cfd804f65c5ca80a4d40994bc853ca6d1f7307"
+ integrity sha512-y7PZAb0l5+5oIgLJr88TNSelxuASGlXyAKj+3pUc4fDuRIdPNBoONMHaIUa9rlffBR5dErmaD2wUBJ7Z1a513Q==
dependencies:
- "@firebase/component" "0.6.12"
- "@firebase/logger" "0.4.4"
- "@firebase/remote-config" "0.5.0"
+ "@firebase/component" "0.7.0"
+ "@firebase/logger" "0.5.0"
+ "@firebase/remote-config" "0.6.6"
"@firebase/remote-config-types" "0.4.0"
- "@firebase/util" "1.10.3"
+ "@firebase/util" "1.13.0"
tslib "^2.1.0"
"@firebase/remote-config-types@0.4.0":
@@ -1433,26 +1444,26 @@
resolved "https://registry.yarnpkg.com/@firebase/remote-config-types/-/remote-config-types-0.4.0.tgz#91b9a836d5ca30ced68c1516163b281fbb544537"
integrity sha512-7p3mRE/ldCNYt8fmWMQ/MSGRmXYlJ15Rvs9Rk17t8p0WwZDbeK7eRmoI1tvCPaDzn9Oqh+yD6Lw+sGLsLg4kKg==
-"@firebase/remote-config@0.5.0":
- version "0.5.0"
- resolved "https://registry.yarnpkg.com/@firebase/remote-config/-/remote-config-0.5.0.tgz#30212fa77adba8a62fc6408eb32122147ae80790"
- integrity sha512-weiEbpBp5PBJTHUWR4GwI7ZacaAg68BKha5QnZ8Go65W4oQjEWqCW/rfskABI/OkrGijlL3CUmCB/SA6mVo0qA==
+"@firebase/remote-config@0.6.6":
+ version "0.6.6"
+ resolved "https://registry.yarnpkg.com/@firebase/remote-config/-/remote-config-0.6.6.tgz#50eae3d2d71791d76fb6521971bb646d6628805e"
+ integrity sha512-Yelp5xd8hM4NO1G1SuWrIk4h5K42mNwC98eWZ9YLVu6Z0S6hFk1mxotAdCRmH2luH8FASlYgLLq6OQLZ4nbnCA==
dependencies:
- "@firebase/component" "0.6.12"
- "@firebase/installations" "0.6.12"
- "@firebase/logger" "0.4.4"
- "@firebase/util" "1.10.3"
+ "@firebase/component" "0.7.0"
+ "@firebase/installations" "0.6.19"
+ "@firebase/logger" "0.5.0"
+ "@firebase/util" "1.13.0"
tslib "^2.1.0"
-"@firebase/storage-compat@0.3.16":
- version "0.3.16"
- resolved "https://registry.yarnpkg.com/@firebase/storage-compat/-/storage-compat-0.3.16.tgz#49ab9c572bb172e6335c099d95a48bee0f17cc98"
- integrity sha512-EeMuok/s0r938lEomia8XILEqSYULm7HcYZ/GTZLDWur0kMf2ktuPVZiTdRiwEV3Iki7FtQO5txrQ/0pLRVLAw==
+"@firebase/storage-compat@0.4.0":
+ version "0.4.0"
+ resolved "https://registry.yarnpkg.com/@firebase/storage-compat/-/storage-compat-0.4.0.tgz#a09bd33c262123e7e3ed0cd590b4c6e2ce4a8902"
+ integrity sha512-vDzhgGczr1OfcOy285YAPur5pWDEvD67w4thyeCUh6Ys0izN9fNYtA1MJERmNBfqjqu0lg0FM5GLbw0Il21M+g==
dependencies:
- "@firebase/component" "0.6.12"
- "@firebase/storage" "0.13.6"
+ "@firebase/component" "0.7.0"
+ "@firebase/storage" "0.14.0"
"@firebase/storage-types" "0.8.3"
- "@firebase/util" "1.10.3"
+ "@firebase/util" "1.13.0"
tslib "^2.1.0"
"@firebase/storage-types@0.8.3":
@@ -1460,37 +1471,26 @@
resolved "https://registry.yarnpkg.com/@firebase/storage-types/-/storage-types-0.8.3.tgz#2531ef593a3452fc12c59117195d6485c6632d3d"
integrity sha512-+Muk7g9uwngTpd8xn9OdF/D48uiQ7I1Fae7ULsWPuKoCH3HU7bfFPhxtJYzyhjdniowhuDpQcfPmuNRAqZEfvg==
-"@firebase/storage@0.13.6":
- version "0.13.6"
- resolved "https://registry.yarnpkg.com/@firebase/storage/-/storage-0.13.6.tgz#322def6cda335df991ce9787aa5ef5650db901bd"
- integrity sha512-BEJLYQzVgAoglRl5VRIRZ91RRBZgS/O37/PSGQJBYNuoLmFZUrtwrlLTOAwG776NlO9VQR+K2j15/36Lr2EqHA==
+"@firebase/storage@0.14.0":
+ version "0.14.0"
+ resolved "https://registry.yarnpkg.com/@firebase/storage/-/storage-0.14.0.tgz#01acb97d413ada7c91de860fb260623468baa25d"
+ integrity sha512-xWWbb15o6/pWEw8H01UQ1dC5U3rf8QTAzOChYyCpafV6Xki7KVp3Yaw2nSklUwHEziSWE9KoZJS7iYeyqWnYFA==
dependencies:
- "@firebase/component" "0.6.12"
- "@firebase/util" "1.10.3"
+ "@firebase/component" "0.7.0"
+ "@firebase/util" "1.13.0"
tslib "^2.1.0"
-"@firebase/util@1.10.3":
- version "1.10.3"
- resolved "https://registry.yarnpkg.com/@firebase/util/-/util-1.10.3.tgz#63fc5fea7b36236219c4875731597494416678d1"
- integrity sha512-wfoF5LTy0m2ufUapV0ZnpcGQvuavTbJ5Qr1Ze9OJGL70cSMvhDyjS4w2121XdA3lGZSTOsDOyGhpoDtYwck85A==
+"@firebase/util@1.13.0":
+ version "1.13.0"
+ resolved "https://registry.yarnpkg.com/@firebase/util/-/util-1.13.0.tgz#2e9e7569722a1e3fc86b1b4076d5cbfbfa7265d6"
+ integrity sha512-0AZUyYUfpMNcztR5l09izHwXkZpghLgCUaAGjtMwXnCg3bj4ml5VgiwqOMOxJ+Nw4qN/zJAaOQBcJ7KGkWStqQ==
dependencies:
tslib "^2.1.0"
-"@firebase/vertexai@1.0.4":
+"@firebase/webchannel-wrapper@1.0.4":
version "1.0.4"
- resolved "https://registry.yarnpkg.com/@firebase/vertexai/-/vertexai-1.0.4.tgz#1966ddfb32492d004f595f639e57162d488c84ba"
- integrity sha512-Nkf/r4u166b4Id6zrrW0Qtg1KyZpQvvYchtkebamnHtIfY+Qnt51I/sx4Saos/WrmO8SnrSU850LfmJ7pehYXg==
- dependencies:
- "@firebase/app-check-interop-types" "0.3.3"
- "@firebase/component" "0.6.12"
- "@firebase/logger" "0.4.4"
- "@firebase/util" "1.10.3"
- tslib "^2.1.0"
-
-"@firebase/webchannel-wrapper@1.0.3":
- version "1.0.3"
- resolved "https://registry.yarnpkg.com/@firebase/webchannel-wrapper/-/webchannel-wrapper-1.0.3.tgz#a73bab8eb491d7b8b7be2f0e6c310647835afe83"
- integrity sha512-2xCRM9q9FlzGZCdgDMJwc0gyUkWFtkosy7Xxr6sFgQwn+wMNIWd7xIvYNauU1r64B5L5rsGKy/n9TKJ0aAFeqQ==
+ resolved "https://registry.yarnpkg.com/@firebase/webchannel-wrapper/-/webchannel-wrapper-1.0.4.tgz#9d5b4b6f23309260a12856cb574c5e64e6c133f7"
+ integrity sha512-6m8+P+dE/RPl4OPzjTxcTbQ0rGeRyeTvAi9KwIffBVCiAMKrfXfLZaqD1F+m8t4B5/Q5aHsMozOgirkH1F5oMQ==
"@grpc/grpc-js@~1.9.0":
version "1.9.15"
@@ -1556,11 +1556,6 @@
get-package-type "^0.1.0"
js-yaml "^3.13.1"
resolve-from "^5.0.0"
- camelcase "^5.3.1"
- find-up "^4.1.0"
- get-package-type "^0.1.0"
- js-yaml "^3.13.1"
- resolve-from "^5.0.0"
"@istanbuljs/schema@^0.1.2", "@istanbuljs/schema@^0.1.3":
version "0.1.3"
@@ -1620,6 +1615,11 @@
dependencies:
"@jest/types" "^29.6.3"
+"@jest/diff-sequences@30.0.1":
+ version "30.0.1"
+ resolved "https://registry.yarnpkg.com/@jest/diff-sequences/-/diff-sequences-30.0.1.tgz#0ededeae4d071f5c8ffe3678d15f3a1be09156be"
+ integrity sha512-n5H8QLDJ47QqbCNn5SuFjCRDrOLEZ0h8vAHCK5RL9Ls7Xa8AQLa/YxAc9UjFqoEDM48muwtBGjtMY5cr0PLDCw==
+
"@jest/environment@^29.7.0":
version "29.7.0"
resolved "https://registry.yarnpkg.com/@jest/environment/-/environment-29.7.0.tgz#24d61f54ff1f786f3cd4073b4b94416383baf2a7"
@@ -1657,6 +1657,11 @@
jest-mock "^29.7.0"
jest-util "^29.7.0"
+"@jest/get-type@30.1.0":
+ version "30.1.0"
+ resolved "https://registry.yarnpkg.com/@jest/get-type/-/get-type-30.1.0.tgz#4fcb4dc2ebcf0811be1c04fd1cb79c2dba431cbc"
+ integrity sha512-eMbZE2hUnx1WV0pmURZY9XoXPkUYjpc55mb0CrhtdWLtzMQPFvu/rZkTLZFTsdaVQa+Tr4eWAteqcUzoawq/uA==
+
"@jest/globals@^29.7.0":
version "29.7.0"
resolved "https://registry.yarnpkg.com/@jest/globals/-/globals-29.7.0.tgz#8d9290f9ec47ff772607fa864ca1d5a2efae1d4d"
@@ -1697,6 +1702,13 @@
strip-ansi "^6.0.0"
v8-to-istanbul "^9.0.1"
+"@jest/schemas@30.0.5":
+ version "30.0.5"
+ resolved "https://registry.yarnpkg.com/@jest/schemas/-/schemas-30.0.5.tgz#7bdf69fc5a368a5abdb49fd91036c55225846473"
+ integrity sha512-DmdYgtezMkh3cpU8/1uyXakv3tJRcmcXxBOcO0tbaozPwpmh4YMsnWrQm9ZmZMfa5ocbxzbFk6O4bDPEc/iAnA==
+ dependencies:
+ "@sinclair/typebox" "^0.34.0"
+
"@jest/schemas@^29.6.3":
version "29.6.3"
resolved "https://registry.yarnpkg.com/@jest/schemas/-/schemas-29.6.3.tgz#430b5ce8a4e0044a7e3819663305a7b3091c8e03"
@@ -1722,7 +1734,6 @@
"@jest/types" "^29.6.3"
"@types/istanbul-lib-coverage" "^2.0.0"
collect-v8-coverage "^1.0.0"
- collect-v8-coverage "^1.0.0"
"@jest/test-sequencer@^29.7.0":
version "29.7.0"
@@ -1765,7 +1776,6 @@
"@types/node" "*"
"@types/yargs" "^15.0.0"
chalk "^4.0.0"
- chalk "^4.0.0"
"@jest/types@^29.6.3":
version "29.6.3"
@@ -1819,6 +1829,11 @@
"@jridgewell/resolve-uri" "^3.1.0"
"@jridgewell/sourcemap-codec" "^1.4.14"
+"@miblanchard/react-native-slider@^2.6.0":
+ version "2.6.0"
+ resolved "https://registry.yarnpkg.com/@miblanchard/react-native-slider/-/react-native-slider-2.6.0.tgz#9f78c805d637ffaff0e3e7429932d2995a67edc9"
+ integrity sha512-o7hk/f/8vkqh6QNR5L52m+ws846fQeD/qNCC9CCSRdBqjq66KiCgbxzlhRzKM/gbtxcvMYMIEEJ1yes5cr6I3A==
+
"@nicolo-ribaudo/eslint-scope-5-internals@5.1.1-v1":
version "5.1.1-v1"
resolved "https://registry.yarnpkg.com/@nicolo-ribaudo/eslint-scope-5-internals/-/eslint-scope-5-internals-5.1.1-v1.tgz#dbf733a965ca47b1973177dc0bb6c889edcfb129"
@@ -2119,36 +2134,36 @@
resolved "https://registry.yarnpkg.com/@react-native-community/slider/-/slider-4.5.7.tgz#523178779f302aa162b0d57211842f81522e8ece"
integrity sha512-WMDDZjNF2Bd8M8TrSqKf5xhM9ikdfCHtSPur64Ow3bB/OVrKufUQZ59NmYdNZNeGPvcHHTsafuYTY8zfZfbchQ==
-"@react-native-firebase/analytics@^21.12.0":
- version "21.14.0"
- resolved "https://registry.yarnpkg.com/@react-native-firebase/analytics/-/analytics-21.14.0.tgz#f3ece5f2f49e6728e7890484d9ae7eee68c0c0a1"
- integrity sha512-hWzb/mMCw9DHu3y8tYlSFTcG6cpUNTbnOO8CDKxjmkCRlryU/sU+dT3ih3NlJXiuI6W9wSUy4qigA0SEBCiQlQ==
+"@react-native-firebase/analytics@^23.1.2":
+ version "23.1.2"
+ resolved "https://registry.yarnpkg.com/@react-native-firebase/analytics/-/analytics-23.1.2.tgz#e7a4b2f863623bd4c736f845828181f2ef84ce09"
+ integrity sha512-fq/2GxRIwgXU6wxe55whI18K1GfQA9ZsnnI1FRUKNfhBV6m5g2CYasP9SgKv4kGj3WPh3JuaeebnKulCD0Xl9A==
dependencies:
superstruct "^2.0.2"
-"@react-native-firebase/app@^21.12.0":
- version "21.14.0"
- resolved "https://registry.yarnpkg.com/@react-native-firebase/app/-/app-21.14.0.tgz#5061c94f37331b89fb50d20d3c71c6120978b4f8"
- integrity sha512-vBNfn7PoQrZfANLJnJiWZSHVu7WG6hjM5w3MDfmG8DLdr8VsAVBUgsn8lGpqobSuno1vTgwDIhR8PYZjMGsuvg==
+"@react-native-firebase/app@^23.1.2":
+ version "23.1.2"
+ resolved "https://registry.yarnpkg.com/@react-native-firebase/app/-/app-23.1.2.tgz#37dbb8df83194ffb4012672da077e24edc9acfbf"
+ integrity sha512-GqnMjUvWZ7TzUvjbkuSa+5zOZVPz3jKOCp42tg3Fvxqu7fbw/M0P/SADHnTpoQaDvjf5FhJ7TGNhmB5+tjJ9yA==
dependencies:
- firebase "11.3.1"
+ firebase "12.1.0"
-"@react-native-firebase/crashlytics@^21.12.0":
- version "21.14.0"
- resolved "https://registry.yarnpkg.com/@react-native-firebase/crashlytics/-/crashlytics-21.14.0.tgz#d72bd045133b0e68a85c051cebade77f5cedf9d1"
- integrity sha512-w51Huoyg3iWgS0UfiozBqofnIaG2ZY8pdVI8iEMRAD82PKYCxFABH7WqSZOefJHvYuqip02DCmEZ9Q0b3lpBug==
+"@react-native-firebase/crashlytics@^23.1.2":
+ version "23.1.2"
+ resolved "https://registry.yarnpkg.com/@react-native-firebase/crashlytics/-/crashlytics-23.1.2.tgz#77ecd5ec69e1b10a86e08b37c7621bdbfd428e71"
+ integrity sha512-BHZkDr0O6LaHEAul5mQi+Mq8l02mBhBAJ6ii103m0ZaYH5oESpEXP/zafeVbFzpU0USKVmuA8Elo2QDa8CfKgA==
dependencies:
stacktrace-js "^2.0.2"
-"@react-native-firebase/messaging@^21.12.0":
- version "21.14.0"
- resolved "https://registry.yarnpkg.com/@react-native-firebase/messaging/-/messaging-21.14.0.tgz#8a6498998b27951c3c76d33b7a248050e1a8f662"
- integrity sha512-EwJVFr2xtQFf6wQ3anXvnxCyFNRc6Hw7B6Ov4GYwgBEXNzw0MIPN2qDDA3lIN2a6nf6zESPuflYyG6Tel1ovSQ==
+"@react-native-firebase/messaging@^23.1.2":
+ version "23.1.2"
+ resolved "https://registry.yarnpkg.com/@react-native-firebase/messaging/-/messaging-23.1.2.tgz#311157883c88200209b3df9fd411a2b3e5af6a6b"
+ integrity sha512-B6ysoo6TMJGgIGNG+1j8SeImFHiiV4wrnB9nBDHr0y+oymGR7iVCNC8iaOd4RVd3FK96U3ojM35Dn2PPhSnrJg==
-"@react-native-firebase/perf@^21.12.0":
- version "21.14.0"
- resolved "https://registry.yarnpkg.com/@react-native-firebase/perf/-/perf-21.14.0.tgz#7e3129736de2dea83fe0f95fd4a4eb575c371f55"
- integrity sha512-TIL4IpN8/hrJj7BouomMjv2m5IMYlqqetKAD121LUo67SIuCNxh1xYyMj9stNl/kKuHZK0D7R79fsGUClOaAbQ==
+"@react-native-firebase/perf@^23.1.2":
+ version "23.1.2"
+ resolved "https://registry.yarnpkg.com/@react-native-firebase/perf/-/perf-23.1.2.tgz#4e90cce41a3ef928707168bf278028241d0ca84a"
+ integrity sha512-MEYZYtKPoBBN0AaWwrHQ6jUYm1JzGJnGy44aBqpvaCMb2RO6gvGuFGPQQKNKhbPEdZemdiSEKFeuP54vMQl1IQ==
"@react-native/assets-registry@0.78.2":
version "0.78.2"
@@ -2533,13 +2548,17 @@
resolved "https://registry.yarnpkg.com/@sinclair/typebox/-/typebox-0.27.8.tgz#6667fac16c436b5434a387a34dedb013198f6e6e"
integrity sha512-+Fj43pSMwJs4KRrH/938Uf+uAELIgVBmQzg/q1YG10djyfA3TnrU8N8XzqCh/okZdszqBQTZf96idMfE5lnwTA==
+"@sinclair/typebox@^0.34.0":
+ version "0.34.41"
+ resolved "https://registry.yarnpkg.com/@sinclair/typebox/-/typebox-0.34.41.tgz#aa51a6c1946df2c5a11494a2cdb9318e026db16c"
+ integrity sha512-6gS8pZzSXdyRHTIqoqSVknxolr1kzfy4/CeDnrzsVz8TTIWUbOBr6gnzOmTYJ3eXQNh4IYHIGi5aIL7sOZ2G/g==
+
"@sinonjs/commons@^3.0.0":
version "3.0.1"
resolved "https://registry.yarnpkg.com/@sinonjs/commons/-/commons-3.0.1.tgz#1029357e44ca901a615585f6d27738dbc89084cd"
integrity sha512-K3mCHKQ9sVh8o1C9cxkwxaOmXoAMlDxC1mYyHrjqOWEcBjYr76t96zL2zlj5dUGZ3HSw240X1qgH3Mjf1yJWpQ==
dependencies:
type-detect "4.0.8"
- type-detect "4.0.8"
"@sinonjs/fake-timers@^10.0.2":
version "10.3.0"
@@ -2558,6 +2577,16 @@
resolved "https://registry.yarnpkg.com/@standard-schema/utils/-/utils-0.3.0.tgz#3d5e608f16c2390c10528e98e59aef6bf73cae7b"
integrity sha512-e7Mew686owMaPJVNNLs55PUvgz371nKgwsc4vxE49zsODpJEnxgxRo2y/OKrqueavXgZNMDVj3DdHFlaSAeU8g==
+"@testing-library/react-native@^13.3.3":
+ version "13.3.3"
+ resolved "https://registry.yarnpkg.com/@testing-library/react-native/-/react-native-13.3.3.tgz#4bf02911c4e18075df40b5de0e029c209fb45bda"
+ integrity sha512-k6Mjsd9dbZgvY4Bl7P1NIpePQNi+dfYtlJ5voi9KQlynxSyQkfOgJmYGCYmw/aSgH/rUcFvG8u5gd4npzgRDyg==
+ dependencies:
+ jest-matcher-utils "^30.0.5"
+ picocolors "^1.1.1"
+ pretty-format "^30.0.5"
+ redent "^3.0.0"
+
"@types/babel__core@^7.1.14":
version "7.20.5"
resolved "https://registry.yarnpkg.com/@types/babel__core/-/babel__core-7.20.5.tgz#3df15f27ba85319caa07ba08d0721889bb39c017"
@@ -2931,11 +2960,19 @@ ansi-styles@^4.0.0, ansi-styles@^4.1.0:
dependencies:
color-convert "^2.0.1"
-ansi-styles@^5.0.0:
+ansi-styles@^5.0.0, ansi-styles@^5.2.0:
version "5.2.0"
resolved "https://registry.yarnpkg.com/ansi-styles/-/ansi-styles-5.2.0.tgz#07449690ad45777d1924ac2abb2fc8895dba836b"
integrity sha512-Cxwpt2SfTzTtXcfOlzGEee8O+c+MmUgGrNiBcXnuWxuFJHe6a5Hz7qwhwe5OgaSYI0IJvkLqWX1ASG+cJOkEiA==
+anvaad-js@^1.5.0:
+ version "1.5.0"
+ resolved "https://registry.yarnpkg.com/anvaad-js/-/anvaad-js-1.5.0.tgz#e8d05d543dbec4080ac5d635003c20df1465044c"
+ integrity sha512-T9lgmnR6sBivodzmm2bIyjYy7f6WaettzgqwqFmsIopufpnY+eabSWuxiYRqFpOdpr4Pe+RV941EUjItBmhBRA==
+ dependencies:
+ lodash.unescape "^4.0.1"
+ string.prototype.padstart "^3.1.6"
+
anymatch@^3.0.3:
version "3.1.3"
resolved "https://registry.yarnpkg.com/anymatch/-/anymatch-3.1.3.tgz#790c58b19ba1720a84205b57c618d5ad8524973e"
@@ -2974,7 +3011,7 @@ array-buffer-byte-length@^1.0.1, array-buffer-byte-length@^1.0.2:
call-bound "^1.0.3"
is-array-buffer "^3.0.5"
-array-includes@^3.1.6, array-includes@^3.1.8:
+array-includes@^3.1.6, array-includes@^3.1.8, array-includes@^3.1.9:
version "3.1.9"
resolved "https://registry.yarnpkg.com/array-includes/-/array-includes-3.1.9.tgz#1f0ccaa08e90cdbc3eb433210f903ad0f17c3f3a"
integrity sha512-FmeCCAenzH0KH381SPT5FZmiA/TmpndpcaShhfgEN9eCVjnFBqq3l1xrI42y8+PPLI6hypzou4GXw00WHmPBLQ==
@@ -3005,7 +3042,7 @@ array.prototype.findlast@^1.2.5:
es-object-atoms "^1.0.0"
es-shim-unscopables "^1.0.2"
-array.prototype.findlastindex@^1.2.5:
+array.prototype.findlastindex@^1.2.6:
version "1.2.6"
resolved "https://registry.yarnpkg.com/array.prototype.findlastindex/-/array.prototype.findlastindex-1.2.6.tgz#cfa1065c81dcb64e34557c9b81d012f6a421c564"
integrity sha512-F/TKATkzseUExPlfvmwQKGITM3DGTK+vkAsCZoDc5daVygbJBnjEUCbgkAvVFsgfXfX4YIqZ/27G3k3tdXrTxQ==
@@ -3018,7 +3055,7 @@ array.prototype.findlastindex@^1.2.5:
es-object-atoms "^1.1.1"
es-shim-unscopables "^1.1.0"
-array.prototype.flat@^1.3.1, array.prototype.flat@^1.3.2:
+array.prototype.flat@^1.3.1, array.prototype.flat@^1.3.3:
version "1.3.3"
resolved "https://registry.yarnpkg.com/array.prototype.flat/-/array.prototype.flat-1.3.3.tgz#534aaf9e6e8dd79fb6b9a9917f839ef1ec63afe5"
integrity sha512-rwG/ja1neyLqCuGZ5YYrznA62D4mZXg0i1cIskIUKSiqF3Cje9/wXAls9B9s1Wa2fomMsIv8czB8jZcPmxCXFg==
@@ -3134,8 +3171,6 @@ babel-plugin-istanbul@^6.1.1:
"@istanbuljs/schema" "^0.1.2"
istanbul-lib-instrument "^5.0.4"
test-exclude "^6.0.0"
- istanbul-lib-instrument "^5.0.4"
- test-exclude "^6.0.0"
babel-plugin-jest-hoist@^29.6.3:
version "29.6.3"
@@ -3831,7 +3866,6 @@ doctrine@^2.1.0:
integrity sha512-35mSku4ZXK0vfCuHEDAwt55dg2jNajHZ1odvF+8SSr82EsZY4QmXfuWso8oEd8zRhVObSN18aM0CjSdoBX7zIw==
dependencies:
esutils "^2.0.2"
- esutils "^2.0.2"
doctrine@^3.0.0:
version "3.0.0"
@@ -4139,10 +4173,10 @@ eslint-import-resolver-node@^0.3.9:
is-core-module "^2.13.0"
resolve "^1.22.4"
-eslint-module-utils@^2.12.0:
- version "2.12.0"
- resolved "https://registry.yarnpkg.com/eslint-module-utils/-/eslint-module-utils-2.12.0.tgz#fe4cfb948d61f49203d7b08871982b65b9af0b0b"
- integrity sha512-wALZ0HFoytlyh/1+4wuZ9FJCD/leWHQzzrxJ8+rebyReSLk7LApMyd3WJaLVoN+D5+WIdJyDK1c6JnE65V4Zyg==
+eslint-module-utils@^2.12.1:
+ version "2.12.1"
+ resolved "https://registry.yarnpkg.com/eslint-module-utils/-/eslint-module-utils-2.12.1.tgz#f76d3220bfb83c057651359295ab5854eaad75ff"
+ integrity sha512-L8jSWTze7K2mTg0vos/RuLRS5soomksDPoJLXIslC7c8Wmut3bx7CPpJijDcBZtxQ5lrbUdM+s0OlNbz0DCDNw==
dependencies:
debug "^3.2.7"
@@ -4162,29 +4196,29 @@ eslint-plugin-ft-flow@^2.0.1:
lodash "^4.17.21"
string-natural-compare "^3.0.1"
-eslint-plugin-import@^2.27.5:
- version "2.31.0"
- resolved "https://registry.yarnpkg.com/eslint-plugin-import/-/eslint-plugin-import-2.31.0.tgz#310ce7e720ca1d9c0bb3f69adfd1c6bdd7d9e0e7"
- integrity sha512-ixmkI62Rbc2/w8Vfxyh1jQRTdRTF52VxwRVHl/ykPAmqG+Nb7/kNn+byLP0LxPgI7zWA16Jt82SybJInmMia3A==
+eslint-plugin-import@^2.32.0:
+ version "2.32.0"
+ resolved "https://registry.yarnpkg.com/eslint-plugin-import/-/eslint-plugin-import-2.32.0.tgz#602b55faa6e4caeaa5e970c198b5c00a37708980"
+ integrity sha512-whOE1HFo/qJDyX4SnXzP4N6zOWn79WhnCUY/iDR0mPfQZO8wcYE4JClzI2oZrhBnnMUCBCHZhO6VQyoBU95mZA==
dependencies:
"@rtsao/scc" "^1.1.0"
- array-includes "^3.1.8"
- array.prototype.findlastindex "^1.2.5"
- array.prototype.flat "^1.3.2"
- array.prototype.flatmap "^1.3.2"
+ array-includes "^3.1.9"
+ array.prototype.findlastindex "^1.2.6"
+ array.prototype.flat "^1.3.3"
+ array.prototype.flatmap "^1.3.3"
debug "^3.2.7"
doctrine "^2.1.0"
eslint-import-resolver-node "^0.3.9"
- eslint-module-utils "^2.12.0"
+ eslint-module-utils "^2.12.1"
hasown "^2.0.2"
- is-core-module "^2.15.1"
+ is-core-module "^2.16.1"
is-glob "^4.0.3"
minimatch "^3.1.2"
object.fromentries "^2.0.8"
object.groupby "^1.0.3"
- object.values "^1.2.0"
+ object.values "^1.2.1"
semver "^6.3.1"
- string.prototype.trimend "^1.0.8"
+ string.prototype.trimend "^1.0.9"
tsconfig-paths "^3.15.0"
eslint-plugin-jest@^27.9.0:
@@ -4221,7 +4255,6 @@ eslint-plugin-prettier@^4.0.0:
integrity sha512-f/0rXLXUt0oFYs8ra4w49wYZBG5GKZpAYsJSm6rnYL5uVDjd+zowwMwVZHnAjf4edNrKpCDYfXDgmRE/Ak7QyQ==
dependencies:
prettier-linter-helpers "^1.0.0"
- prettier-linter-helpers "^1.0.0"
eslint-plugin-react-hooks@^4.6.0:
version "4.6.2"
@@ -4245,6 +4278,13 @@ eslint-plugin-react-native@^4.0.0:
dependencies:
eslint-plugin-react-native-globals "^0.1.1"
+eslint-plugin-react-native@^5.0.0:
+ version "5.0.0"
+ resolved "https://registry.yarnpkg.com/eslint-plugin-react-native/-/eslint-plugin-react-native-5.0.0.tgz#2ee990ba4967c557183b31121578547fb5c02d5d"
+ integrity sha512-VyWlyCC/7FC/aONibOwLkzmyKg4j9oI8fzrk9WYNs4I8/m436JuOTAFwLvEn1CVvc7La4cPfbCyspP4OYpP52Q==
+ dependencies:
+ eslint-plugin-react-native-globals "^0.1.1"
+
eslint-plugin-react@^7.30.1, eslint-plugin-react@^7.37.4:
version "7.37.5"
resolved "https://registry.yarnpkg.com/eslint-plugin-react/-/eslint-plugin-react-7.37.5.tgz#2975511472bdda1b272b34d779335c9b0e877065"
@@ -4276,8 +4316,6 @@ eslint-scope@5.1.1, eslint-scope@^5.1.1:
dependencies:
esrecurse "^4.3.0"
estraverse "^4.1.1"
- esrecurse "^4.3.0"
- estraverse "^4.1.1"
eslint-scope@^7.2.2:
version "7.2.2"
@@ -4375,7 +4413,6 @@ esquery@^1.4.2:
integrity sha512-ca9pw9fomFcKPvFLXhBKUK90ZvGibiGOvRJNbjljY7s7uq/5YO4BOzcYtJqExdx99rF6aAcnRxHmcUHcz6sQsg==
dependencies:
estraverse "^5.1.0"
- estraverse "^5.1.0"
esrecurse@^4.3.0:
version "4.3.0"
@@ -4383,7 +4420,6 @@ esrecurse@^4.3.0:
integrity sha512-KmfKL3b6G+RXvP8N1vr3Tq1kL/oCFgn2NYXEtqP8/L3pKapUA4G8cFVaoF3SU323CD4XypR/ffioHmkti6/Tag==
dependencies:
estraverse "^5.2.0"
- estraverse "^5.2.0"
estraverse@^4.1.1:
version "4.3.0"
@@ -4518,7 +4554,6 @@ fill-range@^7.1.1:
integrity sha512-YsGpe3WHLK8ZYi4tWDg2Jy3ebRz2rXowDxnld4bkQB00cc/1Zw9AWnC0i9ztDJitivtQvaI9KaLyKrc+hBW0yg==
dependencies:
to-regex-range "^5.0.1"
- to-regex-range "^5.0.1"
filter-obj@^1.1.0:
version "1.1.0"
@@ -4537,13 +4572,6 @@ finalhandler@1.1.2:
parseurl "~1.3.3"
statuses "~1.5.0"
unpipe "~1.0.0"
- debug "2.6.9"
- encodeurl "~1.0.2"
- escape-html "~1.0.3"
- on-finished "~2.3.0"
- parseurl "~1.3.3"
- statuses "~1.5.0"
- unpipe "~1.0.0"
find-babel-config@^2.1.1:
version "2.1.2"
@@ -4567,7 +4595,6 @@ find-up@^3.0.0:
integrity sha512-1yD6RmLI1XBfxugvORwlck6f75tYL+iR0jqwsOrOxMZyGYqUuDhJ0l4AXdO1iX/FTs9cBAMEk1gWSEx1kSbylg==
dependencies:
locate-path "^3.0.0"
- locate-path "^3.0.0"
find-up@^4.0.0, find-up@^4.1.0:
version "4.1.0"
@@ -4576,8 +4603,6 @@ find-up@^4.0.0, find-up@^4.1.0:
dependencies:
locate-path "^5.0.0"
path-exists "^4.0.0"
- locate-path "^5.0.0"
- path-exists "^4.0.0"
find-up@^5.0.0:
version "5.0.0"
@@ -4587,39 +4612,39 @@ find-up@^5.0.0:
locate-path "^6.0.0"
path-exists "^4.0.0"
-firebase@11.3.1:
- version "11.3.1"
- resolved "https://registry.yarnpkg.com/firebase/-/firebase-11.3.1.tgz#1507b2b1e3af17418fbe009e82d7bc30a6b5117c"
- integrity sha512-P4YVFM0Bm2d8aO61SCEMF8E1pYgieGLrmr/LFw7vs6sAMebwuwHt+Wug+1qL2fhAHWPwpWbCLsdJH8NQ+4Sw8Q==
- dependencies:
- "@firebase/analytics" "0.10.11"
- "@firebase/analytics-compat" "0.2.17"
- "@firebase/app" "0.11.1"
- "@firebase/app-check" "0.8.11"
- "@firebase/app-check-compat" "0.3.18"
- "@firebase/app-compat" "0.2.50"
+firebase@12.1.0:
+ version "12.1.0"
+ resolved "https://registry.yarnpkg.com/firebase/-/firebase-12.1.0.tgz#38f9c019d0cfd2fdf6c268355a5819aeb559cd5d"
+ integrity sha512-oZucxvfWKuAW4eHHRqGKzC43fLiPqPwHYBHPRNsnkgonqYaq0VurYgqgBosRlEulW+TWja/5Tpo2FpUU+QrfEQ==
+ dependencies:
+ "@firebase/ai" "2.1.0"
+ "@firebase/analytics" "0.10.18"
+ "@firebase/analytics-compat" "0.2.24"
+ "@firebase/app" "0.14.1"
+ "@firebase/app-check" "0.11.0"
+ "@firebase/app-check-compat" "0.4.0"
+ "@firebase/app-compat" "0.5.1"
"@firebase/app-types" "0.9.3"
- "@firebase/auth" "1.9.0"
- "@firebase/auth-compat" "0.5.18"
- "@firebase/data-connect" "0.3.0"
- "@firebase/database" "1.0.12"
- "@firebase/database-compat" "2.0.3"
- "@firebase/firestore" "4.7.8"
- "@firebase/firestore-compat" "0.3.43"
- "@firebase/functions" "0.12.2"
- "@firebase/functions-compat" "0.3.19"
- "@firebase/installations" "0.6.12"
- "@firebase/installations-compat" "0.2.12"
- "@firebase/messaging" "0.12.16"
- "@firebase/messaging-compat" "0.2.16"
- "@firebase/performance" "0.7.0"
- "@firebase/performance-compat" "0.2.13"
- "@firebase/remote-config" "0.5.0"
- "@firebase/remote-config-compat" "0.2.12"
- "@firebase/storage" "0.13.6"
- "@firebase/storage-compat" "0.3.16"
- "@firebase/util" "1.10.3"
- "@firebase/vertexai" "1.0.4"
+ "@firebase/auth" "1.11.0"
+ "@firebase/auth-compat" "0.6.0"
+ "@firebase/data-connect" "0.3.11"
+ "@firebase/database" "1.1.0"
+ "@firebase/database-compat" "2.1.0"
+ "@firebase/firestore" "4.9.0"
+ "@firebase/firestore-compat" "0.4.0"
+ "@firebase/functions" "0.13.0"
+ "@firebase/functions-compat" "0.4.0"
+ "@firebase/installations" "0.6.19"
+ "@firebase/installations-compat" "0.2.19"
+ "@firebase/messaging" "0.12.23"
+ "@firebase/messaging-compat" "0.2.23"
+ "@firebase/performance" "0.7.9"
+ "@firebase/performance-compat" "0.2.22"
+ "@firebase/remote-config" "0.6.6"
+ "@firebase/remote-config-compat" "0.2.19"
+ "@firebase/storage" "0.14.0"
+ "@firebase/storage-compat" "0.4.0"
+ "@firebase/util" "1.13.0"
flat-cache@^3.0.4:
version "3.2.0"
@@ -4961,8 +4986,6 @@ import-fresh@^2.0.0:
dependencies:
caller-path "^2.0.0"
resolve-from "^3.0.0"
- caller-path "^2.0.0"
- resolve-from "^3.0.0"
import-fresh@^3.2.1, import-fresh@^3.3.0:
version "3.3.1"
@@ -4971,8 +4994,6 @@ import-fresh@^3.2.1, import-fresh@^3.3.0:
dependencies:
parent-module "^1.0.0"
resolve-from "^4.0.0"
- parent-module "^1.0.0"
- resolve-from "^4.0.0"
import-local@^3.0.2:
version "3.2.0"
@@ -4987,6 +5008,11 @@ imurmurhash@^0.1.4:
resolved "https://registry.yarnpkg.com/imurmurhash/-/imurmurhash-0.1.4.tgz#9218b9b2b928a238b13dc4fb6b6d576f231453ea"
integrity sha512-JmXMZ6wuvDmLiHEml9ykzqO6lwFbof0GG4IkcGaENdCRDDmMVnny7s5HsIgHCbaq0w2MyPhDqkhTUgS2LU2PHA==
+indent-string@^4.0.0:
+ version "4.0.0"
+ resolved "https://registry.yarnpkg.com/indent-string/-/indent-string-4.0.0.tgz#624f8f4497d619b2d9768531d58f4122854d7251"
+ integrity sha512-EdDDZu4A2OyIK7Lr/2zG+w5jmbuk1DVBnEwREQvBzspBJkCEbRa8GxU1lghYcaGJCnRWibjDXlq779X1/y5xwg==
+
inflight@^1.0.4:
version "1.0.6"
resolved "https://registry.yarnpkg.com/inflight/-/inflight-1.0.6.tgz#49bd6331d7d02d0c09bc910a1075ba8165b56df9"
@@ -4994,8 +5020,6 @@ inflight@^1.0.4:
dependencies:
once "^1.3.0"
wrappy "1"
- once "^1.3.0"
- wrappy "1"
inherits@2, inherits@2.0.4, inherits@^2.0.3, inherits@^2.0.4, inherits@~2.0.3:
version "2.0.4"
@@ -5017,7 +5041,6 @@ invariant@2.2.4, invariant@^2.2.4:
integrity sha512-phJfQVBuaJM5raOpJjSfkiD6BpbCE4Ns//LaXl6wGYtUBY83nWS6Rf9tXm2e8VaK60JEjYldbPif/A2B1C2gNA==
dependencies:
loose-envify "^1.0.0"
- loose-envify "^1.0.0"
is-array-buffer@^3.0.4, is-array-buffer@^3.0.5:
version "3.0.5"
@@ -5069,7 +5092,7 @@ is-callable@^1.2.7:
resolved "https://registry.yarnpkg.com/is-callable/-/is-callable-1.2.7.tgz#3bc2a85ea742d9e36205dcacdd72ca1fdc51b055"
integrity sha512-1BC0BVFhS/p0qtw6enp8e+8OD0UrK0oFLztSjNzhcKA3WDuJxxAPXzPuPtKkjEY9UUoEWlX/8fgKeu2S8i9JTA==
-is-core-module@^2.13.0, is-core-module@^2.15.1, is-core-module@^2.16.0:
+is-core-module@^2.13.0, is-core-module@^2.16.0, is-core-module@^2.16.1:
version "2.16.1"
resolved "https://registry.yarnpkg.com/is-core-module/-/is-core-module-2.16.1.tgz#2a98801a849f43e2add644fbb6bc6229b19a4ef4"
integrity sha512-UfoeMA6fIJ8wTYFEUjelnaGI67v6+N7qXJEvQuIGa99l4xsCruSYOVSQ0uPANn4dAzm8lkYPaKLrrijLq7x23w==
@@ -5146,7 +5169,6 @@ is-glob@^4.0.0, is-glob@^4.0.1, is-glob@^4.0.3:
integrity sha512-xelSayHH36ZgE7ZWhli7pW34hNbNl8Ojv5KVmkJD4hBdD3th8Tfk9vYasLM+mXWOZhFkgZfxhLSnrwRr4elSSg==
dependencies:
is-extglob "^2.1.1"
- is-extglob "^2.1.1"
is-interactive@^1.0.0:
version "1.0.0"
@@ -5192,7 +5214,6 @@ is-plain-object@^2.0.4:
integrity sha512-h5PpgXkWitc38BBMYawTYMWJHFZJVnBquFE57xFpjB8pJFiF6gZ+bU+WyI/yqXiFR5mdLsgYNaPe8uao6Uv9Og==
dependencies:
isobject "^3.0.1"
- isobject "^3.0.1"
is-regex@^1.2.1:
version "1.2.1"
@@ -5341,9 +5362,6 @@ istanbul-lib-source-maps@^4.0.0:
debug "^4.1.1"
istanbul-lib-coverage "^3.0.0"
source-map "^0.6.1"
- debug "^4.1.1"
- istanbul-lib-coverage "^3.0.0"
- source-map "^0.6.1"
istanbul-reports@^3.1.3:
version "3.1.7"
@@ -5445,6 +5463,16 @@ jest-config@^29.7.0:
slash "^3.0.0"
strip-json-comments "^3.1.1"
+jest-diff@30.2.0:
+ version "30.2.0"
+ resolved "https://registry.yarnpkg.com/jest-diff/-/jest-diff-30.2.0.tgz#e3ec3a6ea5c5747f605c9e874f83d756cba36825"
+ integrity sha512-dQHFo3Pt4/NLlG5z4PxZ/3yZTZ1C7s9hveiOj+GCN+uT109NC2QgsoVZsVOAvbJ3RgKkvyLGXZV9+piDpWbm6A==
+ dependencies:
+ "@jest/diff-sequences" "30.0.1"
+ "@jest/get-type" "30.1.0"
+ chalk "^4.1.2"
+ pretty-format "30.2.0"
+
jest-diff@^29.7.0:
version "29.7.0"
resolved "https://registry.yarnpkg.com/jest-diff/-/jest-diff-29.7.0.tgz#017934a66ebb7ecf6f205e84699be10afd70458a"
@@ -5508,7 +5536,6 @@ jest-haste-map@^29.7.0:
walker "^1.0.8"
optionalDependencies:
fsevents "^2.3.2"
- fsevents "^2.3.2"
jest-leak-detector@^29.7.0:
version "29.7.0"
@@ -5528,6 +5555,16 @@ jest-matcher-utils@^29.7.0:
jest-get-type "^29.6.3"
pretty-format "^29.7.0"
+jest-matcher-utils@^30.0.5:
+ version "30.2.0"
+ resolved "https://registry.yarnpkg.com/jest-matcher-utils/-/jest-matcher-utils-30.2.0.tgz#69a0d4c271066559ec8b0d8174829adc3f23a783"
+ integrity sha512-dQ94Nq4dbzmUWkQ0ANAWS9tBRfqCrn0bV9AMYdOi/MHW726xn7eQmMeRTpX2ViC00bpNaWXq+7o4lIQ3AX13Hg==
+ dependencies:
+ "@jest/get-type" "30.1.0"
+ chalk "^4.1.2"
+ jest-diff "30.2.0"
+ pretty-format "30.2.0"
+
jest-message-util@^29.7.0:
version "29.7.0"
resolved "https://registry.yarnpkg.com/jest-message-util/-/jest-message-util-29.7.0.tgz#8bc392e204e95dfe7564abbe72a404e28e51f7f3"
@@ -5677,10 +5714,6 @@ jest-util@^29.7.0:
ci-info "^3.2.0"
graceful-fs "^4.2.9"
picomatch "^2.2.3"
- chalk "^4.0.0"
- ci-info "^3.2.0"
- graceful-fs "^4.2.9"
- picomatch "^2.2.3"
jest-validate@^29.7.0:
version "29.7.0"
@@ -5829,7 +5862,6 @@ json5@^1.0.2:
integrity sha512-g1MWMLBiz8FKi1e4w0UyVL3w+iJceWAFBAaBnnGKOpNa5f8TLktkbre1+s6oICydWAm+HRUGTmI+//xv2hvXYA==
dependencies:
minimist "^1.2.0"
- minimist "^1.2.0"
json5@^2.2.3:
version "2.2.3"
@@ -5842,7 +5874,6 @@ jsonfile@^4.0.0:
integrity sha512-m6F1R3z8jjlf2imQHS2Qez5sjKWQzbuuhuJ/FKYFRZvPE3PuHcSMVZzfsLhGVOkfd20obL5SWEBew5ShlquNxg==
optionalDependencies:
graceful-fs "^4.1.6"
- graceful-fs "^4.1.6"
"jsx-ast-utils@^2.4.1 || ^3.0.0", jsx-ast-utils@^3.3.5:
version "3.3.5"
@@ -5956,6 +5987,11 @@ lodash.throttle@^4.1.1:
resolved "https://registry.yarnpkg.com/lodash.throttle/-/lodash.throttle-4.1.1.tgz#c23e91b710242ac70c37f1e1cda9274cc39bf2f4"
integrity sha512-wIkUCfVKpVsWo3JSZlc+8MB5it+2AN5W8J7YVMST30UrvcQNZ1Okbj+rbVniijTWE6FGYy4XJq/rHkas8qJMLQ==
+lodash.unescape@^4.0.1:
+ version "4.0.1"
+ resolved "https://registry.yarnpkg.com/lodash.unescape/-/lodash.unescape-4.0.1.tgz#bf2249886ce514cda112fae9218cdc065211fc9c"
+ integrity sha512-DhhGRshNS1aX6s5YdBE3njCCouPgnG29ebyHvImlZzXZf2SHgt+J08DHgytTPnpywNbO1Y8mNUFyQuIDBq2JZg==
+
lodash@^4.17.15, lodash@^4.17.21:
version "4.17.21"
resolved "https://registry.yarnpkg.com/lodash/-/lodash-4.17.21.tgz#679591c564c3bffaae8454cf0b3df370c3d6911c"
@@ -5968,8 +6004,6 @@ log-symbols@^4.1.0:
dependencies:
chalk "^4.1.0"
is-unicode-supported "^0.1.0"
- chalk "^4.1.0"
- is-unicode-supported "^0.1.0"
logkitty@^0.7.1:
version "0.7.1"
@@ -6279,7 +6313,6 @@ mime-types@^2.1.27, mime-types@~2.1.34:
integrity sha512-ZDY+bPm5zTTF+YpCrAU9nK0UgICYPT0QtT1NZWFv4s++TNkcgVaT0g6+4R2uI4MjQjzysHB1zxuWL50hzaeXiw==
dependencies:
mime-db "1.52.0"
- mime-db "1.52.0"
mime@1.6.0:
version "1.6.0"
@@ -6296,6 +6329,11 @@ mimic-fn@^2.1.0:
resolved "https://registry.yarnpkg.com/mimic-fn/-/mimic-fn-2.1.0.tgz#7ed2c2ccccaf84d3ffcb7a69b57711fc2083401b"
integrity sha512-OqbOk5oEQeAZ8WXWydlu9HJjz9WVdEIvamMCcXmuqUYjTknH/sqsWvhQ3vgwKFRR1HpjvNBKQ37nbJgYzGqGcg==
+min-indent@^1.0.0:
+ version "1.0.1"
+ resolved "https://registry.yarnpkg.com/min-indent/-/min-indent-1.0.1.tgz#a63f681673b30571fbe8bc25686ae746eefa9869"
+ integrity sha512-I9jwMn07Sy/IwOj3zVkVik2JTvgpaykDZEigL6Rx6N9LbMywwUSMtxET+7lVoDLLd3O3IXwJwvuuns8UB/HeAg==
+
minimatch@^3.0.4, minimatch@^3.0.5, minimatch@^3.1.1, minimatch@^3.1.2:
version "3.1.2"
resolved "https://registry.yarnpkg.com/minimatch/-/minimatch-3.1.2.tgz#19cd194bfd3e428f049a70817c038d89ab4be35b"
@@ -6494,7 +6532,7 @@ object.groupby@^1.0.3:
define-properties "^1.2.1"
es-abstract "^1.23.2"
-object.values@^1.1.6, object.values@^1.2.0, object.values@^1.2.1:
+object.values@^1.1.6, object.values@^1.2.1:
version "1.2.1"
resolved "https://registry.yarnpkg.com/object.values/-/object.values-1.2.1.tgz#deed520a50809ff7f75a7cfd4bc64c7a038c6216"
integrity sha512-gXah6aZrcUxjWg2zR2MwouP2eHlCBzdV4pygudehaKXSGW4v2AsRQUK+lwwXhii6KFZcunEnmSUoYp5CXibxtA==
@@ -6652,9 +6690,6 @@ parse-json@^5.2.0:
error-ex "^1.3.1"
json-parse-even-better-errors "^2.3.0"
lines-and-columns "^1.1.6"
- error-ex "^1.3.1"
- json-parse-even-better-errors "^2.3.0"
- lines-and-columns "^1.1.6"
parseurl@~1.3.3:
version "1.3.3"
@@ -6725,7 +6760,6 @@ pkg-dir@^3.0.0:
integrity sha512-/E57AYkoeQ25qkxMj5PBOVgF8Kiu/h7cYS30Z5+R7WaiCCBfLq58ZI/dSeaEKb9WVJV5n/03QwrN3IeWIFllvw==
dependencies:
find-up "^3.0.0"
- find-up "^3.0.0"
pkg-dir@^4.2.0:
version "4.2.0"
@@ -6733,7 +6767,6 @@ pkg-dir@^4.2.0:
integrity sha512-HRDzbaKjC+AOWVXxAU/x54COGeIv9eb+6CkDSQoNTt4XyWoIJvuPsXizxu/Fr23EiekbtZwmh1IcIG/l/a10GQ==
dependencies:
find-up "^4.0.0"
- find-up "^4.0.0"
pkg-up@^3.1.0:
version "3.1.0"
@@ -6758,13 +6791,21 @@ prettier-linter-helpers@^1.0.0:
integrity sha512-GbK2cP9nraSSUF9N2XwUwqfzlAFlMNYYl+ShE/V+H8a9uNl/oUqB1w2EL54Jh0OlyRSd8RfWYJ3coVS4TROP2w==
dependencies:
fast-diff "^1.1.2"
- fast-diff "^1.1.2"
prettier@^2.8.8:
version "2.8.8"
resolved "https://registry.yarnpkg.com/prettier/-/prettier-2.8.8.tgz#e8c5d7e98a4305ffe3de2e1fc4aca1a71c28b1da"
integrity sha512-tdN8qQGvNjw4CHbY+XXk0JgCXn9QiF21a55rBe5LJAU+kDyC4WQn4+awm2Xfk2lQMk5fKup9XgzTZtGkjBdP9Q==
+pretty-format@30.2.0, pretty-format@^30.0.5:
+ version "30.2.0"
+ resolved "https://registry.yarnpkg.com/pretty-format/-/pretty-format-30.2.0.tgz#2d44fe6134529aed18506f6d11509d8a62775ebe"
+ integrity sha512-9uBdv/B4EefsuAL+pWqueZyZS2Ba+LxfFeQ9DN14HU4bN8bhaxKdkpjpB6fs9+pSjIBu+FXQHImEg8j/Lw0+vA==
+ dependencies:
+ "@jest/schemas" "30.0.5"
+ ansi-styles "^5.2.0"
+ react-is "^18.3.1"
+
pretty-format@^26.6.2:
version "26.6.2"
resolved "https://registry.yarnpkg.com/pretty-format/-/pretty-format-26.6.2.tgz#e35c2705f14cb7fe2fe94fa078345b444120fc93"
@@ -6774,9 +6815,6 @@ pretty-format@^26.6.2:
ansi-regex "^5.0.0"
ansi-styles "^4.0.0"
react-is "^17.0.1"
- ansi-regex "^5.0.0"
- ansi-styles "^4.0.0"
- react-is "^17.0.1"
pretty-format@^29.0.0, pretty-format@^29.7.0:
version "29.7.0"
@@ -6793,7 +6831,6 @@ promise@^8.3.0:
integrity sha512-rZPNPKTOYVNEEKFaq1HqTgOwZD+4/YHS5ukLzQCypkj+OkYx7iv0mA91lJlpPPZ8vMau3IIGj5Qlwrx+8iiSmg==
dependencies:
asap "~2.0.6"
- asap "~2.0.6"
prompts@^2.0.1, prompts@^2.4.2:
version "2.4.2"
@@ -6874,8 +6911,6 @@ react-devtools-core@^6.0.1:
dependencies:
shell-quote "^1.6.1"
ws "^7"
- shell-quote "^1.6.1"
- ws "^7"
react-freeze@^1.0.0:
version "1.0.4"
@@ -6892,7 +6927,7 @@ react-is@^17.0.1:
resolved "https://registry.yarnpkg.com/react-is/-/react-is-17.0.2.tgz#e691d4a8e9c789365655539ab372762b0efb54f0"
integrity sha512-w2GsyukL62IJnlaff/nRegPQR94C/XXamvMWmSHRJ4y7Ts/4ocGRmTHvOs8PSE6pB3dWOrD/nueuU5sduBsQ4w==
-react-is@^18.0.0:
+react-is@^18.0.0, react-is@^18.3.1:
version "18.3.1"
resolved "https://registry.yarnpkg.com/react-is/-/react-is-18.3.1.tgz#e83557dc12eae63a99e003a46388b1dcbb44db7e"
integrity sha512-/LLMVyas0ljjAtoYiPqYiL8VWXzUUdThrmU5+n20DZv+a+ClRoevUzw5JxU+Ieh5/c87ytoTBV9G1FiKfNJdmg==
@@ -6945,9 +6980,9 @@ react-native-fs@^2.20.0:
utf8 "^3.0.0"
react-native-gesture-handler@^2.25.0:
- version "2.26.0"
- resolved "https://registry.yarnpkg.com/react-native-gesture-handler/-/react-native-gesture-handler-2.26.0.tgz#e8774c8cd90f7e72c0ecade0ac1b4f7160fcbd5f"
- integrity sha512-pfE1j9Vzu0qpWj/Aq1IK+cYnougN69mCKvWuq1rdNjH2zs1WIszF0Mum9/oGQTemgjyc/JgiqOOTgwcleAMAGg==
+ version "2.28.0"
+ resolved "https://registry.yarnpkg.com/react-native-gesture-handler/-/react-native-gesture-handler-2.28.0.tgz#07fb4f5eae72f810aac3019b060d26c1835bfd0c"
+ integrity sha512-0msfJ1vRxXKVgTgvL+1ZOoYw3/0z1R+Ked0+udoJhyplC2jbVKIJ8Z1bzWdpQRCV3QcQ87Op0zJVE5DhKK2A0A==
dependencies:
"@egjs/hammerjs" "^2.0.17"
hoist-non-react-statics "^3.3.0"
@@ -6958,13 +6993,17 @@ react-native-is-edge-to-edge@1.1.7, react-native-is-edge-to-edge@^1.1.7:
resolved "https://registry.yarnpkg.com/react-native-is-edge-to-edge/-/react-native-is-edge-to-edge-1.1.7.tgz#28947688f9fafd584e73a4f935ea9603bd9b1939"
integrity sha512-EH6i7E8epJGIcu7KpfXYXiV2JFIYITtq+rVS8uEb+92naMRBdxhTuS8Wn2Q7j9sqyO0B+Xbaaf9VdipIAmGW4w==
+react-native-linear-gradient@^2.8.3:
+ version "2.8.3"
+ resolved "https://registry.yarnpkg.com/react-native-linear-gradient/-/react-native-linear-gradient-2.8.3.tgz#9a116649f86d74747304ee13db325e20b21e564f"
+ integrity sha512-KflAXZcEg54PXkLyflaSZQ3PJp4uC4whM7nT/Uot9m0e/qxFV3p6uor1983D1YOBJbJN7rrWdqIjq0T42jOJyA==
+
react-native-localization@^2.3.1:
version "2.3.2"
resolved "https://registry.yarnpkg.com/react-native-localization/-/react-native-localization-2.3.2.tgz#ac82089519681a8e53fa39dcf7a9f5b6be2c5cb6"
integrity sha512-9i2wkCFwvKVXq0w6tu1eTCGUZuKBQXoCGWBWbW4HYsFJe+MEQsFCYdrhoBsn8IAr/KA9AYUiEyxxuN3hLRxF4w==
dependencies:
react-localization "^1.0.17"
- react-localization "^1.0.17"
react-native-modal-datetime-picker@^18.0.0:
version "18.0.0"
@@ -6972,7 +7011,6 @@ react-native-modal-datetime-picker@^18.0.0:
integrity sha512-0jdvhhraZQlRACwr7pM6vmZ2kxgzJ4CpnmV6J3TVA6MrXMXK6Zo/upRBKkRp0+fTOiKuNblzesA2U59rYo6SGA==
dependencies:
prop-types "^15.7.2"
- prop-types "^15.7.2"
react-native-modal-selector@^2.1.2:
version "2.1.2"
@@ -6980,7 +7018,6 @@ react-native-modal-selector@^2.1.2:
integrity sha512-+Cvoz/yNUFmfIkJ7xkmlLR2nhJOUhx00S6BPqp2Ruy8LkmaiNr7WMZ4BzsgzylyEgZ84Q+42HQ0v0QzJYobviA==
dependencies:
prop-types "^15.5.10"
- prop-types "^15.5.10"
react-native-ratings@^8.1.0:
version "8.1.0"
@@ -7025,9 +7062,6 @@ react-native-screens@^4.10.0:
react-freeze "^1.0.0"
react-native-is-edge-to-edge "^1.1.7"
warn-once "^0.1.0"
- react-freeze "^1.0.0"
- react-native-is-edge-to-edge "^1.1.7"
- warn-once "^0.1.0"
react-native-size-matters@^0.4.0:
version "0.4.2"
@@ -7063,6 +7097,16 @@ react-native-svg@^15.11.2:
css-tree "^1.1.3"
warn-once "0.1.1"
+react-native-toast-message@^2.3.3:
+ version "2.3.3"
+ resolved "https://registry.yarnpkg.com/react-native-toast-message/-/react-native-toast-message-2.3.3.tgz#e301508d386a9902ff6b4559ecc6674f8cfdf97a"
+ integrity sha512-4IIUHwUPvKHu4gjD0Vj2aGQzqPATiblL1ey8tOqsxOWRPGGu52iIbL8M/mCz4uyqecvPdIcMY38AfwRuUADfQQ==
+
+react-native-track-player@^4.1.1:
+ version "4.1.1"
+ resolved "https://registry.yarnpkg.com/react-native-track-player/-/react-native-track-player-4.1.1.tgz#96f8688f270c5a01bc597b6331843b191402502b"
+ integrity sha512-E5N/eK/+HtAVJUAzXpm1cWz8ROheV9jb0TI6h2bM+333U+DWibTTnT2T1122FkCoXLXIYavtm2FR2if+5jH8cA==
+
react-native-vector-icons@^10.2.0:
version "10.2.0"
resolved "https://registry.yarnpkg.com/react-native-vector-icons/-/react-native-vector-icons-10.2.0.tgz#f438f2ca16f7d6be658fd6ec8f0d2b7e2132b91c"
@@ -7070,8 +7114,6 @@ react-native-vector-icons@^10.2.0:
dependencies:
prop-types "^15.7.2"
yargs "^16.1.1"
- prop-types "^15.7.2"
- yargs "^16.1.1"
react-native-webview@^13.12.3:
version "13.15.0"
@@ -7174,6 +7216,14 @@ recast@^0.23.11:
tiny-invariant "^1.3.3"
tslib "^2.0.1"
+redent@^3.0.0:
+ version "3.0.0"
+ resolved "https://registry.yarnpkg.com/redent/-/redent-3.0.0.tgz#e557b7998316bb53c9f1f56fa626352c6963059f"
+ integrity sha512-6tDA8g98We0zd0GvVeMT9arEOnTw9qM03L9cJXaCjrip1OO764RDBLBfrB4cwzNGDj5OA5ioymC9GkizgWJDUg==
+ dependencies:
+ indent-string "^4.0.0"
+ strip-indent "^3.0.0"
+
redux-persist@^6.0.0:
version "6.0.0"
resolved "https://registry.yarnpkg.com/redux-persist/-/redux-persist-6.0.0.tgz#b4d2972f9859597c130d40d4b146fecdab51b3a8"
@@ -7345,7 +7395,6 @@ rimraf@^3.0.2:
integrity sha512-JZkJMZkAGFFPP2YqXZXPbMlMBgsxzE8ILs4lMIX/2o0L9UBw9O/Y3o6wFw/i9YLapcUJWwqbi3kdxIPdC62TIA==
dependencies:
glob "^7.1.3"
- glob "^7.1.3"
run-parallel@^1.1.9:
version "1.2.0"
@@ -7747,6 +7796,17 @@ string.prototype.matchall@^4.0.12:
set-function-name "^2.0.2"
side-channel "^1.1.0"
+string.prototype.padstart@^3.1.6:
+ version "3.1.7"
+ resolved "https://registry.yarnpkg.com/string.prototype.padstart/-/string.prototype.padstart-3.1.7.tgz#529bae9f07186fb0fc6e0709b7f9b1f942aa09d1"
+ integrity sha512-hc5ZFzw8H2Bl4AeHxE5s+CniFg+bPcr7lRRS189GCM6KhJQBACNRhtMsdcnpBNbjc1XisnUOqbP0c94RZU4GCw==
+ dependencies:
+ call-bind "^1.0.8"
+ call-bound "^1.0.4"
+ define-properties "^1.2.1"
+ es-abstract "^1.23.9"
+ es-object-atoms "^1.1.1"
+
string.prototype.repeat@^1.0.0:
version "1.0.0"
resolved "https://registry.yarnpkg.com/string.prototype.repeat/-/string.prototype.repeat-1.0.0.tgz#e90872ee0308b29435aa26275f6e1b762daee01a"
@@ -7768,7 +7828,7 @@ string.prototype.trim@^1.2.10:
es-object-atoms "^1.0.0"
has-property-descriptors "^1.0.2"
-string.prototype.trimend@^1.0.8, string.prototype.trimend@^1.0.9:
+string.prototype.trimend@^1.0.9:
version "1.0.9"
resolved "https://registry.yarnpkg.com/string.prototype.trimend/-/string.prototype.trimend-1.0.9.tgz#62e2731272cd285041b36596054e9f66569b6942"
integrity sha512-G7Ok5C6E/j4SGfyLCloXTrngQIQU3PWtXGst3yM7Bea9FRURf1S42ZHlZZtsNque2FN2PoUhfZXYLNWwEr4dLQ==
@@ -7823,6 +7883,13 @@ strip-final-newline@^2.0.0:
resolved "https://registry.yarnpkg.com/strip-final-newline/-/strip-final-newline-2.0.0.tgz#89b852fb2fcbe936f6f4b3187afb0a12c1ab58ad"
integrity sha512-BrpvfNAE3dcvq7ll3xVumzjKjZQ5tI1sEUIKr3Uoks0XUl45St3FlatVqef9prk4jRDzhW6WZg+3bk93y6pLjA==
+strip-indent@^3.0.0:
+ version "3.0.0"
+ resolved "https://registry.yarnpkg.com/strip-indent/-/strip-indent-3.0.0.tgz#c32e1cee940b6b3432c771bc2c54bcce73cd3001"
+ integrity sha512-laJTa3Jb+VQpaC6DseHhF7dXVqHTfJPCRDaEbid/drOhgitgYku/letMUqOXFoWV0zIIUbjpdH2t+tYj4bQMRQ==
+ dependencies:
+ min-indent "^1.0.0"
+
strip-json-comments@^3.1.1:
version "3.1.1"
resolved "https://registry.yarnpkg.com/strip-json-comments/-/strip-json-comments-3.1.1.tgz#31f1281b3832630434831c310c01cccda8cbe006"
@@ -7849,7 +7916,6 @@ supports-color@^7.1.0:
integrity sha512-qpCAvRl9stuOHveKsn7HncJRvv501qIacKzQlO/+Lwxc9+0q2wLyv4Dfvt80/DPn2pqOBsJdDiogXGR9+OvwRw==
dependencies:
has-flag "^4.0.0"
- has-flag "^4.0.0"
supports-color@^8.0.0:
version "8.1.1"
@@ -7857,7 +7923,6 @@ supports-color@^8.0.0:
integrity sha512-MpUEN2OodtUzxvKQl72cUF7RQ5EiHsGvSsVG0ia9c5RbWGL2CI4C7EpPS8UTBIplnlzZiNuV56w+FuNxy3ty2Q==
dependencies:
has-flag "^4.0.0"
- has-flag "^4.0.0"
supports-preserve-symlinks-flag@^1.0.0:
version "1.0.0"
@@ -8266,9 +8331,6 @@ wrap-ansi@^7.0.0:
ansi-styles "^4.0.0"
string-width "^4.1.0"
strip-ansi "^6.0.0"
- ansi-styles "^4.0.0"
- string-width "^4.1.0"
- strip-ansi "^6.0.0"
wrappy@1:
version "1.0.2"