Compare commits
95 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
| 41ea7eb7b7 | |||
| e31eb9d457 | |||
| 7842e860cb | |||
| daf7bf2ccb | |||
| a712100cfc | |||
| 92e1210fc8 | |||
| 5b4058c093 | |||
| 77ebdebcca | |||
| ea72de0113 | |||
| a1c0820da8 | |||
| c9eb42ff4d | |||
| 8f0c39ab41 | |||
| 4de059d5bb | |||
| 969572b5a0 | |||
| 4236037b6b | |||
| a800c98d02 | |||
| f1c825f826 | |||
| fba8974699 | |||
| 10b90de171 | |||
| 3785dddebc | |||
| 259dd6acc7 | |||
| 90160f7f40 | |||
| 8bdc86c974 | |||
| 5b441562f8 | |||
| 4e1a67efc8 | |||
| cc217e2b04 | |||
| f8d4f50307 | |||
| d0e14d518e | |||
| c06d6f9326 | |||
| 1200b7f75b | |||
| ee7d96daf9 | |||
| 5a6f324638 | |||
| 4b1ff0efc1 | |||
| 45174af3c2 | |||
| baabc073f0 | |||
| 37969da3e9 | |||
| a81b32ae7f | |||
| 389fee8ecb | |||
| 8fcfd22767 | |||
| b216d3739a | |||
| dcceea064a | |||
| 64c027c695 | |||
| 0dd9f2bd94 | |||
| 80228ada1e | |||
| 00c180927e | |||
| 7f61219bb0 | |||
| 00d3a441e3 | |||
| f6b82939de | |||
| 3f2cfc253b | |||
| b107d3ca54 | |||
| 6bda33efd4 | |||
| 1baa3e914c | |||
| ccbbe36ce6 | |||
| 9e81f805c0 | |||
| 47428ec484 | |||
| 58f9427008 | |||
| 5e0f375f7f | |||
| 21611de3aa | |||
| 70520223cd | |||
| 2c8880c9af | |||
| ee864d3e81 | |||
| 6730c773f3 | |||
| 241cb0a664 | |||
| 3d59b8f774 | |||
| 188700112a | |||
| 03f582da98 | |||
| ed215a1d54 | |||
| c487bd2ca6 | |||
| f90a4d1103 | |||
| 8306296736 | |||
| ae022052d5 | |||
| 7299e53df6 | |||
| c513361a5e | |||
| 429829b80b | |||
| ad1ccd815a | |||
| 036608f7a8 | |||
| 8747e8ad39 | |||
| 537cf48fb4 | |||
| 9f51adb196 | |||
| f2d3de4470 | |||
| 5f4507cfd5 | |||
| ecc7a7a6e7 | |||
| 8d5b92bc9e | |||
| 0f5eee1bdb | |||
| 4e3f817792 | |||
| c61c4d0105 | |||
| 659988330c | |||
| 430a067105 | |||
| c2c8935068 | |||
| 9636aab53f | |||
| 22915914fb | |||
| d39dabef68 | |||
| e01cbefdae | |||
| 73fcba007a | |||
| 54a3feb08c |
@@ -1,18 +1,22 @@
|
||||
module.exports = {
|
||||
root: true,
|
||||
extends: '@react-native',
|
||||
parser: '@typescript-eslint/parser',
|
||||
plugins: ['@typescript-eslint'],
|
||||
overrides: [
|
||||
{
|
||||
files: ['*.js', '*.jsx', '*.ts', '*.tsx'],
|
||||
rules: {
|
||||
'@typescript-eslint/no-shadow': ['warn'],
|
||||
'no-shadow': 'off',
|
||||
'no-undef': 'off',
|
||||
'react-native/no-inline-styles': 'warn',
|
||||
'no-console': 'warn',
|
||||
'no-console': 'error',
|
||||
'@typescript-eslint/no-shadow': 'warn',
|
||||
'react-hooks/exhaustive-deps': 'warn',
|
||||
'curly': ['error', 'multi-line', 'consistent'],
|
||||
'no-useless-return': 'error',
|
||||
'block-scoped-var': 'error',
|
||||
'no-var': 'error',
|
||||
'prefer-const': 'error',
|
||||
'no-dupe-else-if': 'error',
|
||||
'no-duplicate-imports': 'error',
|
||||
},
|
||||
},
|
||||
],
|
||||
|
||||
|
After Width: | Height: | Size: 94 KiB |
|
Before Width: | Height: | Size: 48 KiB After Width: | Height: | Size: 48 KiB |
@@ -3,34 +3,59 @@ on:
|
||||
push:
|
||||
branches:
|
||||
- master
|
||||
workflow_dispatch:
|
||||
|
||||
concurrency:
|
||||
group: ${{ github.workflow }}-${{ github.ref }}
|
||||
cancel-in-progress: true
|
||||
|
||||
jobs:
|
||||
build-android:
|
||||
name: Build App
|
||||
runs-on: ubuntu-latest
|
||||
timeout-minutes: 60
|
||||
steps:
|
||||
- name: Clone Repository (Latest)
|
||||
- name: Checkout Repository
|
||||
uses: actions/checkout@v4
|
||||
with:
|
||||
fetch-depth: 0
|
||||
- uses: actions/setup-node@v4
|
||||
|
||||
- name: Setup Node.js
|
||||
uses: actions/setup-node@v4
|
||||
with:
|
||||
node-version: '20'
|
||||
|
||||
- name: Cache npm & node modules
|
||||
- name: Setup pnpm
|
||||
uses: pnpm/action-setup@v4
|
||||
|
||||
- name: Get pnpm Store Directory
|
||||
shell: bash
|
||||
run: |
|
||||
echo "STORE_PATH=$(pnpm store path --silent)" >> $GITHUB_ENV
|
||||
|
||||
- name: Setup pnpm Cache
|
||||
uses: actions/cache@v4
|
||||
with:
|
||||
path: |
|
||||
~/.npm
|
||||
node_modules
|
||||
key: ${{ runner.os }}-cache-npm-${{ hashFiles('**/package-lock.json') }}
|
||||
path: ${{ env.STORE_PATH }}
|
||||
key: ${{ runner.os }}-pnpm-store-${{ hashFiles('**/pnpm-lock.yaml') }}
|
||||
restore-keys: |
|
||||
${{ runner.os }}-cache-npm-
|
||||
|
||||
- name: Install Dependencies
|
||||
run: |
|
||||
npm install
|
||||
${{ runner.os }}-pnpm-store-
|
||||
|
||||
- name: Create .env
|
||||
- name: Setup Java
|
||||
uses: actions/setup-java@v4
|
||||
with:
|
||||
distribution: 'zulu'
|
||||
java-version: '17'
|
||||
|
||||
- name: Setup Gradle
|
||||
uses: gradle/actions/setup-gradle@v4
|
||||
with:
|
||||
cache-read-only: false
|
||||
|
||||
- name: Install Dependencies
|
||||
run: pnpm install --frozen-lockfile
|
||||
|
||||
- name: Create Environment File
|
||||
run: |
|
||||
cat > .env << EOF
|
||||
MYANIMELIST_CLIENT_ID=${{ vars.MYANIMELIST_CLIENT_ID }}
|
||||
@@ -40,66 +65,30 @@ jobs:
|
||||
BUILD_TYPE=Github Action
|
||||
EOF
|
||||
|
||||
- name: Cache Gradle Wrapper
|
||||
uses: actions/cache@v4
|
||||
with:
|
||||
path: ~/.gradle/wrapper
|
||||
key: ${{ runner.os }}-gradle-wrapper-${{ hashFiles('gradle/wrapper/gradle-wrapper.properties') }}
|
||||
|
||||
- name: Cache Gradle Dependencies
|
||||
uses: actions/cache@v4
|
||||
with:
|
||||
path: ~/.gradle/caches
|
||||
key: ${{ runner.os }}-gradle-caches-${{ hashFiles('gradle/wrapper/gradle-wrapper.properties') }}
|
||||
restore-keys: |
|
||||
${{ runner.os }}-gradle-caches-
|
||||
|
||||
- name: Cache build intermediates
|
||||
uses: actions/cache@v4
|
||||
with:
|
||||
path: |
|
||||
android/app/build/generated
|
||||
android/app/build/intermediates
|
||||
android/app/build/tmp
|
||||
android/.gradle
|
||||
key: ${{ runner.os }}-build-${{ hashFiles('package-lock.json') }}
|
||||
|
||||
- name: Make Gradlew Executable
|
||||
run: cd android && chmod +x ./gradlew
|
||||
|
||||
- name: Build Android Release
|
||||
run: |
|
||||
cd android && ./gradlew assembleRelease --no-daemon
|
||||
mv app/build/outputs/apk/release/app-release.apk app/build/outputs/apk/release/LNReader.apk
|
||||
|
||||
- name: Set Env
|
||||
- name: Set Environment Variables
|
||||
run: |
|
||||
set -x
|
||||
echo "COMMIT_COUNT=$(git rev-list --count HEAD)" >> $GITHUB_ENV
|
||||
echo "COMMIT_ID=$(git rev-parse --short HEAD)" >> $GITHUB_ENV
|
||||
|
||||
- name: Upload Artifact
|
||||
env:
|
||||
COMMIT_COUNT: ${{ env.COMMIT_COUNT }}
|
||||
uses: actions/upload-artifact@v4
|
||||
with:
|
||||
name: LNReader-r${{ env.COMMIT_COUNT }}
|
||||
path: android/app/build/outputs/apk/release/LNReader.apk
|
||||
|
||||
- name: Build Custom APK
|
||||
- name: Build Android Release
|
||||
env:
|
||||
COMMIT_COUNT: ${{ env.COMMIT_COUNT }}
|
||||
COMMIT_ID: ${{ env.COMMIT_ID }}
|
||||
run: |
|
||||
sed -i 's/LNReader/LNReader-r${{ env.COMMIT_COUNT }}(${{ env.COMMIT_ID }})/g' android/app/src/main/res/values/strings.xml
|
||||
cd android && ./gradlew assembleRelease --no-daemon -PcustomAppId=com.rajarsheechatterjee.LNReader.commit_${{ env.COMMIT_ID }}
|
||||
mv app/build/outputs/apk/release/app-release.apk app/build/outputs/apk/release/LNReader-r${{ env.COMMIT_COUNT }}\(${{ env.COMMIT_ID }}\)-customID.apk
|
||||
|
||||
- name: Upload Custom APK
|
||||
cd android && ./gradlew assembleRelease -PcustomAppId=com.rajarsheechatterjee.LNReader.commit_${{ env.COMMIT_ID }} --build-cache
|
||||
mv app/build/outputs/apk/release/app-release.apk app/build/outputs/apk/release/LNReader-r${{ env.COMMIT_COUNT }}-${{ env.COMMIT_ID }}.apk
|
||||
|
||||
- name: Upload Release Artifact
|
||||
env:
|
||||
COMMIT_COUNT: ${{ env.COMMIT_COUNT }}
|
||||
COMMIT_ID: ${{ env.COMMIT_ID }}
|
||||
uses: actions/upload-artifact@v4
|
||||
with:
|
||||
name: LNReader-r${{ env.COMMIT_COUNT }}(${{ env.COMMIT_ID }})-customID
|
||||
path: android/app/build/outputs/apk/release/LNReader-r${{ env.COMMIT_COUNT }}(${{ env.COMMIT_ID }})-customID.apk
|
||||
name: LNReader-r${{ env.COMMIT_COUNT }}-${{ env.COMMIT_ID }}
|
||||
path: android/app/build/outputs/apk/release/LNReader-r${{ env.COMMIT_COUNT }}-${{ env.COMMIT_ID }}.apk
|
||||
retention-days: 30
|
||||
|
||||
@@ -7,13 +7,42 @@ on:
|
||||
branches:
|
||||
- master
|
||||
|
||||
concurrency:
|
||||
group: ${{ github.workflow }}-${{ github.ref }}
|
||||
cancel-in-progress: true
|
||||
|
||||
jobs:
|
||||
build:
|
||||
name: ESLint
|
||||
runs-on: ubuntu-latest
|
||||
timeout-minutes: 10
|
||||
steps:
|
||||
- uses: actions/checkout@v2
|
||||
- name: Install modules
|
||||
run: npm install
|
||||
- name: Checkout Repository
|
||||
uses: actions/checkout@v4
|
||||
|
||||
- name: Setup Node.js
|
||||
uses: actions/setup-node@v4
|
||||
with:
|
||||
node-version: '20'
|
||||
|
||||
- name: Setup pnpm
|
||||
uses: pnpm/action-setup@v4
|
||||
|
||||
- name: Get pnpm Store Directory
|
||||
shell: bash
|
||||
run: |
|
||||
echo "STORE_PATH=$(pnpm store path --silent)" >> $GITHUB_ENV
|
||||
|
||||
- name: Setup pnpm Cache
|
||||
uses: actions/cache@v4
|
||||
with:
|
||||
path: ${{ env.STORE_PATH }}
|
||||
key: ${{ runner.os }}-pnpm-store-${{ hashFiles('**/pnpm-lock.yaml') }}
|
||||
restore-keys: |
|
||||
${{ runner.os }}-pnpm-store-
|
||||
|
||||
- name: Install Dependencies
|
||||
run: pnpm install --frozen-lockfile
|
||||
|
||||
- name: Run ESLint
|
||||
run: npm run lint
|
||||
run: pnpm run lint
|
||||
|
||||
@@ -0,0 +1,45 @@
|
||||
# Temporarily Disabled
|
||||
name: Type Check
|
||||
on:
|
||||
workflow_dispatch:
|
||||
# push:
|
||||
# branches:
|
||||
# - master
|
||||
# pull_request:
|
||||
# branches:
|
||||
# - master
|
||||
|
||||
jobs:
|
||||
build:
|
||||
name: Type Checking
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- name: Checkout Repository
|
||||
uses: actions/checkout@v4
|
||||
|
||||
- name: Setup Node.js
|
||||
uses: actions/setup-node@v4
|
||||
with:
|
||||
node-version: '20'
|
||||
|
||||
- name: Setup pnpm
|
||||
uses: pnpm/action-setup@v4
|
||||
|
||||
- name: Get pnpm Store Directory
|
||||
shell: bash
|
||||
run: |
|
||||
echo "STORE_PATH=$(pnpm store path --silent)" >> $GITHUB_ENV
|
||||
|
||||
- name: Setup pnpm Cache
|
||||
uses: actions/cache@v4
|
||||
with:
|
||||
path: ${{ env.STORE_PATH }}
|
||||
key: ${{ runner.os }}-pnpm-store-${{ hashFiles('**/pnpm-lock.yaml') }}
|
||||
restore-keys: |
|
||||
${{ runner.os }}-pnpm-store-
|
||||
|
||||
- name: Install Dependencies
|
||||
run: pnpm install --frozen-lockfile
|
||||
|
||||
- name: Check Types
|
||||
run: pnpm run type-check
|
||||
@@ -38,7 +38,7 @@ DerivedData
|
||||
*.hmap
|
||||
*.ipa
|
||||
*.xcuserstate
|
||||
project.xcworkspace
|
||||
**/.xcode.env.local
|
||||
|
||||
# Android/IntelliJ
|
||||
#
|
||||
@@ -48,6 +48,10 @@ build/
|
||||
local.properties
|
||||
*.iml
|
||||
*.hprof
|
||||
.cxx/
|
||||
*.keystore
|
||||
!debug.keystore
|
||||
.kotlin/
|
||||
|
||||
# node.js
|
||||
#
|
||||
@@ -66,6 +70,7 @@ buck-out/
|
||||
|
||||
# CocoaPods
|
||||
/ios/Pods/
|
||||
Gemfile.lock
|
||||
|
||||
# Expo
|
||||
.expo/
|
||||
@@ -76,4 +81,10 @@ web-build/
|
||||
.vscode/
|
||||
src/sources/en/generators/
|
||||
*.lockb
|
||||
.tool-versions
|
||||
*.lock
|
||||
!pnpm-lock.yaml
|
||||
.tool-versions
|
||||
flake.lock
|
||||
|
||||
# pnpm
|
||||
.pnpm-store
|
||||
|
||||
@@ -1,19 +1,20 @@
|
||||
import 'react-native-gesture-handler';
|
||||
import 'react-native-url-polyfill/auto';
|
||||
import { enableFreeze } from 'react-native-screens';
|
||||
|
||||
enableFreeze(true);
|
||||
|
||||
import React from 'react';
|
||||
import { StatusBar } from 'react-native';
|
||||
import React, { useEffect } from 'react';
|
||||
import { StatusBar, StyleSheet } from 'react-native';
|
||||
import { GestureHandlerRootView } from 'react-native-gesture-handler';
|
||||
import LottieSplashScreen from 'react-native-lottie-splash-screen';
|
||||
import { SafeAreaProvider } from 'react-native-safe-area-context';
|
||||
import { Provider as PaperProvider } from 'react-native-paper';
|
||||
import * as Notifications from 'expo-notifications';
|
||||
|
||||
import { createTables } from '@database/db';
|
||||
import AppErrorBoundary from '@components/AppErrorBoundary/AppErrorBoundary';
|
||||
import AppErrorBoundary, {
|
||||
ErrorFallback,
|
||||
} from '@components/AppErrorBoundary/AppErrorBoundary';
|
||||
import { useDatabaseInitialization } from '@hooks';
|
||||
|
||||
import Main from './src/navigators/Main';
|
||||
import { BottomSheetModalProvider } from '@gorhom/bottom-sheet';
|
||||
@@ -21,19 +22,83 @@ import { BottomSheetModalProvider } from '@gorhom/bottom-sheet';
|
||||
Notifications.setNotificationHandler({
|
||||
handleNotification: async () => {
|
||||
return {
|
||||
shouldShowAlert: true,
|
||||
shouldPlaySound: true,
|
||||
shouldSetBadge: true,
|
||||
shouldPlaySound: false,
|
||||
shouldSetBadge: false,
|
||||
shouldShowBanner: true,
|
||||
shouldShowList: true,
|
||||
};
|
||||
},
|
||||
});
|
||||
|
||||
createTables();
|
||||
LottieSplashScreen.hide();
|
||||
Notifications.setNotificationCategoryAsync('TTS_CONTROLS', [
|
||||
{
|
||||
identifier: 'TTS_PLAY_PAUSE',
|
||||
buttonTitle: '▶️ Play',
|
||||
options: {
|
||||
opensAppToForeground: false,
|
||||
},
|
||||
},
|
||||
{
|
||||
identifier: 'TTS_STOP',
|
||||
buttonTitle: '⏹️ Stop',
|
||||
options: {
|
||||
opensAppToForeground: false,
|
||||
},
|
||||
},
|
||||
{
|
||||
identifier: 'TTS_NEXT',
|
||||
buttonTitle: '⏭️ Next',
|
||||
options: {
|
||||
opensAppToForeground: false,
|
||||
},
|
||||
},
|
||||
]);
|
||||
|
||||
Notifications.setNotificationChannelAsync('tts-controls', {
|
||||
name: 'TTS Controls',
|
||||
description: 'Text-to-Speech playback controls',
|
||||
importance: Notifications.AndroidImportance.HIGH,
|
||||
vibrationPattern: [],
|
||||
enableLights: false,
|
||||
enableVibrate: false,
|
||||
});
|
||||
|
||||
const App = () => {
|
||||
const { isDbReady, dbError, retryInitialization } =
|
||||
useDatabaseInitialization();
|
||||
|
||||
useEffect(() => {
|
||||
if (isDbReady || dbError) {
|
||||
LottieSplashScreen.hide();
|
||||
}
|
||||
}, [isDbReady, dbError]);
|
||||
|
||||
useEffect(() => {
|
||||
const subscription = Notifications.addNotificationResponseReceivedListener(
|
||||
response => {
|
||||
const actionIdentifier = response.actionIdentifier;
|
||||
if (actionIdentifier.startsWith('TTS_')) {
|
||||
const { setTTSAction } = require('@utils/ttsNotification');
|
||||
setTTSAction(actionIdentifier);
|
||||
}
|
||||
},
|
||||
);
|
||||
|
||||
return () => {
|
||||
subscription.remove();
|
||||
};
|
||||
}, []);
|
||||
|
||||
if (dbError) {
|
||||
return <ErrorFallback error={dbError} resetError={retryInitialization} />;
|
||||
}
|
||||
|
||||
if (!isDbReady) {
|
||||
return null;
|
||||
}
|
||||
|
||||
return (
|
||||
<GestureHandlerRootView style={{ flex: 1 }}>
|
||||
<GestureHandlerRootView style={styles.flex}>
|
||||
<AppErrorBoundary>
|
||||
<SafeAreaProvider>
|
||||
<PaperProvider>
|
||||
@@ -49,3 +114,9 @@ const App = () => {
|
||||
};
|
||||
|
||||
export default App;
|
||||
|
||||
const styles = StyleSheet.create({
|
||||
flex: {
|
||||
flex: 1,
|
||||
},
|
||||
});
|
||||
|
||||
@@ -0,0 +1,108 @@
|
||||
# React Native Development Nix Environment Setup
|
||||
|
||||
This document outlines how to set up your React Native development environment using Nix flakes for reproducibility and isolation.
|
||||
|
||||
## Disclaimer
|
||||
|
||||
This was tested on an Arch Linux system with AMD CPU and GPU. There are likely problems with NVIDIA GPUs.
|
||||
|
||||
Compatibility with Microsoft WSL2 is not guaranteed, but should work withoud major issues, except for the emulator.
|
||||
|
||||
## Prerequisites
|
||||
|
||||
- **Nix installed**: [https://nixos.org/download.html](https://nixos.org/download.html)
|
||||
- **Git**: Required for cloning the repository.
|
||||
- **KVM (for emulator, if enabled)**: Ensure KVM is enabled and your user is in the `kvm` group for optimal emulator performance:
|
||||
```bash
|
||||
sudo usermod -aG kvm $USER && newgrp kvm # (or log out/in)
|
||||
kvm-ok # Verify KVM acceleration
|
||||
```
|
||||
- **GPU Drivers (for emulator, if enabled)**: For hardware-accelerated emulator (AMD/Intel), ensure host Vulkan drivers are working:
|
||||
```bash
|
||||
vulkaninfo | grep -i "device name" # Should show your GPU
|
||||
```
|
||||
If not, install `vulkan-radeon` (AMD) or `vulkan-intel` (Intel) on your host system and reboot.
|
||||
|
||||
## Getting Started
|
||||
|
||||
1. **Clone the Project:**
|
||||
|
||||
```bash
|
||||
git clone <your-fork-url>
|
||||
cd <your-fork-name>
|
||||
```
|
||||
|
||||
2. **Enter the Development Shell:**
|
||||
|
||||
If you have `pnpm` installed, use the defined scripts:
|
||||
|
||||
- **For Physical Device Only (default):**
|
||||
```bash
|
||||
pnpm run nix:shell
|
||||
```
|
||||
- **For Emulator Development (explicitly enable):**
|
||||
```bash
|
||||
pnpm run nix:emulator
|
||||
```
|
||||
|
||||
If you do not have `pnpm` installed, execute the commands manually:
|
||||
|
||||
- **For Physical Device Only (default):**
|
||||
```bash
|
||||
nix develop --extra-experimental-features nix-command --extra-experimental-features flakes
|
||||
```
|
||||
- **For Emulator Development (explicitly enable):**
|
||||
```
|
||||
nix develop .#emulator --extra-experimental-features nix-command --extra-experimental-features flakes
|
||||
```
|
||||
|
||||
The first time, this will download and set up all required tools, which can take considerable time. Subsequent entries will be much faster. You will also see a message about "Setting up writable Android SDK..." which only happens once.
|
||||
|
||||
## Development Workflow
|
||||
|
||||
Once inside the `nix develop` shell, all tools are available.
|
||||
|
||||
### 1. Start Metro Bundler
|
||||
|
||||
Open a new terminal or run in the background:
|
||||
|
||||
```bash
|
||||
pnpm run dev:start
|
||||
```
|
||||
|
||||
### 2. Physical Device Development
|
||||
|
||||
1. **Connect Device**: Enable USB debugging on your Android device and connect it via USB.
|
||||
2. **Verify Device**:
|
||||
```bash
|
||||
adb devices
|
||||
```
|
||||
3. **Mirror Screen (Optional)**:
|
||||
```bash
|
||||
scrcpy # Mirrors screen, provides control
|
||||
scrcpy -s <device_serial> # If multiple devices connected
|
||||
```
|
||||
4. **Run App**:
|
||||
```bash
|
||||
npx run android
|
||||
```
|
||||
|
||||
### 3. Android Emulator Development (if enabled)
|
||||
|
||||
1. **Launch Emulator**:
|
||||
|
||||
```bash
|
||||
emulator -avd ReactNative_API35 -gpu host -no-metrics -no-audio
|
||||
```
|
||||
|
||||
- Use `-gpu swiftshader_indirect` if hardware acceleration (`-gpu host`) fails.
|
||||
|
||||
2. **Verify Emulator**:
|
||||
```bash
|
||||
adb devices
|
||||
```
|
||||
3. **Run App**:
|
||||
Launches the the emulater automatically, if it is not already running.
|
||||
```bash
|
||||
npx run android
|
||||
```
|
||||
@@ -2,14 +2,19 @@
|
||||
|
||||
Contributions are welcome and are greatly appreciated!
|
||||
|
||||
## Setup your environment with nix
|
||||
|
||||
If you are on a Linux system, you can install the nix package manager and use the nix flakes to set up your development environment.
|
||||
See [CONTRIBUTING-NIX.md](CONTRIBUTING-NIX.md)
|
||||
|
||||
## Setting up your environment
|
||||
|
||||
After forking to your own github org or account, do the following steps to get started:
|
||||
|
||||
```bash
|
||||
# prerequisites
|
||||
node --version <= 16.13.1 (for version management, get nvm [recommended])
|
||||
java sdk --version <= 11 (for version management, get jenv [optional])
|
||||
node --version >= 20 (for version management, get nvm [recommended])
|
||||
java sdk --version >= 17 (for version management, get jenv [optional])
|
||||
android sdk (https://developer.android.com/studio)
|
||||
|
||||
# clone your fork to your local machine
|
||||
@@ -18,11 +23,14 @@ git clone https://github.com/<your-account-name>/lnreader.git
|
||||
# step into local repo
|
||||
cd lnreader
|
||||
|
||||
# install pnpm (if not already installed)
|
||||
npm install -g pnpm
|
||||
|
||||
# install dependencies
|
||||
npm install
|
||||
pnpm install
|
||||
|
||||
# build the apk (the built apk will be found in ~/lnreader/android/app/build/outputs/apk/release/)
|
||||
npm run buildRelease
|
||||
pnpm run build:release:android
|
||||
```
|
||||
|
||||
### Developing on Android
|
||||
@@ -38,15 +46,12 @@ IDE
|
||||
adb devices
|
||||
|
||||
# run metro for development
|
||||
npm start
|
||||
pnpm run dev:start
|
||||
|
||||
# then to view on your android device (new terminal)
|
||||
npm run android
|
||||
pnpm run dev:android
|
||||
```
|
||||
|
||||
To view any changes to the app with new code, save your code and press "r" on the metro terminal to
|
||||
reload it. The app on the android device/emulator will reload shortly.
|
||||
|
||||
### Style & Linting
|
||||
|
||||
This codebase's linting rules are enforced using [ESLint](http://eslint.org/).
|
||||
@@ -55,48 +60,5 @@ It is recommended that you install an eslint plugin for your editor of choice wh
|
||||
codebase, however you can always check to see if the source code is compliant by running:
|
||||
|
||||
```bash
|
||||
npm run lint
|
||||
```
|
||||
# Database
|
||||
```mermaid
|
||||
erDiagram
|
||||
Category ||--|{ NovelCategory : contains
|
||||
Category{
|
||||
INTEGER id PK
|
||||
TEXT name
|
||||
INTEGER sort
|
||||
}
|
||||
NovelCategory{
|
||||
INTEGER id PK
|
||||
INTEGER novelId FK
|
||||
INTEGER categoryId FK "default: whose sort = 1"
|
||||
}
|
||||
Novel ||--|{ NovelCategory : contains
|
||||
Novel{
|
||||
INTEGER id PK
|
||||
TEXT url
|
||||
TEXT pluginId
|
||||
TEXT name
|
||||
TEXT cover
|
||||
TEXT summary
|
||||
TEXT author
|
||||
TEXT artist
|
||||
TEXT status
|
||||
TEXT genres
|
||||
INTEGER inLibrary "default: 0"
|
||||
}
|
||||
Novel ||--o| Chapter : has
|
||||
Chapter{
|
||||
INTEGER id PK
|
||||
INTEGER novelId FK
|
||||
text url
|
||||
TEXT name
|
||||
TEXT releaseTime
|
||||
INTEGER bookmark "default: 0"
|
||||
INTEGER unread "default: 1"
|
||||
TEXT readTime
|
||||
INTEGER isDownloaded "default: 0"
|
||||
TEXT updatedTime
|
||||
TEXT chapterNumber
|
||||
}
|
||||
pnpm run lint
|
||||
```
|
||||
|
||||
@@ -0,0 +1,16 @@
|
||||
source 'https://rubygems.org'
|
||||
|
||||
# You may use http://rbenv.org/ or https://rvm.io/ to install and use this version
|
||||
ruby ">= 2.6.10"
|
||||
|
||||
# Exclude problematic versions of cocoapods and activesupport that causes build failures.
|
||||
gem 'cocoapods', '>= 1.13', '!= 1.15.0', '!= 1.15.1'
|
||||
gem 'activesupport', '>= 6.1.7.5', '!= 7.1.0'
|
||||
gem 'xcodeproj', '< 1.26.0'
|
||||
gem 'concurrent-ruby', '< 1.3.4'
|
||||
|
||||
# Ruby 3.4.0 has removed some libraries from the standard library.
|
||||
gem 'bigdecimal'
|
||||
gem 'logger'
|
||||
gem 'benchmark'
|
||||
gem 'mutex_m'
|
||||
@@ -1,47 +1,68 @@
|
||||
<p align="center">
|
||||
<img src="./.github/readme-images/icon_alt.png" align="center" width="128" />
|
||||
<p>
|
||||
<h1 align="center">
|
||||
LNReader
|
||||
</h1>
|
||||
<p align="center">
|
||||
LNReader is a Tachiyomi-like, free and open source light novel reader for Android.
|
||||
<a href="https://lnreader.app">
|
||||
<img src="./.github/readme-images/icon_new.png" align="center" width="128" />
|
||||
</a>
|
||||
</p>
|
||||
|
||||
<h1 align="center">LNReader</h1>
|
||||
|
||||
<p align="center">
|
||||
LNReader is a free and open source light novel reader for Android, inspired by Tachiyomi.
|
||||
</p>
|
||||
|
||||
<div align="center">
|
||||
<a href="https://github.com/LNReader/lnreader/actions/workflows/build.yml">
|
||||
<img alt="GitHub Workflow Status" src="https://img.shields.io/github/actions/workflow/status/LNReader/lnreader/build.yml?branch=main">
|
||||
</a>
|
||||
<a href="https://discord.gg/QdcWN4MD63">
|
||||
<img alt="Discord Chat" src="https://img.shields.io/discord/835746409357246465.svg?logo=discord&logoColor=white&logoWidth=20&labelColor=7289DA&label=Discord&color=17cf48">
|
||||
<img alt="Discord Chat" src="https://img.shields.io/discord/835746409357246465.svg?logo=discord&logoColor=white&logoWidth=20&labelColor=5865F2&color=4752C4&label=discord&style=flat">
|
||||
</a>
|
||||
<a href="https://github.com/LNReader/lnreader/releases/latest">
|
||||
<img alt="GitHub release (latest by date)" src="https://img.shields.io/github/v/release/LNReader/lnreader">
|
||||
</a>
|
||||
<a href="https://github.com/LNReader/lnreader-preview/releases/latest">
|
||||
<img alt="GitHub release (latest SemVer)" src="https://img.shields.io/github/v/release/LNReader/lnreader-preview?color=red&label=preview">
|
||||
</a>
|
||||
<a title="Crowdin" target="_blank" href="https://crowdin.com/project/lnreader"><img src="https://badges.crowdin.net/lnreader/localized.svg"></a>
|
||||
<a href="https://github.com/LNReader/lnreader/blob/main/LICENSE">
|
||||
<img alt="GitHub" src="https://img.shields.io/github/license/LNReader/lnreader">
|
||||
<a href="https://github.com/LNReader/lnreader/releases">
|
||||
<img alt="GitHub Downloads" src="https://img.shields.io/github/downloads/LNReader/lnreader/total?label=downloads&labelColor=27303D&color=0D1117&logo=github&logoColor=FFFFFF&style=flat">
|
||||
</a>
|
||||
</div>
|
||||
|
||||
<br>
|
||||
<br>
|
||||
<div align="center">
|
||||
<img alt="GitHub Actions Workflow Status" src="https://img.shields.io/github/actions/workflow/status/LNReader/lnreader/build.yml?labelColor=27303D&style=flat">
|
||||
<a href="https://github.com/LNReader/lnreader/blob/main/LICENSE">
|
||||
<img alt="GitHub" src="https://img.shields.io/github/license/LNReader/lnreader?labelColor=27303D&color=1a73e8&style=flat">
|
||||
</a>
|
||||
<a title="Crowdin" target="_blank" href="https://crowdin.com/project/lnreader">
|
||||
<img src="https://badges.crowdin.net/lnreader/localized.svg">
|
||||
</a>
|
||||
</div>
|
||||
|
||||
<h2 align="center">Download</h2>
|
||||
|
||||
<p align="center">
|
||||
<a href="https://github.com/LNReader/lnreader/releases/latest">
|
||||
<img alt="GitHub release (latest by date)" src="https://img.shields.io/github/v/release/LNReader/lnreader?label=Stable&labelColor=0d7377&color=084c4e&style=flat">
|
||||
</a>
|
||||
<a href="https://github.com/LNReader/lnreader/releases/latest">
|
||||
<img alt="GitHub release (latest SemVer)" src="https://img.shields.io/github/v/release/LNReader/lnreader?include_prereleases&sort=semver&label=Beta&labelColor=3d3d5c&color=2a2a47&style=flat">
|
||||
</a>
|
||||
</p>
|
||||
|
||||
<p align="center">
|
||||
Get the app from our <a href="https://github.com/LNReader/lnreader/releases">releases page</a>.
|
||||
</p>
|
||||
|
||||
<p align="center">
|
||||
<em>Android 7.0 or higher.</em>
|
||||
</p>
|
||||
|
||||
<h2 align="center">Screenshots</h2>
|
||||
|
||||
<p align="center">
|
||||
<img src="./.github/readme-images/screenshots.png" align="center" />
|
||||
</p>
|
||||
|
||||
## Download
|
||||
|
||||
Get the app from our [releases page](https://github.com/rajarsheechatterjee/LNReader/releases).
|
||||
|
||||
## Sources
|
||||
## Plugins
|
||||
|
||||
LNReader does not have any affiliation with the content providers available.
|
||||
|
||||
Source requests should be created at [LNReader/lnreader-sources](https://github.com/LNReader/lnreader-sources).
|
||||
Plugin requests should be created at [lnreader-plugins](https://github.com/LNReader/lnreader-plugins).
|
||||
|
||||
## Translation
|
||||
|
||||
Help translate LNReader into your language on [Crowdin](https://crowdin.com/project/lnreader).
|
||||
|
||||
## Building & Contributing
|
||||
|
||||
@@ -49,4 +70,4 @@ See [CONTRIBUTING.md](./CONTRIBUTING.md)
|
||||
|
||||
## License
|
||||
|
||||
[MIT](https://github.com/rajarsheechatterjee/lnreader/blob/main/LICENSE)
|
||||
[MIT](https://github.com/LNReader/lnreader/blob/main/LICENSE)
|
||||
|
||||
@@ -1,55 +0,0 @@
|
||||
# To learn about Buck see [Docs](https://buckbuild.com/).
|
||||
# To run your application with Buck:
|
||||
# - install Buck
|
||||
# - `npm start` - to start the packager
|
||||
# - `cd android`
|
||||
# - `keytool -genkey -v -keystore keystores/debug.keystore -storepass android -alias androiddebugkey -keypass android -dname "CN=Android Debug,O=Android,C=US"`
|
||||
# - `./gradlew :app:copyDownloadableDepsToLibs` - make all Gradle compile dependencies available to Buck
|
||||
# - `buck install -r android/app` - compile, install and run application
|
||||
#
|
||||
|
||||
load(":build_defs.bzl", "create_aar_targets", "create_jar_targets")
|
||||
|
||||
lib_deps = []
|
||||
|
||||
create_aar_targets(glob(["libs/*.aar"]))
|
||||
|
||||
create_jar_targets(glob(["libs/*.jar"]))
|
||||
|
||||
android_library(
|
||||
name = "all-libs",
|
||||
exported_deps = lib_deps,
|
||||
)
|
||||
|
||||
android_library(
|
||||
name = "app-code",
|
||||
srcs = glob([
|
||||
"src/main/java/**/*.java",
|
||||
]),
|
||||
deps = [
|
||||
":all-libs",
|
||||
":build_config",
|
||||
":res",
|
||||
],
|
||||
)
|
||||
|
||||
android_build_config(
|
||||
name = "build_config",
|
||||
package = "com.rajarsheechatterjee.LNReader",
|
||||
)
|
||||
|
||||
android_resource(
|
||||
name = "res",
|
||||
package = "com.rajarsheechatterjee.LNReader",
|
||||
res = "src/main/res",
|
||||
)
|
||||
|
||||
android_binary(
|
||||
name = "app",
|
||||
keystore = "//android/keystores:debug",
|
||||
manifest = "src/main/AndroidManifest.xml",
|
||||
package_type = "debug",
|
||||
deps = [
|
||||
":app-code",
|
||||
],
|
||||
)
|
||||
@@ -1,5 +1,6 @@
|
||||
apply plugin: "com.android.application"
|
||||
apply plugin: "com.facebook.react"
|
||||
apply plugin: 'org.jetbrains.kotlin.android'
|
||||
|
||||
/**
|
||||
* This is the configuration block to customize your React Native Android app.
|
||||
@@ -7,14 +8,14 @@ apply plugin: "com.facebook.react"
|
||||
*/
|
||||
react {
|
||||
/* Folders */
|
||||
// The root of your project, i.e. where "package.json" lives. Default is '..'
|
||||
// root = file("../")
|
||||
// The folder where the react-native NPM package is. Default is ../node_modules/react-native
|
||||
// reactNativeDir = file("../node_modules/react-native")
|
||||
// The folder where the react-native Codegen package is. Default is ../node_modules/@react-native/codegen
|
||||
// codegenDir = file("../node_modules/@react-native/codegen")
|
||||
// The cli.js file which is the React Native CLI entrypoint. Default is ../node_modules/react-native/cli.js
|
||||
// cliFile = file("../node_modules/react-native/cli.js")
|
||||
// The root of your project, i.e. where "package.json" lives. Default is '../..'
|
||||
// root = file("../../")
|
||||
// The folder where the react-native NPM package is. Default is ../../node_modules/react-native
|
||||
// reactNativeDir = file("../../node_modules/react-native")
|
||||
// The folder where the react-native Codegen package is. Default is ../../node_modules/@react-native/codegen
|
||||
// codegenDir = file("../../node_modules/@react-native/codegen")
|
||||
// The cli.js file which is the React Native CLI entrypoint. Default is ../../node_modules/react-native/cli.js
|
||||
// cliFile = file("../../node_modules/react-native/cli.js")
|
||||
|
||||
/* Variants */
|
||||
// The list of variants to that are debuggable. For those we're going to
|
||||
@@ -48,6 +49,9 @@ react {
|
||||
//
|
||||
// The list of flags to pass to the Hermes compiler. By default is "-O", "-output-source-map"
|
||||
// hermesFlags = ["-O", "-output-source-map"]
|
||||
|
||||
/* Autolinking */
|
||||
autolinkLibrariesWithApp()
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -59,14 +63,14 @@ def enableProguardInReleaseBuilds = false
|
||||
* The preferred build flavor of JavaScriptCore (JSC).
|
||||
*
|
||||
* For example, to use the international variant, you can use:
|
||||
* `def jscFlavor = 'org.webkit:android-jsc-intl:+'`
|
||||
* `def jscFlavor = io.github.react-native-community:jsc-android-intl:2026004.+`
|
||||
*
|
||||
* The international variant includes ICU i18n library and necessary data
|
||||
* allowing to use e.g. `Date.toLocaleString` and `String.localeCompare` that
|
||||
* give correct results when using with locales other than en-US. Note that
|
||||
* this variant is about 6MiB larger per architecture than default.
|
||||
*/
|
||||
def jscFlavor = 'org.webkit:android-jsc:+'
|
||||
def jscFlavor = 'io.github.react-native-community:jsc-android:2026004.+'
|
||||
|
||||
ext {
|
||||
versionMajor = 2
|
||||
@@ -76,8 +80,8 @@ ext {
|
||||
|
||||
android {
|
||||
ndkVersion rootProject.ext.ndkVersion
|
||||
|
||||
compileSdkVersion rootProject.ext.compileSdkVersion
|
||||
buildToolsVersion rootProject.ext.buildToolsVersion
|
||||
compileSdk rootProject.ext.compileSdkVersion
|
||||
namespace "com.rajarsheechatterjee.LNReader"
|
||||
defaultConfig {
|
||||
applicationId project.hasProperty('customAppId') ? project.getProperty('customAppId') : 'com.rajarsheechatterjee.LNReader'
|
||||
@@ -99,7 +103,7 @@ android {
|
||||
debug {
|
||||
signingConfig signingConfigs.debug
|
||||
applicationIdSuffix 'debug'
|
||||
versionNameSuffix "-debug"
|
||||
versionNameSuffix '-debug'
|
||||
}
|
||||
release {
|
||||
// Caution! In production, you need to generate your own keystore file.
|
||||
@@ -109,43 +113,24 @@ android {
|
||||
proguardFiles getDefaultProguardFile("proguard-android.txt"), "proguard-rules.pro"
|
||||
}
|
||||
}
|
||||
kotlinOptions {
|
||||
jvmTarget = '17'
|
||||
}
|
||||
externalNativeBuild {
|
||||
cmake {
|
||||
path "src/main/jni/CMakeLists.txt"
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
dependencies {
|
||||
// The version of react-native is set by the React Native Gradle Plugin
|
||||
implementation("com.facebook.react:react-android")
|
||||
implementation 'com.facebook.soloader:soloader:0.10.5+'
|
||||
implementation 'androidx.core:core-ktx:1.9.0'
|
||||
|
||||
def isGifEnabled = (findProperty('expo.gif.enabled') ?: "") == "true";
|
||||
def isWebpEnabled = (findProperty('expo.webp.enabled') ?: "") == "true";
|
||||
def isWebpAnimatedEnabled = (findProperty('expo.webp.animated') ?: "") == "true";
|
||||
|
||||
if (isGifEnabled || isWebpEnabled) {
|
||||
implementation 'com.facebook.fresco:fresco:2.0.0'
|
||||
implementation 'com.facebook.fresco:imagepipeline-okhttp3:2.0.0'
|
||||
}
|
||||
|
||||
if(isGifEnabled){
|
||||
// For animated gif support
|
||||
implementation 'com.facebook.fresco:animated-gif:2.0.0'
|
||||
}
|
||||
|
||||
if(isWebpEnabled){
|
||||
// For webp support
|
||||
implementation 'com.facebook.fresco:webpsupport:2.0.0'
|
||||
if(isWebpAnimatedEnabled){
|
||||
// Animated webp support
|
||||
implementation 'com.facebook.fresco:animated-webp:2.0.0'
|
||||
}
|
||||
}
|
||||
implementation 'androidx.core:core-ktx:1.15.0'
|
||||
|
||||
if (hermesEnabled.toBoolean()) {
|
||||
implementation("com.facebook.react:hermes-android")
|
||||
} else {
|
||||
implementation jscFlavor
|
||||
}
|
||||
}
|
||||
|
||||
apply from: file("../../node_modules/@react-native-community/cli-platform-android/native_modules.gradle")
|
||||
apply plugin: 'org.jetbrains.kotlin.android'; applyNativeModulesAppBuildGradle(project)
|
||||
}
|
||||
@@ -1,12 +1,9 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<manifest xmlns:android="http://schemas.android.com/apk/res/android"
|
||||
xmlns:tools="http://schemas.android.com/tools">
|
||||
|
||||
<uses-permission android:name="android.permission.SYSTEM_ALERT_WINDOW"/>
|
||||
|
||||
<application
|
||||
android:usesCleartextTraffic="true"
|
||||
tools:targetApi="28"
|
||||
tools:ignore="GoogleAppIndexingWarning">
|
||||
<activity android:name="com.facebook.react.devsupport.DevSettingsActivity" android:exported="false"/>
|
||||
</application>
|
||||
</manifest>
|
||||
tools:ignore="GoogleAppIndexingWarning"/>
|
||||
</manifest>
|
||||
@@ -29,7 +29,8 @@
|
||||
android:roundIcon="@mipmap/ic_launcher"
|
||||
android:allowBackup="true"
|
||||
android:theme="@style/AppTheme"
|
||||
android:usesCleartextTraffic="true">
|
||||
android:usesCleartextTraffic="true"
|
||||
android:supportsRtl="true">
|
||||
|
||||
<meta-data
|
||||
android:name="expo.modules.notifications.default_notification_icon"
|
||||
@@ -63,7 +64,7 @@
|
||||
|
||||
<activity android:name="com.facebook.react.devsupport.DevSettingsActivity" />
|
||||
|
||||
<service android:name="com.asterinet.react.bgactions.RNBackgroundActionsTask" />
|
||||
<service android:name="com.asterinet.react.bgactions.RNBackgroundActionsTask" android:foregroundServiceType="shortService" />
|
||||
|
||||
</application>
|
||||
</manifest>
|
||||
</manifest>
|
||||
|
||||
@@ -28,23 +28,11 @@ body {
|
||||
font-family: var(--readerSettings-fontFamily);
|
||||
}
|
||||
|
||||
body.page-reader {
|
||||
overflow: hidden;
|
||||
padding-bottom: unset;
|
||||
}
|
||||
|
||||
#LNReader-chapter {
|
||||
padding-left: var(--readerSettings-padding);
|
||||
padding-right: var(--readerSettings-padding);
|
||||
}
|
||||
|
||||
body.page-reader > #LNReader-chapter{
|
||||
height: calc(90vh);
|
||||
column-width: calc(100vw - var(--readerSettings-padding) * 2);
|
||||
column-gap: calc(var(--readerSettings-padding) * 2);
|
||||
transition: 200ms;
|
||||
}
|
||||
|
||||
hr {
|
||||
margin-top: 20px;
|
||||
margin-bottom: 20px;
|
||||
@@ -54,6 +42,10 @@ a {
|
||||
color: var(--theme-primary);
|
||||
}
|
||||
|
||||
sup {
|
||||
line-height: 0.1em;
|
||||
}
|
||||
|
||||
img {
|
||||
display: block;
|
||||
width: auto;
|
||||
@@ -123,160 +115,12 @@ td {
|
||||
display: none;
|
||||
}
|
||||
|
||||
#ToolWrapper {
|
||||
position: fixed;
|
||||
top: 50%;
|
||||
transform: translateY(-50%);
|
||||
right: 5vw;
|
||||
}
|
||||
|
||||
#ToolWrapper.horizontal {
|
||||
top: unset;
|
||||
right: unset;
|
||||
left: 50%;
|
||||
transform: translateX(-50%);
|
||||
bottom: 120px;
|
||||
display: flex;
|
||||
flex-direction: row-reverse;
|
||||
align-items: center;
|
||||
}
|
||||
|
||||
@media only screen and (min-width: 500px) {
|
||||
#ToolWrapper.horizontal {
|
||||
bottom: 80px;
|
||||
}
|
||||
}
|
||||
|
||||
#ToolWrapper.hidden {
|
||||
opacity: 0;
|
||||
right: 0;
|
||||
}
|
||||
|
||||
#ToolWrapper.hidden.horizontal {
|
||||
opacity: 0;
|
||||
bottom: 0;
|
||||
right: unset;
|
||||
}
|
||||
|
||||
#ToolWrapper > #ScrollBar {
|
||||
margin-top: 8px;
|
||||
width: 2.4rem;
|
||||
height: 50vh;
|
||||
min-height: 200px;
|
||||
border-radius: 1.2rem;
|
||||
background-color: var(--theme-surface-0-9);
|
||||
touch-action: none;
|
||||
font-size: 14px;
|
||||
user-select: none;
|
||||
}
|
||||
|
||||
#ScrollBar > .scrollbar-item {
|
||||
display: flex;
|
||||
justify-content: center;
|
||||
align-items: center;
|
||||
color: var(--theme-onSurface);
|
||||
}
|
||||
|
||||
#ScrollBar > .scrollbar-item.scrollbar-text {
|
||||
height: 12.5%;
|
||||
}
|
||||
|
||||
#ScrollBar > .scrollbar-item#scrollbar-slider {
|
||||
height: 75%;
|
||||
align-items: unset;
|
||||
}
|
||||
|
||||
#ScrollBar #scrollbar-track {
|
||||
width: 0.1rem;
|
||||
height: 100%;
|
||||
background-color: var(--theme-outline);
|
||||
}
|
||||
|
||||
#ScrollBar #scrollbar-progress {
|
||||
width: 100%;
|
||||
height: 0;
|
||||
background-color: var(--theme-primary);
|
||||
}
|
||||
|
||||
#ScrollBar #scrollbar-thumb-wrapper {
|
||||
position: relative;
|
||||
top: 100%;
|
||||
height: 2rem;
|
||||
width: 2rem;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
transform: translate(-0.95rem, -1rem);
|
||||
}
|
||||
|
||||
#ScrollBar #scrollbar-thumb {
|
||||
top: 100%;
|
||||
height: 1rem;
|
||||
width: 1rem;
|
||||
border-radius: 100%;
|
||||
background-color: var(--theme-primary);
|
||||
}
|
||||
|
||||
#ToolWrapper.horizontal > #ScrollBar {
|
||||
display: flex;
|
||||
width: 95.5vw;
|
||||
height: 2.4rem;
|
||||
min-height: unset;
|
||||
align-items: center;
|
||||
margin-top: unset;
|
||||
}
|
||||
|
||||
#ToolWrapper.horizontal > #ScrollBar > .scrollbar-item {
|
||||
display: flex;
|
||||
justify-content: center;
|
||||
color: var(--theme-onSurface);
|
||||
}
|
||||
|
||||
#ToolWrapper.horizontal > #ScrollBar > .scrollbar-item.scrollbar-text {
|
||||
width: 12.5%;
|
||||
}
|
||||
|
||||
#ToolWrapper.horizontal > #ScrollBar > .scrollbar-item#scrollbar-slider {
|
||||
width: 75%;
|
||||
align-items: center;
|
||||
}
|
||||
|
||||
#ToolWrapper.horizontal > #ScrollBar #scrollbar-track {
|
||||
height: 0.1rem;
|
||||
width: 100%;
|
||||
background-color: var(--theme-outline);
|
||||
}
|
||||
|
||||
#ToolWrapper.horizontal > #ScrollBar #scrollbar-progress {
|
||||
height: 100%;
|
||||
width: 0;
|
||||
background-color: var(--theme-primary);
|
||||
}
|
||||
|
||||
#ToolWrapper.horizontal > #ScrollBar #scrollbar-thumb-wrapper {
|
||||
position: relative;
|
||||
left: 100%;
|
||||
height: 2rem;
|
||||
width: 2rem;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
transform: translate(-0.95rem, -1rem);
|
||||
}
|
||||
|
||||
#ToolWrapper.horizontal > #ScrollBar #scrollbar-thumb {
|
||||
top: 100%;
|
||||
height: 1rem;
|
||||
width: 1rem;
|
||||
border-radius: 100%;
|
||||
background-color: var(--theme-primary);
|
||||
}
|
||||
|
||||
#reader-footer-wrapper {
|
||||
position: fixed;
|
||||
bottom: 0;
|
||||
left: 0;
|
||||
width: 100%;
|
||||
z-index: 9999999;
|
||||
}
|
||||
|
||||
#reader-footer {
|
||||
@@ -302,9 +146,18 @@ td {
|
||||
text-align: right;
|
||||
}
|
||||
|
||||
.highlight, .highlight *{
|
||||
color: var(--theme-onSecondary) !important;
|
||||
background-color: var(--theme-secondary);
|
||||
.highlight {
|
||||
color: var(--readerSettings-textColor) !important;
|
||||
background-color: color-mix(
|
||||
in srgb,
|
||||
var(--readerSettings-textColor) 20%,
|
||||
var(--readerSettings-theme)
|
||||
);
|
||||
}
|
||||
|
||||
.highlight * {
|
||||
color: var(--readerSettings-textColor) !important;
|
||||
background-color: transparent;
|
||||
}
|
||||
|
||||
#Image-Modal {
|
||||
@@ -328,41 +181,3 @@ td {
|
||||
display: block;
|
||||
width: 100%;
|
||||
}
|
||||
|
||||
#TTS-Controller {
|
||||
position: fixed;
|
||||
top: 50%;
|
||||
left: 20px;
|
||||
opacity: 0.5;
|
||||
}
|
||||
|
||||
#TTS-Controller button {
|
||||
background: var(--theme-surface-0-9);
|
||||
outline: none;
|
||||
border-width: 0;
|
||||
display: flex;
|
||||
justify-items: center;
|
||||
align-items: center;
|
||||
padding: 4px;
|
||||
border-radius: 100%;
|
||||
transition: .5s;
|
||||
}
|
||||
|
||||
#TTS-Controller.active{
|
||||
opacity: 1;
|
||||
}
|
||||
|
||||
#TTS-Controller.active button {
|
||||
padding: 16px;
|
||||
}
|
||||
|
||||
#TTS-Controller svg {
|
||||
fill: var(--theme-onSurface);
|
||||
width: 20px;
|
||||
height: 20px;
|
||||
}
|
||||
|
||||
#TTS-Controller.active svg {
|
||||
width: 24px;
|
||||
height: 24px;
|
||||
}
|
||||
|
||||
@@ -0,0 +1,29 @@
|
||||
body.page-reader {
|
||||
overflow: hidden;
|
||||
padding-bottom: unset;
|
||||
}
|
||||
|
||||
body.page-reader > #LNReader-chapter {
|
||||
height: calc(90vh);
|
||||
column-width: calc(100vw - var(--readerSettings-padding) * 2);
|
||||
column-gap: calc(var(--readerSettings-padding) * 2);
|
||||
transition: 200ms;
|
||||
}
|
||||
.transition-chapter {
|
||||
height: 100vh;
|
||||
width: 100vw;
|
||||
max-width: 100vw;
|
||||
|
||||
position: fixed;
|
||||
left: 100vw;
|
||||
top: 0;
|
||||
|
||||
box-sizing: border-box;
|
||||
padding: var(--readerSettings-padding);
|
||||
padding-top: 30vh;
|
||||
|
||||
text-align: center;
|
||||
font-size: 2rem;
|
||||
z-index: 888888;
|
||||
background-color: var(--readerSettings-theme);
|
||||
}
|
||||
@@ -0,0 +1,151 @@
|
||||
#ToolWrapper {
|
||||
position: fixed;
|
||||
top: 50%;
|
||||
transform: translateY(-50%);
|
||||
right: 5vw;
|
||||
transition: all 250ms cubic-bezier(0.4, 0, 0.2, 1);
|
||||
z-index: 9999999;
|
||||
}
|
||||
|
||||
#ToolWrapper.horizontal {
|
||||
top: unset;
|
||||
right: unset;
|
||||
left: 50%;
|
||||
transform: translateX(-50%);
|
||||
bottom: 120px;
|
||||
display: flex;
|
||||
flex-direction: row-reverse;
|
||||
align-items: center;
|
||||
}
|
||||
|
||||
@media only screen and (min-width: 500px) {
|
||||
#ToolWrapper.horizontal {
|
||||
bottom: 80px;
|
||||
}
|
||||
}
|
||||
|
||||
#ToolWrapper.hidden {
|
||||
opacity: 0;
|
||||
right: 0;
|
||||
transition: all 250ms cubic-bezier(0.4, 0, 0.2, 1);
|
||||
}
|
||||
|
||||
#ToolWrapper.hidden.horizontal {
|
||||
opacity: 0;
|
||||
bottom: 80px;
|
||||
right: unset;
|
||||
}
|
||||
|
||||
#ToolWrapper > #ScrollBar {
|
||||
margin-top: 8px;
|
||||
width: 2.4rem;
|
||||
height: 50vh;
|
||||
min-height: 200px;
|
||||
border-radius: 1.2rem;
|
||||
background-color: var(--theme-surface-0-9);
|
||||
touch-action: none;
|
||||
font-size: 14px;
|
||||
user-select: none;
|
||||
}
|
||||
|
||||
#ScrollBar > .scrollbar-item {
|
||||
display: flex;
|
||||
justify-content: center;
|
||||
align-items: center;
|
||||
color: var(--theme-onSurface);
|
||||
}
|
||||
|
||||
#ScrollBar > .scrollbar-item.scrollbar-text {
|
||||
height: 12.5%;
|
||||
}
|
||||
|
||||
#ScrollBar > .scrollbar-item#scrollbar-slider {
|
||||
height: 75%;
|
||||
align-items: unset;
|
||||
}
|
||||
|
||||
#ScrollBar #scrollbar-track {
|
||||
width: 0.1rem;
|
||||
height: 100%;
|
||||
background-color: var(--theme-outline);
|
||||
}
|
||||
|
||||
#ScrollBar #scrollbar-progress {
|
||||
width: 100%;
|
||||
height: 0;
|
||||
background-color: var(--theme-primary);
|
||||
}
|
||||
|
||||
#ScrollBar #scrollbar-thumb-wrapper {
|
||||
position: relative;
|
||||
top: 100%;
|
||||
height: 2rem;
|
||||
width: 2rem;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
transform: translate(-0.95rem, -1rem);
|
||||
}
|
||||
|
||||
#ScrollBar #scrollbar-thumb {
|
||||
top: 100%;
|
||||
height: 1rem;
|
||||
width: 1rem;
|
||||
border-radius: 100%;
|
||||
background-color: var(--theme-primary);
|
||||
}
|
||||
|
||||
#ToolWrapper.horizontal > #ScrollBar {
|
||||
display: flex;
|
||||
width: 95.5vw;
|
||||
height: 2.4rem;
|
||||
min-height: unset;
|
||||
align-items: center;
|
||||
margin-top: unset;
|
||||
}
|
||||
|
||||
#ToolWrapper.horizontal > #ScrollBar > .scrollbar-item {
|
||||
display: flex;
|
||||
justify-content: center;
|
||||
color: var(--theme-onSurface);
|
||||
}
|
||||
|
||||
#ToolWrapper.horizontal > #ScrollBar > .scrollbar-item.scrollbar-text {
|
||||
width: 12.5%;
|
||||
}
|
||||
|
||||
#ToolWrapper.horizontal > #ScrollBar > .scrollbar-item#scrollbar-slider {
|
||||
width: 75%;
|
||||
align-items: center;
|
||||
}
|
||||
|
||||
#ToolWrapper.horizontal > #ScrollBar #scrollbar-track {
|
||||
height: 0.1rem;
|
||||
width: 100%;
|
||||
background-color: var(--theme-outline);
|
||||
}
|
||||
|
||||
#ToolWrapper.horizontal > #ScrollBar #scrollbar-progress {
|
||||
height: 100%;
|
||||
width: 0;
|
||||
background-color: var(--theme-primary);
|
||||
}
|
||||
|
||||
#ToolWrapper.horizontal > #ScrollBar #scrollbar-thumb-wrapper {
|
||||
position: relative;
|
||||
left: 100%;
|
||||
height: 2rem;
|
||||
width: 2rem;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
transform: translate(-0.95rem, -1rem);
|
||||
}
|
||||
|
||||
#ToolWrapper.horizontal > #ScrollBar #scrollbar-thumb {
|
||||
top: 100%;
|
||||
height: 1rem;
|
||||
width: 1rem;
|
||||
border-radius: 100%;
|
||||
background-color: var(--theme-primary);
|
||||
}
|
||||
@@ -0,0 +1,48 @@
|
||||
#TTS-Controller {
|
||||
position: fixed;
|
||||
top: 50%;
|
||||
left: 20px;
|
||||
opacity: 0.5;
|
||||
}
|
||||
|
||||
#TTS-Controller button {
|
||||
background: color-mix(
|
||||
in srgb,
|
||||
var(--readerSettings-theme) 85%,
|
||||
var(--readerSettings-textColor) 15%
|
||||
);
|
||||
outline: none;
|
||||
border-width: 1px;
|
||||
border-style: solid;
|
||||
border-color: color-mix(
|
||||
in srgb,
|
||||
var(--readerSettings-textColor) 30%,
|
||||
var(--readerSettings-theme) 70%
|
||||
);
|
||||
display: flex;
|
||||
justify-items: center;
|
||||
align-items: center;
|
||||
padding: 4px;
|
||||
border-radius: 100%;
|
||||
transition: 0.5s;
|
||||
box-shadow: 0 2px 8px rgba(0, 0, 0, 0.15);
|
||||
}
|
||||
|
||||
#TTS-Controller.active {
|
||||
opacity: 1;
|
||||
}
|
||||
|
||||
#TTS-Controller.active button {
|
||||
padding: 16px;
|
||||
}
|
||||
|
||||
#TTS-Controller svg {
|
||||
fill: var(--readerSettings-textColor);
|
||||
width: 20px;
|
||||
height: 20px;
|
||||
}
|
||||
|
||||
#TTS-Controller.active svg {
|
||||
width: 24px;
|
||||
height: 24px;
|
||||
}
|
||||
@@ -1,3 +1,4 @@
|
||||
/* eslint-disable no-console */
|
||||
window.reader = new (function () {
|
||||
const {
|
||||
readerSettings,
|
||||
@@ -5,6 +6,7 @@ window.reader = new (function () {
|
||||
novel,
|
||||
chapter,
|
||||
nextChapter,
|
||||
prevChapter,
|
||||
batteryLevel,
|
||||
autoSaveInterval,
|
||||
DEBUG,
|
||||
@@ -24,6 +26,7 @@ window.reader = new (function () {
|
||||
this.novel = novel;
|
||||
this.chapter = chapter;
|
||||
this.nextChapter = nextChapter;
|
||||
this.prevChapter = prevChapter;
|
||||
this.strings = strings;
|
||||
this.autoSaveInterval = autoSaveInterval;
|
||||
this.rawHTML = this.chapterElement.innerHTML;
|
||||
@@ -39,6 +42,9 @@ window.reader = new (function () {
|
||||
this.layoutHeight = window.screen.height;
|
||||
this.layoutWidth = window.screen.width;
|
||||
|
||||
this.layoutEvent = undefined;
|
||||
this.chapterEndingVisible = van.state(false);
|
||||
|
||||
this.post = obj => window.ReactNativeWebView.postMessage(JSON.stringify(obj));
|
||||
this.refresh = () => {
|
||||
if (this.generalSettings.val.pageReader) {
|
||||
@@ -93,7 +99,7 @@ window.reader = new (function () {
|
||||
}
|
||||
});
|
||||
|
||||
setInterval(() => {
|
||||
document.onscrollend = () => {
|
||||
if (!this.generalSettings.val.pageReader) {
|
||||
this.post({
|
||||
type: 'save',
|
||||
@@ -102,17 +108,11 @@ window.reader = new (function () {
|
||||
10,
|
||||
),
|
||||
});
|
||||
} else {
|
||||
this.post({
|
||||
type: 'save',
|
||||
data: parseInt(
|
||||
((pageReader.page.val + 1) / pageReader.totalPages.val) * 100,
|
||||
),
|
||||
});
|
||||
}
|
||||
}, this.autoSaveInterval);
|
||||
};
|
||||
|
||||
if (DEBUG) {
|
||||
// eslint-disable-next-line no-global-assign, no-new-object
|
||||
console = new Object();
|
||||
console.log = function (...data) {
|
||||
reader.post({ 'type': 'console', 'msg': data?.join(' ') });
|
||||
@@ -142,7 +142,7 @@ window.tts = new (function () {
|
||||
this.reading = false;
|
||||
|
||||
this.readable = element => {
|
||||
ele = element ?? this.currentElement;
|
||||
const ele = element ?? this.currentElement;
|
||||
if (
|
||||
ele.nodeName !== 'SPAN' &&
|
||||
this.readableNodeNames.includes(ele.nodeName)
|
||||
@@ -160,6 +160,14 @@ window.tts = new (function () {
|
||||
return true;
|
||||
};
|
||||
|
||||
this.normalizeText = text => {
|
||||
if (!text) return '';
|
||||
return text
|
||||
.replace(/\s+/g, ' ')
|
||||
.replace(/\s*([.,!?;:])\s*/g, '$1 ')
|
||||
.trim();
|
||||
};
|
||||
|
||||
// if can find a readable node, else stop tts
|
||||
this.findNextTextNode = () => {
|
||||
if (this.currentElement.isSameNode(reader.chapterElement) && this.started) {
|
||||
@@ -204,13 +212,23 @@ window.tts = new (function () {
|
||||
try {
|
||||
this.currentElement?.classList?.remove('highlight');
|
||||
if (this.findNextTextNode()) {
|
||||
this.reading = true;
|
||||
this.speak();
|
||||
const text = this.normalizeText(this.currentElement?.innerText);
|
||||
if (text) {
|
||||
this.reading = true;
|
||||
this.speak();
|
||||
} else {
|
||||
this.next();
|
||||
}
|
||||
} else {
|
||||
this.reading = false;
|
||||
this.stop();
|
||||
document.getElementById('TTS-Controller').firstElementChild.innerHTML =
|
||||
volumnIcon;
|
||||
const controller = document.getElementById('TTS-Controller');
|
||||
if (controller?.firstElementChild) {
|
||||
controller.firstElementChild.innerHTML = volumnIcon;
|
||||
}
|
||||
if (reader.nextChapter) {
|
||||
reader.post({ type: 'next', autoStartTTS: true });
|
||||
}
|
||||
}
|
||||
} catch (e) {
|
||||
alert(e);
|
||||
@@ -240,6 +258,7 @@ window.tts = new (function () {
|
||||
this.pause = () => {
|
||||
this.reading = false;
|
||||
reader.post({ type: 'stop-speak' });
|
||||
reader.post({ type: 'tts-state', data: { isReading: false } });
|
||||
};
|
||||
|
||||
this.stop = () => {
|
||||
@@ -249,30 +268,142 @@ window.tts = new (function () {
|
||||
this.currentElement = reader.chapterElement;
|
||||
this.started = false;
|
||||
this.reading = false;
|
||||
reader.post({ type: 'tts-state', data: { isReading: false } });
|
||||
};
|
||||
|
||||
this.isElementInViewport = element => {
|
||||
if (!element) return false;
|
||||
const rect = element.getBoundingClientRect();
|
||||
const windowHeight =
|
||||
window.innerHeight || document.documentElement.clientHeight;
|
||||
const windowWidth =
|
||||
window.innerWidth || document.documentElement.clientWidth;
|
||||
|
||||
return (
|
||||
rect.top >= 0 &&
|
||||
rect.left >= 0 &&
|
||||
rect.bottom <= windowHeight &&
|
||||
rect.right <= windowWidth
|
||||
);
|
||||
};
|
||||
|
||||
this.scrollToElement = element => {
|
||||
if (!element) return;
|
||||
// Check if element is partially visible (at least some part is in viewport)
|
||||
const rect = element.getBoundingClientRect();
|
||||
const windowHeight =
|
||||
window.innerHeight || document.documentElement.clientHeight;
|
||||
const isPartiallyVisible =
|
||||
rect.top < windowHeight &&
|
||||
rect.bottom > 0 &&
|
||||
rect.left < window.innerWidth &&
|
||||
rect.right > 0;
|
||||
|
||||
// Only scroll if element is not visible or barely visible
|
||||
if (!isPartiallyVisible || rect.top < 0 || rect.bottom > windowHeight) {
|
||||
element.scrollIntoView({
|
||||
behavior: 'smooth',
|
||||
block: 'center',
|
||||
inline: 'nearest',
|
||||
});
|
||||
}
|
||||
};
|
||||
|
||||
this.speak = () => {
|
||||
if (!this.currentElement) return;
|
||||
this.prevElement = this.currentElement;
|
||||
this.scrollToElement(this.currentElement);
|
||||
this.currentElement.classList.add('highlight');
|
||||
reader.post({ type: 'speak', data: this.currentElement?.innerText });
|
||||
const text = this.normalizeText(this.currentElement.innerText);
|
||||
if (text) {
|
||||
reader.post({ type: 'speak', data: text });
|
||||
reader.post({ type: 'tts-state', data: { isReading: true } });
|
||||
} else {
|
||||
this.next();
|
||||
}
|
||||
};
|
||||
})();
|
||||
|
||||
window.pageReader = new (function () {
|
||||
this.page = van.state(0);
|
||||
this.totalPages = van.state(0);
|
||||
this.chapterEndingVisible = van.state(
|
||||
initialPageReaderConfig.nextChapterScreenVisible,
|
||||
);
|
||||
this.chapterEnding = document.getElementsByClassName('transition-chapter')[0];
|
||||
|
||||
this.showChapterEnding = (bool, instant, left) => {
|
||||
if (!this.chapterEnding) {
|
||||
this.chapterEnding =
|
||||
document.getElementsByClassName('transition-chapter')[0];
|
||||
if (!this.chapterEnding) return;
|
||||
}
|
||||
this.chapterEnding.style.transition = 'unset';
|
||||
if (bool) {
|
||||
this.chapterEnding.style.transform = `translateX(${left ? -200 : 0}vw)`;
|
||||
requestAnimationFrame(() => {
|
||||
if (!instant) this.chapterEnding.style.transition = '200ms';
|
||||
this.chapterEnding.style.transform = 'translateX(-100vw)';
|
||||
});
|
||||
this.chapterEndingVisible.val = true;
|
||||
} else {
|
||||
if (!instant) this.chapterEnding.style.transition = '200ms';
|
||||
this.chapterEnding.style.transform = `translateX(${left ? -200 : 0}vw)`;
|
||||
this.chapterEndingVisible.val = false;
|
||||
}
|
||||
};
|
||||
|
||||
this.movePage = destPage => {
|
||||
destPage = parseInt(destPage);
|
||||
if (this.chapterEndingVisible.val) {
|
||||
if (destPage < 0) {
|
||||
this.showChapterEnding(false);
|
||||
return;
|
||||
}
|
||||
if (destPage < this.totalPages.val) {
|
||||
this.showChapterEnding(false, false, true);
|
||||
return;
|
||||
}
|
||||
if (destPage >= this.totalPages.val) {
|
||||
return reader.post({ type: 'next' });
|
||||
}
|
||||
}
|
||||
destPage = parseInt(destPage, 10);
|
||||
if (destPage < 0) {
|
||||
return reader.post({ type: 'prev' });
|
||||
document.getElementsByClassName('transition-chapter')[0].innerText =
|
||||
reader.prevChapter.name;
|
||||
this.showChapterEnding(true, false, true);
|
||||
setTimeout(() => {
|
||||
reader.post({ type: 'prev' });
|
||||
}, 200);
|
||||
return;
|
||||
}
|
||||
if (destPage >= this.totalPages.val) {
|
||||
return reader.post({ type: 'next' });
|
||||
document.getElementsByClassName('transition-chapter')[0].innerText =
|
||||
reader.nextChapter.name;
|
||||
this.showChapterEnding(true);
|
||||
setTimeout(() => {
|
||||
reader.post({ type: 'next' });
|
||||
}, 200);
|
||||
return;
|
||||
}
|
||||
this.page.val = destPage;
|
||||
reader.chapterElement.style.transform =
|
||||
'translateX(-' + destPage * 100 + '%)';
|
||||
|
||||
const newProgress = parseInt(
|
||||
((pageReader.page.val + 1) / pageReader.totalPages.val) * 100,
|
||||
10,
|
||||
);
|
||||
|
||||
if (newProgress > reader.chapter.progress) {
|
||||
reader.post({
|
||||
type: 'save',
|
||||
data: parseInt(
|
||||
((pageReader.page.val + 1) / pageReader.totalPages.val) * 100,
|
||||
10,
|
||||
),
|
||||
});
|
||||
}
|
||||
};
|
||||
|
||||
van.derive(() => {
|
||||
@@ -294,6 +425,7 @@ window.pageReader = new (function () {
|
||||
this.totalPages.val = parseInt(
|
||||
(reader.chapterWidth + reader.readerSettings.val.padding * 2) /
|
||||
reader.layoutWidth,
|
||||
10,
|
||||
);
|
||||
this.movePage(this.totalPages.val * ratio);
|
||||
}, 100);
|
||||
@@ -313,30 +445,54 @@ window.pageReader = new (function () {
|
||||
});
|
||||
})();
|
||||
|
||||
window.addEventListener('DOMContentLoaded', async () => {
|
||||
// wait for content is rendered
|
||||
setTimeout(() => {
|
||||
reader.refresh();
|
||||
if (reader.generalSettings.val.pageReader) {
|
||||
pageReader.totalPages.val = parseInt(
|
||||
(reader.chapterWidth + reader.readerSettings.val.padding * 2) /
|
||||
reader.layoutWidth,
|
||||
);
|
||||
pageReader.movePage(
|
||||
parseInt(
|
||||
pageReader.totalPages.val *
|
||||
Math.min(0.99, reader.chapter.progress / 100),
|
||||
),
|
||||
);
|
||||
} else {
|
||||
window.scrollTo({
|
||||
top:
|
||||
(reader.chapterHeight * reader.chapter.progress) / 100 -
|
||||
reader.layoutHeight,
|
||||
behavior: 'smooth',
|
||||
});
|
||||
}
|
||||
}, 100);
|
||||
document.addEventListener('DOMContentLoaded', () => {
|
||||
if (pageReader.chapterEndingVisible.val) {
|
||||
pageReader.showChapterEnding(true, true);
|
||||
}
|
||||
});
|
||||
|
||||
function calculatePages() {
|
||||
reader.refresh();
|
||||
|
||||
if (reader.generalSettings.val.pageReader) {
|
||||
pageReader.totalPages.val = parseInt(
|
||||
(reader.chapterWidth + reader.readerSettings.val.padding * 2) /
|
||||
reader.layoutWidth,
|
||||
10,
|
||||
);
|
||||
|
||||
if (initialPageReaderConfig.nextChapterScreenVisible) return;
|
||||
|
||||
pageReader.movePage(
|
||||
Math.max(
|
||||
0,
|
||||
Math.round(
|
||||
(pageReader.totalPages.val * reader.chapter.progress) / 100,
|
||||
) - 1,
|
||||
),
|
||||
);
|
||||
} else {
|
||||
window.scrollTo({
|
||||
top:
|
||||
(reader.chapterHeight * reader.chapter.progress) / 100 -
|
||||
reader.layoutHeight,
|
||||
behavior: 'smooth',
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
const ro = new ResizeObserver(() => {
|
||||
if (pageReader.totalPages.val) {
|
||||
calculatePages();
|
||||
}
|
||||
});
|
||||
ro.observe(reader.chapterElement);
|
||||
|
||||
// Also call once on load
|
||||
window.addEventListener('load', () => {
|
||||
document.fonts.ready.then(() => {
|
||||
requestAnimationFrame(() => setTimeout(calculatePages, 0));
|
||||
});
|
||||
});
|
||||
|
||||
// click handler
|
||||
@@ -365,6 +521,7 @@ window.addEventListener('DOMContentLoaded', async () => {
|
||||
x: clientX / reader.layoutWidth,
|
||||
y: clientY / reader.layoutHeight,
|
||||
};
|
||||
|
||||
if (reader.generalSettings.val.pageReader) {
|
||||
const position = detectTapPosition(x, y, true);
|
||||
if (position === 'left') {
|
||||
@@ -464,7 +621,22 @@ window.addEventListener('DOMContentLoaded', async () => {
|
||||
}
|
||||
|
||||
if (reader.generalSettings.val.removeExtraParagraphSpacing) {
|
||||
html = html.replace(/<\s*br[^>]*>/gi, '\n').replace(/\n{2,}/g, '\n\n');
|
||||
html = html
|
||||
.replace(/(?: \s*|[\u200b]\s*)+(?=<\/?p[> ])/g, '')
|
||||
.replace(/<br>\s*<br>\s*(?:<br>\s*)+/g, '<br><br>') //force max 2 consecutive <br>, chaining regex
|
||||
.replace(
|
||||
/<br>\s*<br>[^]+/,
|
||||
_ =>
|
||||
`${
|
||||
/\/p>/.test(_)
|
||||
? _.replace(
|
||||
/<br>\s*<br>(?:(?=\s*<\/?p[> ])|(?<=<\/?p\b[^>]*><br>\s*<br>))\s*/g,
|
||||
'',
|
||||
)
|
||||
: _
|
||||
}`,
|
||||
) //if p found, delete all double br near p
|
||||
.replace(/<br>(?:(?=\s*<\/?p[> ])|(?<=<\/?p>\s*<br>))\s*/g, '');
|
||||
}
|
||||
reader.chapterElement.innerHTML = html;
|
||||
});
|
||||
|
||||
@@ -9,7 +9,10 @@ const ChapterEnding = () => {
|
||||
? button(
|
||||
{
|
||||
class: 'next-button',
|
||||
onclick: () => reader.post({ type: 'next' }),
|
||||
onclick: e => {
|
||||
e.stopPropagation();
|
||||
reader.post({ type: 'next' });
|
||||
},
|
||||
},
|
||||
reader.strings.nextChapter,
|
||||
)
|
||||
|
||||
@@ -0,0 +1,91 @@
|
||||
/* eslint-disable eslint-comments/no-unlimited-disable */
|
||||
/* eslint-disable */
|
||||
'use strict';
|
||||
(() => {
|
||||
function f(i, o, r, s = !1) {
|
||||
if (!(o in i) || typeof r != 'function') return !1;
|
||||
let t = i[o];
|
||||
return (
|
||||
(i[o] = function (...e) {
|
||||
return (
|
||||
s || t.apply(this, e), (t = t.bind(this)), r.apply(this, [t, ...e])
|
||||
);
|
||||
}),
|
||||
!0
|
||||
);
|
||||
}
|
||||
if (typeof window == 'undefined') throw 'Not in a browser.';
|
||||
var p = 'onscrollend' in window;
|
||||
if (!p) {
|
||||
let s = function (e, u, l) {
|
||||
if (u !== 'scrollend') {
|
||||
e.apply(this, [u, l]);
|
||||
return;
|
||||
}
|
||||
let c = this,
|
||||
n = i.get(c);
|
||||
if (!n) {
|
||||
let d;
|
||||
(n = {
|
||||
scrollFn: () => {
|
||||
clearTimeout(d),
|
||||
(d = setTimeout(() => {
|
||||
l();
|
||||
}, 100));
|
||||
},
|
||||
handlers: [l],
|
||||
}),
|
||||
e.apply(c, ['scroll', n.scrollFn, !1]),
|
||||
i.set(c, n);
|
||||
}
|
||||
},
|
||||
t = function (e, u, l) {
|
||||
if (u !== 'scrollend') {
|
||||
e.apply(this, [u, l]);
|
||||
return;
|
||||
}
|
||||
let c = this,
|
||||
n = i.get(c);
|
||||
if (n) {
|
||||
if (
|
||||
(typeof n.scrollFn == 'function' &&
|
||||
e.apply(this, ['scroll', n.scrollFn]),
|
||||
typeof l == 'undefined')
|
||||
)
|
||||
n.handlers = [];
|
||||
else {
|
||||
let d = n.handlers.indexOf(l);
|
||||
d > -1 && n.handlers.splice(d, 1);
|
||||
}
|
||||
n.handlers.length === 0 && i.delete(c);
|
||||
}
|
||||
};
|
||||
(a = s), (w = t);
|
||||
let i = new WeakMap(),
|
||||
o = null,
|
||||
r = null;
|
||||
f(window, 'addEventListener', s, !0),
|
||||
f(window, 'removeEventListener', t, !0),
|
||||
Object.defineProperty(window, 'onscrollend', {
|
||||
set: e => {
|
||||
(typeof e != 'function' || o) &&
|
||||
t.call(window, window.removeEventListener, 'scrollend', o),
|
||||
(o = e),
|
||||
e && s.call(window, window.addEventListener, 'scrollend', e);
|
||||
},
|
||||
get: () => o,
|
||||
}),
|
||||
f(document, 'addEventListener', s, !0),
|
||||
f(document, 'removeEventListener', t, !0),
|
||||
Object.defineProperty(document, 'onscrollend', {
|
||||
set: e => {
|
||||
(typeof e != 'function' || r) &&
|
||||
t.call(document, document.removeEventListener, 'scrollend', r),
|
||||
(r = e),
|
||||
e && s.call(document, document.addEventListener, 'scrollend', e);
|
||||
},
|
||||
get: () => r,
|
||||
});
|
||||
}
|
||||
var a, w;
|
||||
})();
|
||||
@@ -1 +1 @@
|
||||
var textVide = function(L){'use strict'; const f = t=>t == null || t === '',h = (t,n)=>Object.keys(t).reduce((e,s)=>(n(e[s]) && delete e[s],e),t),I = (t,n)=>({...n,...h(t,f)}),_ = ['<b>','</b>'],y = t=>I(t,{sep:_,fixationPoint:1,ignoreHtmlTag:!0,ignoreHtmlEntity:!0}),u = [[0,4,12,17,24,29,35,42,48],[1,2,7,10,13,14,19,22,25,28,31,34,37,40,43,46,49],[1,2,5,7,9,11,13,15,17,19,21,23,25,27,29,31,33,35,37,39,41,43,45,47,49],[0,2,4,5,6,8,9,11,14,15,17,18,20,0,21,23,24,26,27,29,30,32,33,35,36,38,39,41,42,44,45,47,48],[0,2,3,5,6,7,8,10,11,12,14,15,17,19,20,21,23,24,25,26,28,29,30,32,33,34,35,37,38,39,41,42,43,44,46,47,48]],A = (t,n)=>{const {length:i} = t,e = u[n - 1] ?? u[0],s = e.findIndex(c=>i <= c); let o = i - s; return s === -1 && (o = i - e.length),Math.max(o,0);},R = (t,n)=>typeof n === 'string' ? `${n}${t}${n}` : `${n[0]}${t}${n[1]}`,m = t=>Array.from(t).map(n=>{const i = n.index,[e] = n,{length:s} = e; return [i,i + s - 1];}),H = /(<!--[\s\S]*?-->)|(<[^>]*>)/g,N = t=>{const n = t.matchAll(H),e = m(n).reverse(); return s=>{const o = s.index,c = e.find(([r])=>o > r); if (!c) {return !1;} const [,a] = c; return o < a;};},O = /(&[\w#]+;)/g,M = t=>{const n = t.matchAll(O),e = m(n).reverse(); return s=>{const o = s.index,c = e.find(([r])=>o > r); if (!c) {return !1;} const [,a] = c; return o < a;};},x = /(\p{L}|\p{Nd})*\p{L}(\p{L}|\p{Nd})*/gu,F = (t,n = {})=>{if (!(t != null && t.length)) {return '';} const {fixationPoint:i,sep:e,ignoreHtmlTag:s,ignoreHtmlEntity:o} = y(n),c = Array.from(t.matchAll(x)); let a = '',l = 0,r; s && (r = N(t)); let d; o && (d = M(t)); for (const g of c){if ((r == null ? void 0 : r(g)) || (d == null ? void 0 : d(g))) {continue;} const [D] = g,T = g.index,E = T + A(D,i),U = t.slice(l,T); a += U,T !== E && (a += R(t.slice(T,E),e)),l = E;} const G = t.slice(l); return a + G;}; return L.textVide = F,Object.defineProperty(L,Symbol.toStringTag,{value:'Module'}),L;}({});
|
||||
var textVide = function(L){'use strict'; const f = t=>t == null || t === '',h = (t,n)=>Object.keys(t).reduce((e,s)=>(n(e[s]) && delete e[s],e),t),I = (t,n)=>({...n,...h(t,f)}),_ = ['<b>','</b>'],y = t=>I(t,{sep:_,fixationPoint:1,ignoreHtmlTag:!0,ignoreHtmlEntity:!0}),u = [[0,4,12,17,24,29,35,42,48],[1,2,7,10,13,14,19,22,25,28,31,34,37,40,43,46,49],[1,2,5,7,9,11,13,15,17,19,21,23,25,27,29,31,33,35,37,39,41,43,45,47,49],[0,2,4,5,6,8,9,11,14,15,17,18,20,0,21,23,24,26,27,29,30,32,33,35,36,38,39,41,42,44,45,47,48],[0,2,3,5,6,7,8,10,11,12,14,15,17,19,20,21,23,24,25,26,28,29,30,32,33,34,35,37,38,39,41,42,43,44,46,47,48]],A = (t,n)=>{const {length:i} = t,e = u[n - 1] ?? u[0],s = e.findIndex(c=>i <= c); let o = i - s; return s === -1 && (o = i - e.length),Math.max(o,0);},R = (t,n)=>typeof n === 'string' ? `${n}${t}${n}` : `${n[0]}${t}${n[1]}`,m = t=>Array.from(t).map(n=>{const i = n.index,[e] = n,{length:s} = e; return [i,i + s - 1];}),H = /<!--[^]*?-->|(<[^>]+>)/g,N = t=>{const n = t.matchAll(H),e = m(n).reverse(); return s=>{const o = s.index,c = e.find(([r])=>o > r); if (!c) {return !1;} const [,a] = c; return o < a;};},O = /&[\w#]+;/g,M = t=>{const n = t.matchAll(O),e = m(n).reverse(); return s=>{const o = s.index,c = e.find(([r])=>o > r); if (!c) {return !1;} const [,a] = c; return o < a;};},x = /(\p{L}|\p{Nd})*\p{L}(\p{L}|\p{Nd})*/gu,F = (t,n = {})=>{if (!(t != null && t.length)) {return '';} const {fixationPoint:i,sep:e,ignoreHtmlTag:s,ignoreHtmlEntity:o} = y(n),c = Array.from(t.matchAll(x)); let a = '',l = 0,r; s && (r = N(t)); let d; o && (d = M(t)); for (const g of c){if ((r == null ? void 0 : r(g)) || (d == null ? void 0 : d(g))) {continue;} const [D] = g,T = g.index,E = T + A(D,i),U = t.slice(l,T); a += U,T !== E && (a += R(t.slice(T,E),e)),l = E;} const G = t.slice(l); return a + G;}; return L.textVide = F,Object.defineProperty(L,Symbol.toStringTag,{value:'Module'}),L;}({});
|
||||
|
||||
@@ -1,3 +0,0 @@
|
||||
package com.rajarsheechatterjee.EpubUtil
|
||||
|
||||
class ChapterEntry (val name: String, val href: String)
|
||||
@@ -1,204 +0,0 @@
|
||||
package com.rajarsheechatterjee.EpubUtil
|
||||
|
||||
import android.util.Xml
|
||||
import com.facebook.react.bridge.Promise
|
||||
import com.facebook.react.bridge.ReactApplicationContext
|
||||
import com.facebook.react.bridge.ReactContextBaseJavaModule
|
||||
import com.facebook.react.bridge.ReactMethod
|
||||
import com.facebook.react.bridge.ReadableArray
|
||||
import com.facebook.react.bridge.ReadableMap
|
||||
import com.facebook.react.bridge.WritableArray
|
||||
import com.facebook.react.bridge.WritableMap
|
||||
import com.facebook.react.bridge.WritableNativeArray
|
||||
import com.facebook.react.bridge.WritableNativeMap
|
||||
import org.xmlpull.v1.XmlPullParser
|
||||
import java.io.File
|
||||
import java.io.FileInputStream
|
||||
|
||||
class EpubUtil(context: ReactApplicationContext) : ReactContextBaseJavaModule(context) {
|
||||
override fun getName(): String {
|
||||
return "EpubUtil"
|
||||
}
|
||||
|
||||
private fun initParse(file: File): XmlPullParser {
|
||||
val parser = Xml.newPullParser()
|
||||
parser.setFeature(XmlPullParser.FEATURE_PROCESS_NAMESPACES, true)
|
||||
parser.setInput(FileInputStream(file), null)
|
||||
parser.nextTag()
|
||||
return parser
|
||||
}
|
||||
|
||||
private fun readText(parser: XmlPullParser): String {
|
||||
var result = ""
|
||||
if (parser.next() == XmlPullParser.TEXT) {
|
||||
result = parser.text
|
||||
parser.nextTag()
|
||||
}
|
||||
return result
|
||||
}
|
||||
|
||||
private fun cleanUrl(url: String): String {
|
||||
return url.replaceFirst("#[^.]+?$".toRegex(), "")
|
||||
}
|
||||
|
||||
@ReactMethod
|
||||
fun parseNovelAndChapters(epubDirPath: String, promise: Promise) {
|
||||
try {
|
||||
val containerFile = File(epubDirPath, "META-INF/container.xml")
|
||||
val contentMetaFile = File(epubDirPath, getContentMetaFilePath(containerFile))
|
||||
val contentDir = contentMetaFile.parent
|
||||
val novel = contentDir?.let { getNovelMetadata(contentMetaFile, it) }
|
||||
promise.resolve(novel)
|
||||
} catch (e: Exception) {
|
||||
promise.reject(e)
|
||||
}
|
||||
}
|
||||
|
||||
private fun getContentMetaFilePath(file: File): String {
|
||||
val parser = initParse(file)
|
||||
while (parser.next() != XmlPullParser.END_TAG) {
|
||||
val tag = parser.name
|
||||
if (tag != null && tag == "rootfile") {
|
||||
return parser.getAttributeValue(null, "full-path")
|
||||
}
|
||||
}
|
||||
return "OEBPS/content.opf" // default
|
||||
}
|
||||
|
||||
private fun mergeChapters(
|
||||
entryList: List<ChapterEntry>,
|
||||
fileList: List<String>,
|
||||
contentDir: String
|
||||
): ReadableArray {
|
||||
val foundIndexes = entryList.mapIndexed { index, entry ->
|
||||
val foundIndex = fileList.indexOf(entry.href)
|
||||
if (foundIndex < index) -1
|
||||
else foundIndex
|
||||
}
|
||||
val linkedIndex = foundIndexes.mapIndexed { arrayIndex, foundIndex ->
|
||||
if (foundIndex == -1) Pair(-1, -1)
|
||||
else {
|
||||
var nextIndex = -1
|
||||
for (j in arrayIndex + 1 until foundIndexes.size) {
|
||||
if (foundIndexes[j] != -1) {
|
||||
nextIndex = foundIndexes[j]
|
||||
break
|
||||
}
|
||||
}
|
||||
if (nextIndex == -1) nextIndex = fileList.size
|
||||
Pair(foundIndex, nextIndex)
|
||||
}
|
||||
}
|
||||
|
||||
val chapters: WritableArray = WritableNativeArray()
|
||||
linkedIndex.forEachIndexed { arrayIndex, (firstIndex, lastIndex) ->
|
||||
val chapter = WritableNativeMap()
|
||||
val entryPath = "${contentDir}/${entryList[arrayIndex].href}"
|
||||
chapter.putString("name", entryList[arrayIndex].name)
|
||||
if (firstIndex == -1 || firstIndex == lastIndex) {
|
||||
chapter.putString("path", entryPath)
|
||||
} else {
|
||||
val mergedChapterPath = "${entryPath}-copy-$arrayIndex.html"
|
||||
chapter.putString("path", mergedChapterPath)
|
||||
val entryFile = File(entryPath)
|
||||
val mergedFile = File(mergedChapterPath)
|
||||
mergedFile.createNewFile()
|
||||
mergedFile.appendBytes(entryFile.readBytes())
|
||||
for (i in firstIndex + 1 until lastIndex) {
|
||||
val file = File("${contentDir}/${fileList[i]}")
|
||||
mergedFile.appendBytes(file.readBytes())
|
||||
}
|
||||
}
|
||||
chapters.pushMap(chapter)
|
||||
}
|
||||
return chapters
|
||||
}
|
||||
|
||||
private fun getNovelMetadata(file: File, contentDir: String): ReadableMap {
|
||||
val novel: WritableMap = WritableNativeMap()
|
||||
val parser = initParse(file)
|
||||
val refMap = HashMap<String, String>()
|
||||
val entryList = mutableListOf<ChapterEntry>()
|
||||
val fileList = mutableListOf<String>()
|
||||
val tocFile = File(contentDir, "toc.ncx")
|
||||
if (tocFile.exists()) {
|
||||
val tocParser = initParse(tocFile)
|
||||
var label = ""
|
||||
while (tocParser.next() != XmlPullParser.END_DOCUMENT) {
|
||||
if (tocParser.name == "navMap") break
|
||||
}
|
||||
while (tocParser.next() != XmlPullParser.END_DOCUMENT) {
|
||||
val tag = tocParser.name
|
||||
if (tag == "navMap") break
|
||||
if (tag != null) {
|
||||
if (tag == "text") {
|
||||
label = readText(tocParser)
|
||||
} else if (tag == "content") {
|
||||
val href = cleanUrl(tocParser.getAttributeValue(null, "src"))
|
||||
if (label.isNotBlank() && File("${contentDir}/$href").exists()) {
|
||||
entryList.add(ChapterEntry(name = label, href = href))
|
||||
}
|
||||
label = ""
|
||||
}
|
||||
}
|
||||
}
|
||||
} else {
|
||||
throw Exception("Table of content (toc.ncx) does not exist!")
|
||||
}
|
||||
var cover: String? = null
|
||||
while (parser.next() != XmlPullParser.END_DOCUMENT) {
|
||||
val tag = parser.name
|
||||
if (tag != null) {
|
||||
when (tag) {
|
||||
"item" -> {
|
||||
val id = parser.getAttributeValue(null, "id")
|
||||
val href = parser.getAttributeValue(null, "href")
|
||||
if (id != null) {
|
||||
refMap[id] = href
|
||||
}
|
||||
}
|
||||
|
||||
"itemref" -> {
|
||||
val idRef = parser.getAttributeValue(null, "idref")
|
||||
val href = refMap[idRef]
|
||||
if (href != null && File("$contentDir/$href").exists()) {
|
||||
fileList.add(href)
|
||||
}
|
||||
}
|
||||
|
||||
"title" -> novel.putString("name", readText(parser))
|
||||
"creator" -> novel.putString("author", readText(parser))
|
||||
"contributor" -> novel.putString("artist", readText(parser))
|
||||
"description" -> novel.putString("summary", readText(parser))
|
||||
"meta" -> {
|
||||
val metaName = parser.getAttributeValue(null, "name")
|
||||
if (metaName != null && metaName == "cover") {
|
||||
cover = parser.getAttributeValue(null, "content")
|
||||
}
|
||||
}
|
||||
}
|
||||
parser.next()
|
||||
}
|
||||
}
|
||||
val chapters = mergeChapters(entryList, fileList, contentDir)
|
||||
novel.putArray("chapters", chapters)
|
||||
if (cover != null) {
|
||||
val coverPath = contentDir + "/" + refMap[cover]
|
||||
novel.putString("cover", coverPath)
|
||||
} else {
|
||||
// try scanning Images dir if exists
|
||||
val imageDir = File("$contentDir/Images")
|
||||
if (imageDir.exists() && imageDir.isDirectory) {
|
||||
imageDir.listFiles()?.forEach { img ->
|
||||
if (img.isFile && img.name.lowercase().contains("cover|illus".toRegex())) {
|
||||
cover = img.path
|
||||
}
|
||||
}
|
||||
}
|
||||
if (cover != null) {
|
||||
novel.putString("cover", cover)
|
||||
}
|
||||
}
|
||||
return novel
|
||||
}
|
||||
}
|
||||
@@ -1,17 +0,0 @@
|
||||
package com.rajarsheechatterjee.EpubUtil
|
||||
|
||||
import android.view.View
|
||||
import com.facebook.react.ReactPackage
|
||||
import com.facebook.react.bridge.NativeModule
|
||||
import com.facebook.react.bridge.ReactApplicationContext
|
||||
import com.facebook.react.uimanager.ReactShadowNode
|
||||
import com.facebook.react.uimanager.ViewManager
|
||||
|
||||
class EpubUtilPackage : ReactPackage {
|
||||
override fun createNativeModules(context: ReactApplicationContext): MutableList<NativeModule> =
|
||||
listOf(EpubUtil(context)).toMutableList()
|
||||
|
||||
override fun createViewManagers(
|
||||
reactContext: ReactApplicationContext
|
||||
): MutableList<ViewManager<View, ReactShadowNode<*>>> = mutableListOf()
|
||||
}
|
||||
@@ -1,17 +0,0 @@
|
||||
package com.rajarsheechatterjee.FileManager
|
||||
|
||||
import android.view.View
|
||||
import com.facebook.react.ReactPackage
|
||||
import com.facebook.react.bridge.NativeModule
|
||||
import com.facebook.react.bridge.ReactApplicationContext
|
||||
import com.facebook.react.uimanager.ReactShadowNode
|
||||
import com.facebook.react.uimanager.ViewManager
|
||||
|
||||
class FileManagerPackage : ReactPackage {
|
||||
override fun createNativeModules(context: ReactApplicationContext): MutableList<NativeModule> =
|
||||
listOf(FileManager(context)).toMutableList()
|
||||
|
||||
override fun createViewManagers(
|
||||
reactContext: ReactApplicationContext
|
||||
): MutableList<ViewManager<View, ReactShadowNode<*>>> = mutableListOf()
|
||||
}
|
||||
@@ -10,7 +10,7 @@ import com.facebook.react.ReactActivity
|
||||
import com.facebook.react.ReactActivityDelegate
|
||||
import com.facebook.react.defaults.DefaultNewArchitectureEntryPoint.fabricEnabled
|
||||
import com.facebook.react.defaults.DefaultReactActivityDelegate
|
||||
import com.rajarsheechatterjee.VolumeButtonListener.VolumeButtonListener
|
||||
import com.rajarsheechatterjee.NativeVolumeButtonListener.NativeVolumeButtonListener
|
||||
import expo.modules.ReactActivityDelegateWrapper
|
||||
import org.devio.rn.splashscreen.SplashScreen
|
||||
|
||||
@@ -33,23 +33,19 @@ class MainActivity : ReactActivity() {
|
||||
}
|
||||
|
||||
override fun dispatchKeyEvent(event: KeyEvent): Boolean {
|
||||
if (VolumeButtonListener.isActive) {
|
||||
if (NativeVolumeButtonListener.isActive) {
|
||||
val action = event.action
|
||||
return when (event.keyCode) {
|
||||
KeyEvent.KEYCODE_VOLUME_UP -> {
|
||||
if (action == KeyEvent.ACTION_DOWN) {
|
||||
// volup
|
||||
VolumeButtonListener.up()
|
||||
if (!VolumeButtonListener.prevent) return super.dispatchKeyEvent(event)
|
||||
NativeVolumeButtonListener.sendEvent(true)
|
||||
}
|
||||
true
|
||||
}
|
||||
|
||||
KeyEvent.KEYCODE_VOLUME_DOWN -> {
|
||||
if (action == KeyEvent.ACTION_DOWN) {
|
||||
// voldown
|
||||
VolumeButtonListener.down()
|
||||
if (!VolumeButtonListener.prevent) return super.dispatchKeyEvent(event)
|
||||
NativeVolumeButtonListener.sendEvent(false)
|
||||
}
|
||||
true
|
||||
}
|
||||
@@ -64,9 +60,7 @@ class MainActivity : ReactActivity() {
|
||||
* Returns the name of the main component registered from JavaScript.
|
||||
* This is used to schedule rendering of the component.
|
||||
*/
|
||||
override fun getMainComponentName(): String {
|
||||
return "main"
|
||||
}
|
||||
override fun getMainComponentName(): String = "main"
|
||||
|
||||
override fun createReactActivityDelegate(): ReactActivityDelegate {
|
||||
return ReactActivityDelegateWrapper(
|
||||
|
||||
@@ -4,61 +4,49 @@ import android.app.Application
|
||||
import android.content.res.Configuration
|
||||
import com.facebook.react.PackageList
|
||||
import com.facebook.react.ReactApplication
|
||||
import com.facebook.react.ReactHost
|
||||
import com.facebook.react.ReactNativeApplicationEntryPoint.loadReactNative
|
||||
import com.facebook.react.ReactNativeHost
|
||||
import com.facebook.react.ReactPackage
|
||||
import com.facebook.react.defaults.DefaultNewArchitectureEntryPoint.load
|
||||
import com.facebook.react.defaults.DefaultReactHost.getDefaultReactHost
|
||||
import com.facebook.react.defaults.DefaultReactNativeHost
|
||||
import com.facebook.react.soloader.OpenSourceMergedSoMapping
|
||||
import com.facebook.soloader.SoLoader
|
||||
import com.rajarsheechatterjee.EpubUtil.EpubUtilPackage
|
||||
import com.rajarsheechatterjee.FileManager.FileManagerPackage
|
||||
import com.rajarsheechatterjee.VolumeButtonListener.VolumeButtonListenerPackage
|
||||
import com.rajarsheechatterjee.ZipArchive.ZipArchivePackage
|
||||
import expo.modules.ApplicationLifecycleDispatcher.onApplicationCreate
|
||||
import expo.modules.ApplicationLifecycleDispatcher.onConfigurationChanged
|
||||
import expo.modules.ReactNativeHostWrapper
|
||||
import com.rajarsheechatterjee.NativeFile.NativePackage
|
||||
import com.rajarsheechatterjee.NativeVolumeButtonListener.NativeVolumeButtonListenerPackage
|
||||
import com.rajarsheechatterjee.NativeZipArchive.NativeZipArchivePackage
|
||||
import expo.modules.ApplicationLifecycleDispatcher
|
||||
|
||||
class MainApplication : Application(), ReactApplication {
|
||||
private val mReactNativeHost: ReactNativeHost =
|
||||
ReactNativeHostWrapper(this, object : DefaultReactNativeHost(this) {
|
||||
override fun getUseDeveloperSupport(): Boolean {
|
||||
return BuildConfig.DEBUG
|
||||
}
|
||||
override val reactNativeHost: ReactNativeHost =
|
||||
object : DefaultReactNativeHost(this) {
|
||||
override fun getPackages(): List<ReactPackage> =
|
||||
PackageList(this).packages.apply {
|
||||
add(NativePackage())
|
||||
add(NativeVolumeButtonListenerPackage())
|
||||
add(NativeZipArchivePackage())
|
||||
}
|
||||
|
||||
override fun getPackages(): List<ReactPackage> {
|
||||
val packages: MutableList<ReactPackage> = PackageList(this).packages
|
||||
packages.add(VolumeButtonListenerPackage())
|
||||
packages.add(ZipArchivePackage())
|
||||
packages.add(FileManagerPackage())
|
||||
packages.add(EpubUtilPackage())
|
||||
return packages
|
||||
}
|
||||
override fun getJSMainModuleName(): String = "index"
|
||||
|
||||
override fun getJSMainModuleName(): String {
|
||||
return "index"
|
||||
}
|
||||
override fun getUseDeveloperSupport(): Boolean = BuildConfig.DEBUG
|
||||
|
||||
override val isNewArchEnabled: Boolean
|
||||
get() = BuildConfig.IS_NEW_ARCHITECTURE_ENABLED
|
||||
override val isHermesEnabled: Boolean
|
||||
get() = BuildConfig.IS_HERMES_ENABLED
|
||||
})
|
||||
|
||||
override fun getReactNativeHost(): ReactNativeHost {
|
||||
return mReactNativeHost
|
||||
}
|
||||
override val isNewArchEnabled: Boolean = BuildConfig.IS_NEW_ARCHITECTURE_ENABLED
|
||||
override val isHermesEnabled: Boolean = BuildConfig.IS_HERMES_ENABLED
|
||||
}
|
||||
|
||||
override val reactHost: ReactHost
|
||||
get() = getDefaultReactHost(applicationContext, reactNativeHost)
|
||||
|
||||
override fun onCreate() {
|
||||
super.onCreate()
|
||||
SoLoader.init(this, /* native exopackage */false)
|
||||
if (BuildConfig.IS_NEW_ARCHITECTURE_ENABLED) {
|
||||
// If you opted-in for the New Architecture, we load the native entry point for this app.
|
||||
load()
|
||||
}
|
||||
onApplicationCreate(this)
|
||||
loadReactNative(this)
|
||||
ApplicationLifecycleDispatcher.onApplicationCreate(this)
|
||||
}
|
||||
|
||||
override fun onConfigurationChanged(newConfig: Configuration) {
|
||||
super.onConfigurationChanged(newConfig)
|
||||
onConfigurationChanged(this, newConfig)
|
||||
ApplicationLifecycleDispatcher.onConfigurationChanged(this, newConfig)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,16 +1,9 @@
|
||||
package com.rajarsheechatterjee.FileManager
|
||||
package com.rajarsheechatterjee.NativeFile
|
||||
|
||||
import android.app.Activity
|
||||
import android.content.Intent
|
||||
import android.net.Uri
|
||||
import android.os.Build
|
||||
import android.os.Environment
|
||||
import android.provider.DocumentsContract
|
||||
import com.facebook.react.bridge.BaseActivityEventListener
|
||||
import com.facebook.react.bridge.Promise
|
||||
import com.facebook.react.bridge.ReactApplicationContext
|
||||
import com.facebook.react.bridge.ReactContextBaseJavaModule
|
||||
import com.facebook.react.bridge.ReactMethod
|
||||
import com.facebook.react.bridge.ReadableMap
|
||||
import com.facebook.react.bridge.WritableArray
|
||||
import com.facebook.react.bridge.WritableMap
|
||||
@@ -19,6 +12,7 @@ import com.facebook.react.bridge.WritableNativeMap
|
||||
import com.facebook.react.modules.network.CookieJarContainer
|
||||
import com.facebook.react.modules.network.ForwardingCookieHandler
|
||||
import com.facebook.react.modules.network.OkHttpClientProvider
|
||||
import com.lnreader.spec.NativeFileSpec
|
||||
import kotlinx.coroutines.CoroutineScope
|
||||
import kotlinx.coroutines.Dispatchers
|
||||
import kotlinx.coroutines.launch
|
||||
@@ -29,10 +23,8 @@ import okhttp3.JavaNetCookieJar
|
||||
import okhttp3.Request
|
||||
import okhttp3.RequestBody.Companion.toRequestBody
|
||||
import okhttp3.Response
|
||||
import java.io.BufferedReader
|
||||
import java.io.File
|
||||
import java.io.FileOutputStream
|
||||
import java.io.FileReader
|
||||
import java.io.FileWriter
|
||||
import java.io.IOException
|
||||
import java.io.InputStream
|
||||
@@ -41,43 +33,13 @@ import java.io.PushbackInputStream
|
||||
import java.util.zip.GZIPInputStream
|
||||
|
||||
|
||||
class FileManager(context: ReactApplicationContext) :
|
||||
ReactContextBaseJavaModule(context) {
|
||||
override fun getName(): String {
|
||||
return "FileManager"
|
||||
}
|
||||
|
||||
class NativeFile(context: ReactApplicationContext) :
|
||||
NativeFileSpec(context) {
|
||||
private val BUFFER_SIZE = 4096
|
||||
private val okHttpClient = OkHttpClientProvider.createClient()
|
||||
private var _promise: Promise? = null
|
||||
private val coroutineScope = CoroutineScope(Dispatchers.IO)
|
||||
private val activityEventListener = object : BaseActivityEventListener() {
|
||||
override fun onActivityResult(
|
||||
activity: Activity?,
|
||||
requestCode: Int,
|
||||
resultCode: Int,
|
||||
intent: Intent?
|
||||
) {
|
||||
if (requestCode == FOLDER_PICKER_REQUEST) {
|
||||
when (resultCode) {
|
||||
Activity.RESULT_CANCELED -> resolveUri()
|
||||
Activity.RESULT_OK -> {
|
||||
if (intent != null) {
|
||||
intent.data?.let { uri ->
|
||||
val flags =
|
||||
Intent.FLAG_GRANT_READ_URI_PERMISSION or Intent.FLAG_GRANT_WRITE_URI_PERMISSION
|
||||
context.contentResolver.takePersistableUriPermission(uri, flags)
|
||||
resolveUri(uri)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
init {
|
||||
context.addActivityEventListener(activityEventListener)
|
||||
val cookieContainer = okHttpClient.cookieJar as CookieJarContainer
|
||||
val cookieHandler = ForwardingCookieHandler(reactApplicationContext)
|
||||
cookieContainer.setCookieJar(JavaNetCookieJar(cookieHandler))
|
||||
@@ -111,87 +73,51 @@ class FileManager(context: ReactApplicationContext) :
|
||||
?: throw Exception("ENOENT: could not open an output stream for '$filepath'")
|
||||
}
|
||||
|
||||
@ReactMethod
|
||||
fun writeFile(path: String, content: String, promise: Promise) {
|
||||
try {
|
||||
val fw = FileWriter(path)
|
||||
fw.write(content)
|
||||
fw.close()
|
||||
promise.resolve(null)
|
||||
} catch (e: Exception) {
|
||||
promise.reject(e)
|
||||
}
|
||||
override fun writeFile(path: String, content: String) {
|
||||
val fw = FileWriter(path)
|
||||
fw.write(content)
|
||||
fw.close()
|
||||
}
|
||||
|
||||
@ReactMethod(isBlockingSynchronousMethod = true)
|
||||
fun readFile(path: String): String {
|
||||
override fun readFile(path: String): String {
|
||||
return File(path).bufferedReader().readText()
|
||||
}
|
||||
|
||||
@ReactMethod
|
||||
fun copyFile(filepath: String, destPath: String, promise: Promise) {
|
||||
try {
|
||||
copyFileContent(filepath, destPath, null, promise)
|
||||
} catch (e: Exception) {
|
||||
promise.reject(e)
|
||||
}
|
||||
override fun copyFile(filepath: String, destPath: String) {
|
||||
copyFileContent(filepath, destPath)
|
||||
}
|
||||
|
||||
@ReactMethod
|
||||
fun moveFile(filepath: String, destPath: String, promise: Promise) {
|
||||
try {
|
||||
val inFile = File(filepath)
|
||||
copyFileContent(filepath, destPath, { inFile.delete() }, promise)
|
||||
} catch (e: Exception) {
|
||||
promise.reject(e)
|
||||
}
|
||||
override fun moveFile(filepath: String, destPath: String) {
|
||||
val inFile = File(filepath)
|
||||
copyFileContent(filepath, destPath, { inFile.delete() })
|
||||
}
|
||||
|
||||
private fun copyFileContent(
|
||||
filepath: String,
|
||||
destPath: String,
|
||||
onDone: (() -> Unit)? = null,
|
||||
promise: Promise? = null
|
||||
) {
|
||||
coroutineScope.launch(Dispatchers.IO) {
|
||||
val inputStream = getInputStream(filepath)
|
||||
val outputStream = getOutputStream(destPath)
|
||||
val buffer = ByteArray(1024)
|
||||
var length: Int
|
||||
while (inputStream.read(buffer).also { length = it } > 0) {
|
||||
outputStream.write(buffer, 0, length)
|
||||
Thread.yield()
|
||||
}
|
||||
inputStream.close()
|
||||
outputStream.close()
|
||||
if (onDone != null) {
|
||||
onDone()
|
||||
}
|
||||
promise?.resolve(null)
|
||||
val inputStream = getInputStream(filepath)
|
||||
val outputStream = getOutputStream(destPath)
|
||||
val buffer = ByteArray(BUFFER_SIZE)
|
||||
var length: Int
|
||||
while (inputStream.read(buffer).also { length = it } > 0) {
|
||||
outputStream.write(buffer, 0, length)
|
||||
}
|
||||
inputStream.close()
|
||||
outputStream.close()
|
||||
if (onDone != null) {
|
||||
onDone()
|
||||
}
|
||||
}
|
||||
|
||||
@ReactMethod
|
||||
fun exists(filepath: String, promise: Promise) {
|
||||
try {
|
||||
val file = File(filepath)
|
||||
promise.resolve(file.exists())
|
||||
} catch (e: Exception) {
|
||||
promise.reject(e)
|
||||
}
|
||||
}
|
||||
override fun exists(filepath: String) = File(filepath).exists()
|
||||
|
||||
@ReactMethod
|
||||
fun mkdir(filepath: String, promise: Promise) {
|
||||
try {
|
||||
val file = File(filepath)
|
||||
if (!file.exists()) {
|
||||
val created = file.mkdirs()
|
||||
if (!created) throw Exception("Directory could not be created")
|
||||
}
|
||||
promise.resolve(null)
|
||||
} catch (e: Exception) {
|
||||
promise.reject(e)
|
||||
override fun mkdir(filepath: String) {
|
||||
val file = File(filepath)
|
||||
if (!file.exists()) {
|
||||
val created = file.mkdirs()
|
||||
if (!created) throw Exception("Directory could not be created")
|
||||
}
|
||||
}
|
||||
|
||||
@@ -204,90 +130,38 @@ class FileManager(context: ReactApplicationContext) :
|
||||
fileOrDirectory.delete()
|
||||
}
|
||||
|
||||
@ReactMethod
|
||||
fun unlink(filepath: String, promise: Promise) {
|
||||
try {
|
||||
val file = File(filepath)
|
||||
if (!file.exists()) throw Exception("File does not exist")
|
||||
deleteRecursive(file)
|
||||
promise.resolve(null)
|
||||
} catch (e: Exception) {
|
||||
promise.reject(e)
|
||||
}
|
||||
override fun unlink(filepath: String) {
|
||||
val file = File(filepath)
|
||||
if (!file.exists()) throw Exception("File does not exist")
|
||||
deleteRecursive(file)
|
||||
}
|
||||
|
||||
@ReactMethod
|
||||
fun readDir(directory: String, promise: Promise) {
|
||||
try {
|
||||
val file = File(directory)
|
||||
if (!file.exists()) throw Exception("Folder does not exist")
|
||||
val files = file.listFiles()
|
||||
val fileMaps: WritableArray = WritableNativeArray()
|
||||
for (childFile in files!!) {
|
||||
val fileMap: WritableMap = WritableNativeMap()
|
||||
fileMap.putString("name", childFile.name)
|
||||
fileMap.putString("path", childFile.absolutePath)
|
||||
fileMap.putBoolean("isDirectory", childFile.isDirectory)
|
||||
fileMaps.pushMap(fileMap)
|
||||
}
|
||||
promise.resolve(fileMaps)
|
||||
} catch (e: Exception) {
|
||||
promise.reject(e)
|
||||
override fun readDir(directory: String): WritableArray {
|
||||
val file = File(directory)
|
||||
if (!file.exists()) throw Exception("Folder does not exist")
|
||||
val files = file.listFiles()
|
||||
val fileMaps: WritableArray = WritableNativeArray()
|
||||
for (childFile in files!!) {
|
||||
val fileMap: WritableMap = WritableNativeMap()
|
||||
fileMap.putString("name", childFile.name)
|
||||
fileMap.putString("path", childFile.absolutePath)
|
||||
fileMap.putBoolean("isDirectory", childFile.isDirectory)
|
||||
fileMaps.pushMap(fileMap)
|
||||
}
|
||||
}
|
||||
|
||||
override fun getConstants(): Map<String, Any> {
|
||||
val constants: MutableMap<String, Any> = HashMap()
|
||||
val externalDirectory = this.reactApplicationContext.getExternalFilesDir(null)
|
||||
if (externalDirectory != null) {
|
||||
constants["ExternalDirectoryPath"] = externalDirectory.absolutePath
|
||||
}
|
||||
val externalCachesDirectory = this.reactApplicationContext.externalCacheDir
|
||||
if (externalCachesDirectory != null) {
|
||||
constants["ExternalCachesDirectoryPath"] = externalCachesDirectory.absolutePath
|
||||
}
|
||||
return constants
|
||||
}
|
||||
|
||||
private fun resolveUri(uri: Uri? = null) {
|
||||
_promise?.let { promise ->
|
||||
try {
|
||||
if (uri == null) promise.resolve(null)
|
||||
else {
|
||||
val docId = DocumentsContract.getTreeDocumentId(uri)
|
||||
val split =
|
||||
docId.split(":".toRegex()).dropLastWhile { it.isEmpty() }.toTypedArray()
|
||||
promise.resolve(
|
||||
Environment.getExternalStorageDirectory().toString() + "/" + split[1]
|
||||
)
|
||||
}
|
||||
} catch (e: Exception) {
|
||||
promise.reject(e)
|
||||
} finally {
|
||||
_promise = null
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@ReactMethod
|
||||
fun pickFolder(promise: Promise) {
|
||||
if (_promise != null) promise.reject(Exception("Can not perform action"))
|
||||
_promise = promise
|
||||
val intent = Intent(Intent.ACTION_OPEN_DOCUMENT_TREE)
|
||||
currentActivity?.startActivityForResult(intent, FOLDER_PICKER_REQUEST)
|
||||
return fileMaps
|
||||
}
|
||||
|
||||
private fun decompressStream(input: InputStream?): InputStream {
|
||||
val pb = PushbackInputStream(input, 2)
|
||||
val signature = ByteArray(2)
|
||||
val len = pb.read(signature)
|
||||
if(len == -1) return pb;
|
||||
pb.unread(signature, 0, len)
|
||||
return if (signature[0] == 0x1f.toByte() && signature[1] == 0x8b.toByte())
|
||||
GZIPInputStream(pb) else pb
|
||||
}
|
||||
|
||||
@ReactMethod
|
||||
fun downloadFile(
|
||||
override fun downloadFile(
|
||||
url: String,
|
||||
destPath: String,
|
||||
method: String,
|
||||
@@ -338,7 +212,16 @@ class FileManager(context: ReactApplicationContext) :
|
||||
}
|
||||
}
|
||||
|
||||
companion object {
|
||||
const val FOLDER_PICKER_REQUEST = 1
|
||||
override fun getTypedExportedConstants(): MutableMap<String, Any> {
|
||||
val constants: MutableMap<String, Any> = HashMap()
|
||||
val externalDirectory = this.reactApplicationContext.getExternalFilesDir(null)
|
||||
if (externalDirectory != null) {
|
||||
constants["ExternalDirectoryPath"] = externalDirectory.absolutePath
|
||||
}
|
||||
val externalCachesDirectory = this.reactApplicationContext.externalCacheDir
|
||||
if (externalCachesDirectory != null) {
|
||||
constants["ExternalCachesDirectoryPath"] = externalCachesDirectory.absolutePath
|
||||
}
|
||||
return constants
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,30 @@
|
||||
package com.rajarsheechatterjee.NativeFile
|
||||
|
||||
import com.facebook.react.BaseReactPackage
|
||||
import com.facebook.react.bridge.NativeModule
|
||||
import com.facebook.react.bridge.ReactApplicationContext
|
||||
import com.facebook.react.module.model.ReactModuleInfo
|
||||
import com.facebook.react.module.model.ReactModuleInfoProvider
|
||||
import com.lnreader.spec.NativeFileSpec
|
||||
|
||||
class NativePackage : BaseReactPackage() {
|
||||
override fun getModule(name: String, reactContext: ReactApplicationContext): NativeModule? =
|
||||
if (name == NativeFileSpec.NAME) {
|
||||
NativeFile(reactContext)
|
||||
} else {
|
||||
null
|
||||
}
|
||||
|
||||
override fun getReactModuleInfoProvider() = ReactModuleInfoProvider {
|
||||
mapOf(
|
||||
NativeFileSpec.NAME to ReactModuleInfo(
|
||||
NativeFileSpec.NAME,
|
||||
NativeFileSpec.NAME,
|
||||
canOverrideExistingModule = false,
|
||||
needsEagerInit = false,
|
||||
isCxxModule = false,
|
||||
isTurboModule = true
|
||||
)
|
||||
)
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,32 @@
|
||||
package com.rajarsheechatterjee.NativeVolumeButtonListener
|
||||
|
||||
import com.facebook.react.bridge.ReactApplicationContext
|
||||
import com.facebook.react.modules.core.DeviceEventManagerModule
|
||||
import com.lnreader.spec.NativeVolumeButtonListenerSpec
|
||||
|
||||
class NativeVolumeButtonListener(appContext: ReactApplicationContext) :
|
||||
NativeVolumeButtonListenerSpec(appContext) {
|
||||
init {
|
||||
NativeVolumeButtonListener.appContext = appContext
|
||||
}
|
||||
|
||||
override fun addListener(eventName: String?) {
|
||||
isActive = true
|
||||
}
|
||||
|
||||
override fun removeListeners(count: Double) {
|
||||
isActive = false
|
||||
}
|
||||
|
||||
companion object {
|
||||
lateinit var appContext: ReactApplicationContext
|
||||
var isActive = false
|
||||
|
||||
fun sendEvent(up: Boolean) {
|
||||
if (isActive) {
|
||||
appContext.getJSModule(DeviceEventManagerModule.RCTDeviceEventEmitter::class.java)
|
||||
.emit(if (up) "VolumeUp" else "VolumeDown", null)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,30 @@
|
||||
package com.rajarsheechatterjee.NativeVolumeButtonListener
|
||||
|
||||
import com.facebook.react.BaseReactPackage
|
||||
import com.facebook.react.bridge.NativeModule
|
||||
import com.facebook.react.bridge.ReactApplicationContext
|
||||
import com.facebook.react.module.model.ReactModuleInfo
|
||||
import com.facebook.react.module.model.ReactModuleInfoProvider
|
||||
import com.lnreader.spec.NativeVolumeButtonListenerSpec
|
||||
|
||||
class NativeVolumeButtonListenerPackage : BaseReactPackage() {
|
||||
override fun getModule(name: String, reactContext: ReactApplicationContext): NativeModule? =
|
||||
if (name == NativeVolumeButtonListenerSpec.NAME) {
|
||||
NativeVolumeButtonListener(reactContext)
|
||||
} else {
|
||||
null
|
||||
}
|
||||
|
||||
override fun getReactModuleInfoProvider() = ReactModuleInfoProvider {
|
||||
mapOf(
|
||||
NativeVolumeButtonListenerSpec.NAME to ReactModuleInfo(
|
||||
NativeVolumeButtonListenerSpec.NAME,
|
||||
NativeVolumeButtonListenerSpec.NAME,
|
||||
canOverrideExistingModule = false,
|
||||
needsEagerInit = false,
|
||||
isCxxModule = false,
|
||||
isTurboModule = true
|
||||
)
|
||||
)
|
||||
}
|
||||
}
|
||||
@@ -1,10 +1,10 @@
|
||||
package com.rajarsheechatterjee.ZipArchive
|
||||
package com.rajarsheechatterjee.NativeZipArchive
|
||||
|
||||
import com.facebook.react.bridge.Promise
|
||||
import com.facebook.react.bridge.ReactApplicationContext
|
||||
import com.facebook.react.bridge.ReactContextBaseJavaModule
|
||||
import com.facebook.react.bridge.ReactMethod
|
||||
import com.facebook.react.bridge.ReadableMap
|
||||
import com.lnreader.spec.NativeZipArchiveSpec
|
||||
import java.io.File
|
||||
import java.io.FileOutputStream
|
||||
import java.net.HttpURLConnection
|
||||
@@ -14,13 +14,9 @@ import java.util.zip.ZipFile
|
||||
import java.util.zip.ZipInputStream
|
||||
import java.util.zip.ZipOutputStream
|
||||
|
||||
class ZipArchive(context: ReactApplicationContext) : ReactContextBaseJavaModule(context) {
|
||||
override fun getName(): String {
|
||||
return "ZipArchive"
|
||||
}
|
||||
|
||||
class NativeZipArchive(context: ReactApplicationContext) : NativeZipArchiveSpec(context) {
|
||||
@ReactMethod
|
||||
fun unzip(sourceFilePath: String, distDirPath: String, promise: Promise) {
|
||||
override fun unzip(sourceFilePath: String, distDirPath: String, promise: Promise) {
|
||||
Thread {
|
||||
try {
|
||||
ZipFile(sourceFilePath).use { zis ->
|
||||
@@ -30,6 +26,7 @@ class ZipArchive(context: ReactApplicationContext) : ReactContextBaseJavaModule(
|
||||
zis.getInputStream(zipEntry).use { inputStream ->
|
||||
FileOutputStream(newFile).use { fos -> inputStream.copyTo(fos, 4096) }
|
||||
}
|
||||
Thread.yield()
|
||||
}
|
||||
}
|
||||
promise.resolve(null)
|
||||
@@ -38,9 +35,23 @@ class ZipArchive(context: ReactApplicationContext) : ReactContextBaseJavaModule(
|
||||
}
|
||||
}.start()
|
||||
}
|
||||
|
||||
@ReactMethod
|
||||
override fun zip(sourceDirPath: String, zipFilePath: String, promise: Promise) {
|
||||
Thread {
|
||||
try {
|
||||
FileOutputStream(zipFilePath).use { fos ->
|
||||
ZipOutputStream(fos).use { zos -> zipProcess(sourceDirPath, zos) }
|
||||
}
|
||||
promise.resolve(null)
|
||||
} catch (e: Exception) {
|
||||
promise.reject(e)
|
||||
}
|
||||
}.start()
|
||||
}
|
||||
|
||||
@ReactMethod
|
||||
fun remoteUnzip(
|
||||
override fun remoteUnzip(
|
||||
distDirPath: String,
|
||||
urlString: String,
|
||||
headers: ReadableMap,
|
||||
@@ -62,6 +73,7 @@ class ZipArchive(context: ReactApplicationContext) : ReactContextBaseJavaModule(
|
||||
val newFile = File(distDirPath, zipEntry.name)
|
||||
newFile.parentFile?.mkdirs()
|
||||
FileOutputStream(newFile).use { fos -> zis.copyTo(fos, 4096) }
|
||||
Thread.yield()
|
||||
}
|
||||
}
|
||||
if (connection.responseCode == 200) {
|
||||
@@ -84,12 +96,16 @@ class ZipArchive(context: ReactApplicationContext) : ReactContextBaseJavaModule(
|
||||
file.absolutePath.removePrefix(sourceDir.absolutePath).removePrefix("/")
|
||||
val entry = ZipEntry("$zipFileName${(if (file.isDirectory) "/" else "")}")
|
||||
zos.putNextEntry(entry)
|
||||
file.inputStream().use { fis -> fis.copyTo(zos, 4096) }
|
||||
file.inputStream().use { fis ->
|
||||
fis.copyTo(zos, 4096)
|
||||
fis.close()
|
||||
}
|
||||
Thread.yield()
|
||||
}
|
||||
}
|
||||
|
||||
@ReactMethod
|
||||
fun remoteZip(
|
||||
override fun remoteZip(
|
||||
sourceDirPath: String,
|
||||
urlString: String,
|
||||
headers: ReadableMap,
|
||||
@@ -0,0 +1,30 @@
|
||||
package com.rajarsheechatterjee.NativeZipArchive
|
||||
|
||||
import com.facebook.react.BaseReactPackage
|
||||
import com.facebook.react.bridge.NativeModule
|
||||
import com.facebook.react.bridge.ReactApplicationContext
|
||||
import com.facebook.react.module.model.ReactModuleInfo
|
||||
import com.facebook.react.module.model.ReactModuleInfoProvider
|
||||
import com.lnreader.spec.NativeZipArchiveSpec
|
||||
|
||||
class NativeZipArchivePackage : BaseReactPackage() {
|
||||
override fun getModule(name: String, reactContext: ReactApplicationContext): NativeModule? =
|
||||
if (name == NativeZipArchiveSpec.NAME) {
|
||||
NativeZipArchive(reactContext)
|
||||
} else {
|
||||
null
|
||||
}
|
||||
|
||||
override fun getReactModuleInfoProvider() = ReactModuleInfoProvider {
|
||||
mapOf(
|
||||
NativeZipArchiveSpec.NAME to ReactModuleInfo(
|
||||
NativeZipArchiveSpec.NAME,
|
||||
NativeZipArchiveSpec.NAME,
|
||||
canOverrideExistingModule = false,
|
||||
needsEagerInit = false,
|
||||
isCxxModule = false,
|
||||
isTurboModule = true
|
||||
)
|
||||
)
|
||||
}
|
||||
}
|
||||
@@ -1,86 +0,0 @@
|
||||
package com.rajarsheechatterjee.VolumeButtonListener
|
||||
|
||||
import com.facebook.react.bridge.Arguments
|
||||
import com.facebook.react.bridge.ReactApplicationContext
|
||||
import com.facebook.react.bridge.ReactContextBaseJavaModule
|
||||
import com.facebook.react.bridge.ReactMethod
|
||||
import com.facebook.react.modules.core.DeviceEventManagerModule
|
||||
|
||||
class VolumeButtonListener(appContext: ReactApplicationContext) :
|
||||
ReactContextBaseJavaModule(appContext) {
|
||||
init {
|
||||
VolumeButtonListener.appContext = appContext
|
||||
}
|
||||
|
||||
@ReactMethod
|
||||
fun connect() {
|
||||
isActive = true
|
||||
}
|
||||
|
||||
@ReactMethod
|
||||
fun disconnect() {
|
||||
isActive = false
|
||||
}
|
||||
|
||||
@ReactMethod
|
||||
fun pause() {
|
||||
isActive = false
|
||||
}
|
||||
|
||||
@ReactMethod
|
||||
fun unpause() {
|
||||
isActive = true
|
||||
}
|
||||
|
||||
@ReactMethod
|
||||
fun preventDefault() {
|
||||
prevent = true
|
||||
}
|
||||
|
||||
@ReactMethod
|
||||
fun noPreventDefault() {
|
||||
prevent = false
|
||||
}
|
||||
|
||||
override fun getName(): String {
|
||||
return "VolumeButtonListener"
|
||||
}
|
||||
|
||||
@ReactMethod
|
||||
fun addListener(eventName: String?) {
|
||||
// Keep: Required for RN built in Event Emitter Calls.
|
||||
}
|
||||
|
||||
@ReactMethod
|
||||
fun removeListeners(count: Int?) {
|
||||
// Keep: Required for RN built in Event Emitter Calls.
|
||||
}
|
||||
|
||||
companion object {
|
||||
lateinit var appContext: ReactApplicationContext
|
||||
var isActive = false
|
||||
private set
|
||||
var prevent = false
|
||||
fun sendEvent(up: Boolean) {
|
||||
if (isActive) {
|
||||
val args = Arguments.createMap()
|
||||
if (up) appContext.getJSModule(
|
||||
DeviceEventManagerModule.RCTDeviceEventEmitter::class.java
|
||||
).emit(
|
||||
"VolumeUp",
|
||||
args
|
||||
) else appContext.getJSModule(
|
||||
DeviceEventManagerModule.RCTDeviceEventEmitter::class.java
|
||||
).emit("VolumeDown", args)
|
||||
}
|
||||
}
|
||||
|
||||
fun down() {
|
||||
sendEvent(false)
|
||||
}
|
||||
|
||||
fun up() {
|
||||
sendEvent(true)
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,16 +0,0 @@
|
||||
package com.rajarsheechatterjee.VolumeButtonListener
|
||||
|
||||
import android.view.View
|
||||
import com.facebook.react.ReactPackage
|
||||
import com.facebook.react.bridge.NativeModule
|
||||
import com.facebook.react.bridge.ReactApplicationContext
|
||||
import com.facebook.react.uimanager.ReactShadowNode
|
||||
import com.facebook.react.uimanager.ViewManager
|
||||
|
||||
class VolumeButtonListenerPackage : ReactPackage {
|
||||
override fun createNativeModules(reactContext: ReactApplicationContext): MutableList<NativeModule> = listOf(VolumeButtonListener(reactContext)).toMutableList()
|
||||
|
||||
override fun createViewManagers(
|
||||
reactContext: ReactApplicationContext
|
||||
): MutableList<ViewManager<View, ReactShadowNode<*>>> = mutableListOf()
|
||||
}
|
||||
@@ -1,17 +0,0 @@
|
||||
package com.rajarsheechatterjee.ZipArchive
|
||||
|
||||
import android.view.View
|
||||
import com.facebook.react.ReactPackage
|
||||
import com.facebook.react.bridge.NativeModule
|
||||
import com.facebook.react.bridge.ReactApplicationContext
|
||||
import com.facebook.react.uimanager.ReactShadowNode
|
||||
import com.facebook.react.uimanager.ViewManager
|
||||
|
||||
class ZipArchivePackage : ReactPackage {
|
||||
override fun createNativeModules(context: ReactApplicationContext): MutableList<NativeModule> =
|
||||
listOf(ZipArchive(context)).toMutableList()
|
||||
|
||||
override fun createViewManagers(
|
||||
reactContext: ReactApplicationContext
|
||||
): MutableList<ViewManager<View, ReactShadowNode<*>>> = mutableListOf()
|
||||
}
|
||||
@@ -0,0 +1,17 @@
|
||||
cmake_minimum_required(VERSION 3.13)
|
||||
|
||||
# Define the library name here.
|
||||
project(appmodules)
|
||||
|
||||
# This file includes all the necessary to let you build your React Native application
|
||||
include(${REACT_ANDROID_DIR}/cmake-utils/ReactNative-application.cmake)
|
||||
|
||||
# Define where the additional source code lives. We need to crawl back the jni, main, src, app, android folders
|
||||
target_sources(${CMAKE_PROJECT_NAME} PRIVATE
|
||||
../../../../../shared/NativeEpub.cpp
|
||||
../../../../../shared/Epub.cpp
|
||||
../../../../../shared/pugixml.cpp
|
||||
)
|
||||
|
||||
# Define where CMake can find the additional header files. We need to crawl back the jni, main, src, app, android folders
|
||||
target_include_directories(${CMAKE_PROJECT_NAME} PUBLIC ../../../../../shared)
|
||||
@@ -0,0 +1,138 @@
|
||||
/*
|
||||
* Copyright (c) Meta Platforms, Inc. and affiliates.
|
||||
*
|
||||
* This source code is licensed under the MIT license found in the
|
||||
* LICENSE file in the root directory of this source tree.
|
||||
*/
|
||||
|
||||
// This C++ file is part of the default configuration used by apps and is placed
|
||||
// inside react-native to encapsulate it from user space (so you won't need to
|
||||
// touch C++/Cmake code at all on Android).
|
||||
//
|
||||
// If you wish to customize it (because you want to manually link a C++ library
|
||||
// or pass a custom compilation flag) you can:
|
||||
//
|
||||
// 1. Copy this CMake file inside the `android/app/src/main/jni` folder of your
|
||||
// project
|
||||
// 2. Copy the OnLoad.cpp (in this same folder) file inside the same folder as
|
||||
// above.
|
||||
// 3. Extend your `android/app/build.gradle` as follows
|
||||
//
|
||||
// android {
|
||||
// // Other config here...
|
||||
// externalNativeBuild {
|
||||
// cmake {
|
||||
// path "src/main/jni/CMakeLists.txt"
|
||||
// }
|
||||
// }
|
||||
// }
|
||||
|
||||
#include <DefaultComponentsRegistry.h>
|
||||
#include <DefaultTurboModuleManagerDelegate.h>
|
||||
#include <autolinking.h>
|
||||
#include <fbjni/fbjni.h>
|
||||
#include <react/renderer/componentregistry/ComponentDescriptorProviderRegistry.h>
|
||||
#include <FBReactNativeSpec.h>
|
||||
#include <NativeEpub.hpp>
|
||||
|
||||
#ifdef REACT_NATIVE_APP_CODEGEN_HEADER
|
||||
#include REACT_NATIVE_APP_CODEGEN_HEADER
|
||||
#endif
|
||||
#ifdef REACT_NATIVE_APP_COMPONENT_DESCRIPTORS_HEADER
|
||||
#include REACT_NATIVE_APP_COMPONENT_DESCRIPTORS_HEADER
|
||||
#endif
|
||||
|
||||
namespace facebook::react
|
||||
{
|
||||
|
||||
void registerComponents(
|
||||
std::shared_ptr<const ComponentDescriptorProviderRegistry> registry)
|
||||
{
|
||||
// Custom Fabric Components go here. You can register custom
|
||||
// components coming from your App or from 3rd party libraries here.
|
||||
//
|
||||
// providerRegistry->add(concreteComponentDescriptorProvider<
|
||||
// MyComponentDescriptor>());
|
||||
|
||||
// We link app local components if available
|
||||
#ifdef REACT_NATIVE_APP_COMPONENT_REGISTRATION
|
||||
REACT_NATIVE_APP_COMPONENT_REGISTRATION(registry);
|
||||
#endif
|
||||
|
||||
// And we fallback to the components autolinked
|
||||
autolinking_registerProviders(registry);
|
||||
}
|
||||
|
||||
std::shared_ptr<TurboModule> cxxModuleProvider(
|
||||
const std::string &name,
|
||||
const std::shared_ptr<CallInvoker> &jsInvoker)
|
||||
{
|
||||
// Here you can provide your CXX Turbo Modules coming from
|
||||
// either your application or from external libraries. The approach to follow
|
||||
// is similar to the following (for a module called `NativeCxxModuleExample`):
|
||||
//
|
||||
// if (name == NativeCxxModuleExample::kModuleName) {
|
||||
// return std::make_shared<NativeCxxModuleExample>(jsInvoker);
|
||||
// }
|
||||
|
||||
if (name == NativeEpub::kModuleName)
|
||||
{
|
||||
return std::make_shared<NativeEpub>(jsInvoker);
|
||||
}
|
||||
|
||||
// And we fallback to the CXX module providers autolinked
|
||||
return autolinking_cxxModuleProvider(name, jsInvoker);
|
||||
}
|
||||
|
||||
std::shared_ptr<TurboModule> javaModuleProvider(
|
||||
const std::string &name,
|
||||
const JavaTurboModule::InitParams ¶ms)
|
||||
{
|
||||
// Here you can provide your own module provider for TurboModules coming from
|
||||
// either your application or from external libraries. The approach to follow
|
||||
// is similar to the following (for a library called `samplelibrary`):
|
||||
//
|
||||
// auto module = samplelibrary_ModuleProvider(name, params);
|
||||
// if (module != nullptr) {
|
||||
// return module;
|
||||
// }
|
||||
// return FBReactNativeSpec_ModuleProvider(name, params);
|
||||
|
||||
// We link app local modules if available
|
||||
#ifdef REACT_NATIVE_APP_MODULE_PROVIDER
|
||||
auto module = REACT_NATIVE_APP_MODULE_PROVIDER(name, params);
|
||||
if (module != nullptr)
|
||||
{
|
||||
return module;
|
||||
}
|
||||
#endif
|
||||
|
||||
// We first try to look up core modules
|
||||
if (auto module = FBReactNativeSpec_ModuleProvider(name, params))
|
||||
{
|
||||
return module;
|
||||
}
|
||||
|
||||
// And we fallback to the module providers autolinked
|
||||
if (auto module = autolinking_ModuleProvider(name, params))
|
||||
{
|
||||
return module;
|
||||
}
|
||||
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
} // namespace facebook::react
|
||||
|
||||
JNIEXPORT jint JNICALL JNI_OnLoad(JavaVM *vm, void *)
|
||||
{
|
||||
return facebook::jni::initialize(vm, []
|
||||
{
|
||||
facebook::react::DefaultTurboModuleManagerDelegate::cxxModuleProvider =
|
||||
&facebook::react::cxxModuleProvider;
|
||||
facebook::react::DefaultTurboModuleManagerDelegate::javaModuleProvider =
|
||||
&facebook::react::javaModuleProvider;
|
||||
facebook::react::DefaultComponentsRegistry::
|
||||
registerComponentDescriptorsFromEntryPoint =
|
||||
&facebook::react::registerComponents; });
|
||||
}
|
||||
@@ -1,4 +1,4 @@
|
||||
<?xml version="1.0" encoding="UTF-8" standalone="yes"?>
|
||||
<resources>
|
||||
<string name="app_name">LNReader</string>
|
||||
</resources>
|
||||
</resources>
|
||||
|
||||
@@ -1,20 +0,0 @@
|
||||
/**
|
||||
* Copyright (c) Meta Platforms, Inc. and affiliates.
|
||||
*
|
||||
* <p>This source code is licensed under the MIT license found in the LICENSE file in the root
|
||||
* directory of this source tree.
|
||||
*/
|
||||
package com.rajarsheechatterjee.LNReader;
|
||||
|
||||
import android.content.Context;
|
||||
import com.facebook.react.ReactInstanceManager;
|
||||
|
||||
/**
|
||||
* Class responsible of loading Flipper inside your React Native application. This is the release
|
||||
* flavor of it so it's empty as we don't want to load Flipper.
|
||||
*/
|
||||
public class ReactNativeFlipper {
|
||||
public static void initializeFlipper(Context context, ReactInstanceManager reactInstanceManager) {
|
||||
// Do nothing as we don't want to initialize Flipper on Release.
|
||||
}
|
||||
}
|
||||
@@ -1,16 +1,11 @@
|
||||
import org.apache.tools.ant.taskdefs.condition.Os
|
||||
|
||||
// Top-level build file where you can add configuration options common to all sub-projects/modules.
|
||||
|
||||
buildscript {
|
||||
ext {
|
||||
buildToolsVersion = "33.0.0"
|
||||
minSdkVersion = 21
|
||||
compileSdkVersion = 33
|
||||
targetSdkVersion = 33
|
||||
kotlinVersion = "1.8.10"
|
||||
// We use NDK 23 which has both M1 support and is the side-by-side NDK version from AGP.
|
||||
ndkVersion = "23.1.7779620"
|
||||
buildToolsVersion = "36.0.0"
|
||||
minSdkVersion = 24
|
||||
compileSdkVersion = 36
|
||||
targetSdkVersion = 36
|
||||
ndkVersion = "27.1.12297006"
|
||||
kotlinVersion = "2.1.20"
|
||||
}
|
||||
repositories {
|
||||
google()
|
||||
@@ -19,6 +14,8 @@ buildscript {
|
||||
dependencies {
|
||||
classpath("com.android.tools.build:gradle")
|
||||
classpath("com.facebook.react:react-native-gradle-plugin")
|
||||
classpath "org.jetbrains.kotlin:kotlin-gradle-plugin:$kotlinVersion"
|
||||
classpath("org.jetbrains.kotlin:kotlin-gradle-plugin")
|
||||
}
|
||||
}
|
||||
|
||||
apply plugin: "com.facebook.react.rootproject"
|
||||
@@ -12,7 +12,7 @@
|
||||
|
||||
# Default value: -Xmx512m -XX:MaxMetaspaceSize=256m
|
||||
|
||||
org.gradle.jvmargs=-Xmx2048m -XX:MaxMetaspaceSize=512m
|
||||
org.gradle.jvmargs=-Xmx4608m -XX:MaxMetaspaceSize=512m
|
||||
|
||||
# When configured, Gradle will run in incubating parallel mode.
|
||||
# This option should only be used with decoupled projects. More details, visit
|
||||
@@ -36,7 +36,7 @@ reactNativeArchitectures=armeabi-v7a,arm64-v8a,x86,x86_64
|
||||
# your application. You should enable this flag either if you want
|
||||
# to write custom TurboModules/Fabric components OR use libraries that
|
||||
# are providing them.
|
||||
newArchEnabled=false
|
||||
newArchEnabled=true
|
||||
|
||||
# Use this property to enable or disable the Hermes JS engine.
|
||||
# If set to false, you will be using JSC instead.
|
||||
@@ -49,3 +49,8 @@ expo.webp.enabled=false
|
||||
# Enable animated webp support (~3.4 MB increase)
|
||||
# Disabled by default because iOS doesn't support animated webp
|
||||
expo.webp.animated=false
|
||||
|
||||
# Use this property to enable edge-to-edge display support.
|
||||
# This allows your app to draw behind system bars for an immersive UI.
|
||||
# Note: Only works with ReactActivity and should not be used with custom Activity.
|
||||
edgeToEdgeEnabled=false
|
||||
@@ -1,6 +1,7 @@
|
||||
distributionBase=GRADLE_USER_HOME
|
||||
distributionPath=wrapper/dists
|
||||
distributionUrl=https\://services.gradle.org/distributions/gradle-8.0.1-all.zip
|
||||
distributionUrl=https\://services.gradle.org/distributions/gradle-8.14.3-bin.zip
|
||||
networkTimeout=10000
|
||||
validateDistributionUrl=true
|
||||
zipStoreBase=GRADLE_USER_HOME
|
||||
zipStorePath=wrapper/dists
|
||||
|
||||
@@ -15,6 +15,8 @@
|
||||
# See the License for the specific language governing permissions and
|
||||
# limitations under the License.
|
||||
#
|
||||
# SPDX-License-Identifier: Apache-2.0
|
||||
#
|
||||
|
||||
##############################################################################
|
||||
#
|
||||
@@ -55,7 +57,7 @@
|
||||
# Darwin, MinGW, and NonStop.
|
||||
#
|
||||
# (3) This script is generated from the Groovy template
|
||||
# https://github.com/gradle/gradle/blob/HEAD/subprojects/plugins/src/main/resources/org/gradle/api/internal/plugins/unixStartScript.txt
|
||||
# https://github.com/gradle/gradle/blob/HEAD/platforms/jvm/plugins-application/src/main/resources/org/gradle/api/internal/plugins/unixStartScript.txt
|
||||
# within the Gradle project.
|
||||
#
|
||||
# You can find Gradle at https://github.com/gradle/gradle/.
|
||||
@@ -83,10 +85,8 @@ done
|
||||
# This is normally unused
|
||||
# shellcheck disable=SC2034
|
||||
APP_BASE_NAME=${0##*/}
|
||||
APP_HOME=$( cd "${APP_HOME:-./}" && pwd -P ) || exit
|
||||
|
||||
# Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script.
|
||||
DEFAULT_JVM_OPTS='"-Xmx64m" "-Xms64m"'
|
||||
# Discard cd standard output in case $CDPATH is set (https://github.com/gradle/gradle/issues/25036)
|
||||
APP_HOME=$( cd -P "${APP_HOME:-./}" > /dev/null && printf '%s\n' "$PWD" ) || exit
|
||||
|
||||
# Use the maximum available, or set MAX_FD != -1 to use that value.
|
||||
MAX_FD=maximum
|
||||
@@ -114,7 +114,7 @@ case "$( uname )" in #(
|
||||
NONSTOP* ) nonstop=true ;;
|
||||
esac
|
||||
|
||||
CLASSPATH=$APP_HOME/gradle/wrapper/gradle-wrapper.jar
|
||||
CLASSPATH="\\\"\\\""
|
||||
|
||||
|
||||
# Determine the Java command to use to start the JVM.
|
||||
@@ -133,10 +133,13 @@ location of your Java installation."
|
||||
fi
|
||||
else
|
||||
JAVACMD=java
|
||||
which java >/dev/null 2>&1 || die "ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH.
|
||||
if ! command -v java >/dev/null 2>&1
|
||||
then
|
||||
die "ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH.
|
||||
|
||||
Please set the JAVA_HOME variable in your environment to match the
|
||||
location of your Java installation."
|
||||
fi
|
||||
fi
|
||||
|
||||
# Increase the maximum file descriptors if we can.
|
||||
@@ -144,7 +147,7 @@ if ! "$cygwin" && ! "$darwin" && ! "$nonstop" ; then
|
||||
case $MAX_FD in #(
|
||||
max*)
|
||||
# In POSIX sh, ulimit -H is undefined. That's why the result is checked to see if it worked.
|
||||
# shellcheck disable=SC3045
|
||||
# shellcheck disable=SC2039,SC3045
|
||||
MAX_FD=$( ulimit -H -n ) ||
|
||||
warn "Could not query maximum file descriptor limit"
|
||||
esac
|
||||
@@ -152,7 +155,7 @@ if ! "$cygwin" && ! "$darwin" && ! "$nonstop" ; then
|
||||
'' | soft) :;; #(
|
||||
*)
|
||||
# In POSIX sh, ulimit -n is undefined. That's why the result is checked to see if it worked.
|
||||
# shellcheck disable=SC3045
|
||||
# shellcheck disable=SC2039,SC3045
|
||||
ulimit -n "$MAX_FD" ||
|
||||
warn "Could not set maximum file descriptor limit to $MAX_FD"
|
||||
esac
|
||||
@@ -197,16 +200,20 @@ if "$cygwin" || "$msys" ; then
|
||||
done
|
||||
fi
|
||||
|
||||
# Collect all arguments for the java command;
|
||||
# * $DEFAULT_JVM_OPTS, $JAVA_OPTS, and $GRADLE_OPTS can contain fragments of
|
||||
# shell script including quotes and variable substitutions, so put them in
|
||||
# double quotes to make sure that they get re-expanded; and
|
||||
# * put everything else in single quotes, so that it's not re-expanded.
|
||||
|
||||
# Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script.
|
||||
DEFAULT_JVM_OPTS='"-Xmx64m" "-Xms64m"'
|
||||
|
||||
# Collect all arguments for the java command:
|
||||
# * DEFAULT_JVM_OPTS, JAVA_OPTS, JAVA_OPTS, and optsEnvironmentVar are not allowed to contain shell fragments,
|
||||
# and any embedded shellness will be escaped.
|
||||
# * For example: A user cannot expect ${Hostname} to be expanded, as it is an environment variable and will be
|
||||
# treated as '${Hostname}' itself on the command line.
|
||||
|
||||
set -- \
|
||||
"-Dorg.gradle.appname=$APP_BASE_NAME" \
|
||||
-classpath "$CLASSPATH" \
|
||||
org.gradle.wrapper.GradleWrapperMain \
|
||||
-jar "$APP_HOME/gradle/wrapper/gradle-wrapper.jar" \
|
||||
"$@"
|
||||
|
||||
# Stop when "xargs" is not available.
|
||||
@@ -241,4 +248,4 @@ eval "set -- $(
|
||||
tr '\n' ' '
|
||||
)" '"$@"'
|
||||
|
||||
exec "$JAVACMD" "$@"
|
||||
exec "$JAVACMD" "$@"
|
||||
@@ -1,3 +1,8 @@
|
||||
@REM Copyright (c) Meta Platforms, Inc. and affiliates.
|
||||
@REM
|
||||
@REM This source code is licensed under the MIT license found in the
|
||||
@REM LICENSE file in the root directory of this source tree.
|
||||
|
||||
@rem
|
||||
@rem Copyright 2015 the original author or authors.
|
||||
@rem
|
||||
@@ -13,6 +18,8 @@
|
||||
@rem See the License for the specific language governing permissions and
|
||||
@rem limitations under the License.
|
||||
@rem
|
||||
@rem SPDX-License-Identifier: Apache-2.0
|
||||
@rem
|
||||
|
||||
@if "%DEBUG%"=="" @echo off
|
||||
@rem ##########################################################################
|
||||
@@ -43,11 +50,11 @@ set JAVA_EXE=java.exe
|
||||
%JAVA_EXE% -version >NUL 2>&1
|
||||
if %ERRORLEVEL% equ 0 goto execute
|
||||
|
||||
echo.
|
||||
echo ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH.
|
||||
echo.
|
||||
echo Please set the JAVA_HOME variable in your environment to match the
|
||||
echo location of your Java installation.
|
||||
echo. 1>&2
|
||||
echo ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH. 1>&2
|
||||
echo. 1>&2
|
||||
echo Please set the JAVA_HOME variable in your environment to match the 1>&2
|
||||
echo location of your Java installation. 1>&2
|
||||
|
||||
goto fail
|
||||
|
||||
@@ -57,22 +64,22 @@ set JAVA_EXE=%JAVA_HOME%/bin/java.exe
|
||||
|
||||
if exist "%JAVA_EXE%" goto execute
|
||||
|
||||
echo.
|
||||
echo ERROR: JAVA_HOME is set to an invalid directory: %JAVA_HOME%
|
||||
echo.
|
||||
echo Please set the JAVA_HOME variable in your environment to match the
|
||||
echo location of your Java installation.
|
||||
echo. 1>&2
|
||||
echo ERROR: JAVA_HOME is set to an invalid directory: %JAVA_HOME% 1>&2
|
||||
echo. 1>&2
|
||||
echo Please set the JAVA_HOME variable in your environment to match the 1>&2
|
||||
echo location of your Java installation. 1>&2
|
||||
|
||||
goto fail
|
||||
|
||||
:execute
|
||||
@rem Setup the command line
|
||||
|
||||
set CLASSPATH=%APP_HOME%\gradle\wrapper\gradle-wrapper.jar
|
||||
set CLASSPATH=
|
||||
|
||||
|
||||
@rem Execute Gradle
|
||||
"%JAVA_EXE%" %DEFAULT_JVM_OPTS% %JAVA_OPTS% %GRADLE_OPTS% "-Dorg.gradle.appname=%APP_BASE_NAME%" -classpath "%CLASSPATH%" org.gradle.wrapper.GradleWrapperMain %*
|
||||
"%JAVA_EXE%" %DEFAULT_JVM_OPTS% %JAVA_OPTS% %GRADLE_OPTS% "-Dorg.gradle.appname=%APP_BASE_NAME%" -classpath "%CLASSPATH%" -jar "%APP_HOME%\gradle\wrapper\gradle-wrapper.jar" %*
|
||||
|
||||
:end
|
||||
@rem End local scope for the variables with windows NT shell
|
||||
@@ -89,4 +96,4 @@ exit /b %EXIT_CODE%
|
||||
:mainEnd
|
||||
if "%OS%"=="Windows_NT" endlocal
|
||||
|
||||
:omega
|
||||
:omega
|
||||
@@ -1,6 +1,8 @@
|
||||
rootProject.name = 'LNReader'
|
||||
pluginManagement { includeBuild("../node_modules/@react-native/gradle-plugin") }
|
||||
plugins { id("com.facebook.react.settings") }
|
||||
|
||||
apply from: file("../node_modules/@react-native-community/cli-platform-android/native_modules.gradle"); applyNativeModulesSettingsGradle(settings)
|
||||
extensions.configure(com.facebook.react.ReactSettingsExtension){ ex -> ex.autolinkLibrariesFromCommand() }
|
||||
rootProject.name = 'LNReader'
|
||||
|
||||
include ':app'
|
||||
includeBuild('../node_modules/@react-native/gradle-plugin')
|
||||
|
||||
@@ -4,7 +4,16 @@
|
||||
"slug": "LNReader",
|
||||
"scheme": "lnreader",
|
||||
"plugins": [
|
||||
"expo-localization"
|
||||
"expo-localization",
|
||||
"react-native-edge-to-edge",
|
||||
[
|
||||
"expo-sqlite",
|
||||
{
|
||||
"enableFTS": false,
|
||||
"useSQLCipher": false
|
||||
}
|
||||
],
|
||||
"expo-web-browser"
|
||||
]
|
||||
}
|
||||
}
|
||||
|
||||
|
After Width: | Height: | Size: 12 KiB |
@@ -1,8 +1,14 @@
|
||||
const ReactCompilerConfig = {
|
||||
target: '19',
|
||||
};
|
||||
|
||||
module.exports = function (api) {
|
||||
api.cache(true);
|
||||
return {
|
||||
presets: ['babel-preset-expo', 'module:metro-react-native-babel-preset'],
|
||||
presets: ['module:@react-native/babel-preset'],
|
||||
plugins: [
|
||||
'module:@babel/plugin-transform-export-namespace-from',
|
||||
['babel-plugin-react-compiler', ReactCompilerConfig],
|
||||
[
|
||||
'module-resolver',
|
||||
{
|
||||
@@ -12,18 +18,20 @@ module.exports = function (api) {
|
||||
'@hooks': './src/hooks',
|
||||
'@screens': './src/screens',
|
||||
'@strings': './strings',
|
||||
'@redux': './src/redux',
|
||||
'@services': './src/services',
|
||||
'@plugins': './src/plugins',
|
||||
'@utils': './src/utils',
|
||||
'@theme': './src/theme',
|
||||
'@navigators': './src/navigators',
|
||||
'@native': './src/native',
|
||||
'@api': './src/api',
|
||||
'@type': './src/type',
|
||||
'@specs': './specs',
|
||||
'react-native-vector-icons/MaterialCommunityIcons':
|
||||
'@react-native-vector-icons/material-design-icons',
|
||||
},
|
||||
},
|
||||
],
|
||||
'react-native-reanimated/plugin',
|
||||
'react-native-worklets/plugin',
|
||||
[
|
||||
'module:react-native-dotenv',
|
||||
{
|
||||
|
||||
@@ -0,0 +1,180 @@
|
||||
{
|
||||
description = "React Native development environment";
|
||||
|
||||
inputs = {
|
||||
nixpkgs.url = "github:NixOS/nixpkgs/nixos-unstable";
|
||||
flake-utils.url = "github:numtide/flake-utils";
|
||||
};
|
||||
|
||||
outputs = { self, nixpkgs, flake-utils }:
|
||||
flake-utils.lib.eachDefaultSystem (system:
|
||||
let
|
||||
pkgs = import nixpkgs {
|
||||
inherit system;
|
||||
config.allowUnfree = true;
|
||||
android_sdk.accept_license = true;
|
||||
};
|
||||
|
||||
androidEnv = pkgs.androidenv.override { licenseAccepted = true; };
|
||||
# The androidComposition now expects enableEmulator to be passed in
|
||||
# It's no longer defined here but passed from mkShell below.
|
||||
mkAndroidComposition = { enableEmulatorInput }: androidEnv.composeAndroidPackages {
|
||||
cmdLineToolsVersion = "8.0";
|
||||
platformToolsVersion = "35.0.2";
|
||||
buildToolsVersions = [ "35.0.0" ];
|
||||
platformVersions = [ "35" ];
|
||||
abiVersions = [ "x86_64" ];
|
||||
includeNDK = true;
|
||||
ndkVersions = [
|
||||
"27.1.12297006"
|
||||
"27.0.12077973"
|
||||
];
|
||||
includeSystemImages = enableEmulatorInput; # Use the input parameter
|
||||
systemImageTypes = [ "google_apis" ];
|
||||
includeEmulator = enableEmulatorInput; # Use the input parameter
|
||||
useGoogleAPIs = true;
|
||||
extraLicenses = [
|
||||
"android-sdk-arm-dbt-license"
|
||||
"android-sdk-license"
|
||||
"android-sdk-preview-license"
|
||||
"google-gdk-license"
|
||||
"intel-android-extra-license"
|
||||
"intel-android-sysimage-license"
|
||||
"mips-android-sysimage-license"
|
||||
];
|
||||
};
|
||||
|
||||
in
|
||||
{
|
||||
devShells = {
|
||||
# Define a specific devShell for when emulator is enabled
|
||||
emulator =
|
||||
let
|
||||
enableEmulator = true;
|
||||
androidComposition = mkAndroidComposition { enableEmulatorInput = enableEmulator; };
|
||||
androidSdk = androidComposition.androidsdk;
|
||||
in
|
||||
pkgs.mkShell {
|
||||
buildInputs = with pkgs; [
|
||||
nodejs_20
|
||||
nodePackages.pnpm
|
||||
openjdk17
|
||||
androidSdk
|
||||
gradle
|
||||
git
|
||||
curl
|
||||
unzip
|
||||
which
|
||||
rsync
|
||||
scrcpy
|
||||
]
|
||||
++ pkgs.lib.optionals enableEmulator [
|
||||
libglvnd
|
||||
mesa
|
||||
vulkan-loader
|
||||
qemu_kvm
|
||||
];
|
||||
shellHook = ''
|
||||
export ANDROID_USER_HOME="$HOME/.android-nix"
|
||||
mkdir -p "$ANDROID_USER_HOME"
|
||||
if [ ! -d "$ANDROID_USER_HOME/sdk" ]; then
|
||||
echo "Setting up writable Android SDK. This may take a moment..."
|
||||
mkdir -p "$ANDROID_USER_HOME/sdk"
|
||||
rsync -a "${androidSdk}/libexec/android-sdk/" "$ANDROID_USER_HOME/sdk/"
|
||||
chmod -R u+w "$ANDROID_USER_HOME/sdk"
|
||||
echo "Writable Android SDK setup complete."
|
||||
fi
|
||||
export ANDROID_HOME="$ANDROID_USER_HOME/sdk"
|
||||
export ANDROID_SDK_ROOT="$ANDROID_HOME"
|
||||
export PATH="$ANDROID_HOME/cmdline-tools/latest/bin:$ANDROID_HOME/platform-tools:$ANDROID_HOME/emulator:$PATH"
|
||||
export JAVA_HOME="${pkgs.openjdk17}"
|
||||
export REACT_NATIVE_PACKAGER_HOSTNAME="127.0.0.1"
|
||||
export ANDROID_NDK_HOME="$ANDROID_HOME/ndk-bundle"
|
||||
export NDK_HOME="$ANDROID_NDK_HOME"
|
||||
echo "Emulator support is ENABLED."
|
||||
export VK_ICD_FILENAMES="${pkgs.mesa}/share/vulkan/icd.d/radeon_icd.x86_64.json"
|
||||
export LD_LIBRARY_PATH="${pkgs.libglvnd}/lib:${pkgs.mesa}/lib''${LD_LIBRARY_PATH:+:}$LD_LIBRARY_PATH"
|
||||
export EGL_PLATFORM=x11
|
||||
if ! "$ANDROID_HOME/cmdline-tools/latest/bin/avdmanager" list avd | grep -q "ReactNative_API35"; then
|
||||
echo "Creating default AVD (ReactNative_API35 - Pixel 8 profile)..."
|
||||
echo "no" | "$ANDROID_HOME/cmdline-tools/latest/bin/avdmanager" create avd -n "ReactNative_API35" -k "system-images;android-35;google_apis;x86_64" -d "pixel_8"
|
||||
echo "AVD created. First launch may take longer."
|
||||
fi
|
||||
echo "To launch emulator: emulator -avd ReactNative_API35 -gpu host -no-metrics -no-audio"
|
||||
echo "React Native development environment loaded!"
|
||||
echo "Android SDK: $ANDROID_HOME"
|
||||
echo "Android NDK: $ANDROID_NDK_HOME"
|
||||
echo "Java: $JAVA_HOME"
|
||||
echo "Emulator Enabled: Yes"
|
||||
echo ""
|
||||
echo "--- Available Commands ---"
|
||||
echo " npx react-native run-android"
|
||||
echo " adb devices"
|
||||
echo " scrcpy"
|
||||
echo " npm start"
|
||||
echo "--------------------------"
|
||||
'';
|
||||
};
|
||||
|
||||
# Define a specific devShell for when emulator is disabled
|
||||
default =
|
||||
let
|
||||
enableEmulator = false; # Define enableEmulator locally for this shell instance
|
||||
androidComposition = mkAndroidComposition { enableEmulatorInput = enableEmulator; };
|
||||
androidSdk = androidComposition.androidsdk;
|
||||
in
|
||||
pkgs.mkShell {
|
||||
buildInputs = with pkgs; [
|
||||
nodejs_20
|
||||
nodePackages.pnpm
|
||||
openjdk17
|
||||
androidSdk
|
||||
gradle
|
||||
git
|
||||
curl
|
||||
unzip
|
||||
which
|
||||
rsync
|
||||
scrcpy
|
||||
]
|
||||
++ pkgs.lib.optionals enableEmulator [ # This list will be empty as enableEmulator is false
|
||||
libglvnd
|
||||
mesa
|
||||
vulkan-loader
|
||||
qemu_kvm
|
||||
];
|
||||
shellHook = ''
|
||||
export ANDROID_USER_HOME="$HOME/.android-nix"
|
||||
mkdir -p "$ANDROID_USER_HOME"
|
||||
if [ ! -d "$ANDROID_USER_HOME/sdk" ]; then
|
||||
echo "Setting up writable Android SDK. This may take a moment..."
|
||||
mkdir -p "$ANDROID_USER_HOME/sdk"
|
||||
rsync -a "${androidSdk}/libexec/android-sdk/" "$ANDROID_USER_HOME/sdk/"
|
||||
chmod -R u+w "$ANDROID_USER_HOME/sdk"
|
||||
echo "Writable Android SDK setup complete."
|
||||
fi
|
||||
export ANDROID_HOME="$ANDROID_USER_HOME/sdk"
|
||||
export ANDROID_SDK_ROOT="$ANDROID_HOME"
|
||||
export PATH="$ANDROID_HOME/cmdline-tools/latest/bin:$ANDROID_HOME/platform-tools:$ANDROID_HOME/emulator:$PATH"
|
||||
export JAVA_HOME="${pkgs.openjdk17}"
|
||||
export REACT_NATIVE_PACKAGER_HOSTNAME="127.0.0.1"
|
||||
export ANDROID_NDK_HOME="$ANDROID_HOME/ndk-bundle"
|
||||
export NDK_HOME="$ANDROID_NDK_HOME"
|
||||
echo "Emulator support is DISABLED. Focusing on physical device development."
|
||||
echo "React Native development environment loaded!"
|
||||
echo "Android SDK: $ANDROID_HOME"
|
||||
echo "Android NDK: $ANDROID_NDK_HOME"
|
||||
echo "Java: $JAVA_HOME"
|
||||
echo "Emulator Enabled: No"
|
||||
echo ""
|
||||
echo "--- Available Commands ---"
|
||||
echo " npx react-native run-android"
|
||||
echo " adb devices"
|
||||
echo " scrcpy"
|
||||
echo " pnpm run dev:start"
|
||||
echo "--------------------------"
|
||||
'';
|
||||
};
|
||||
};
|
||||
});
|
||||
}
|
||||
@@ -0,0 +1 @@
|
||||
export NODE_BINARY=$(command -v node)
|
||||
@@ -0,0 +1,22 @@
|
||||
import UIKit
|
||||
import Foundation
|
||||
import Lottie
|
||||
|
||||
@objc class Dynamic: NSObject {
|
||||
|
||||
@objc func createAnimationView(rootView: UIView, lottieName: String) -> AnimationView {
|
||||
let animationView = AnimationView(name: lottieName)
|
||||
animationView.frame = rootView.frame
|
||||
animationView.center = rootView.center
|
||||
animationView.backgroundColor = UIColor.white;
|
||||
return animationView;
|
||||
}
|
||||
|
||||
@objc func play(animationView: AnimationView) {
|
||||
animationView.play(
|
||||
completion: { (success) in
|
||||
RNSplashScreen.setAnimationFinished(true)
|
||||
}
|
||||
);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,596 @@
|
||||
// !$*UTF8*$!
|
||||
{
|
||||
archiveVersion = 1;
|
||||
classes = {
|
||||
};
|
||||
objectVersion = 54;
|
||||
objects = {
|
||||
|
||||
/* Begin PBXBuildFile section */
|
||||
0C80B921A6F3F58F76C31292 /* libPods-LNReader.a in Frameworks */ = {isa = PBXBuildFile; fileRef = 5DCACB8F33CDC322A6C60F78 /* libPods-LNReader.a */; };
|
||||
13B07FBF1A68108700A75B9A /* Images.xcassets in Resources */ = {isa = PBXBuildFile; fileRef = 13B07FB51A68108700A75B9A /* Images.xcassets */; };
|
||||
147488B02DD8E9BD00C6D0A2 /* Dynamic.swift in Sources */ = {isa = PBXBuildFile; fileRef = 147488AF2DD8E9BD00C6D0A2 /* Dynamic.swift */; };
|
||||
147488B22DD8EA0700C6D0A2 /* loading.json in Resources */ = {isa = PBXBuildFile; fileRef = 147488B12DD8EA0700C6D0A2 /* loading.json */; };
|
||||
147488B72DD97E8A00C6D0A2 /* RCTNativeFile.swift in Sources */ = {isa = PBXBuildFile; fileRef = 147488B62DD97E8A00C6D0A2 /* RCTNativeFile.swift */; };
|
||||
147488BC2DD9882000C6D0A2 /* RCTNativeFile.mm in Sources */ = {isa = PBXBuildFile; fileRef = 147488BB2DD9882000C6D0A2 /* RCTNativeFile.mm */; };
|
||||
147488C02DD9A14100C6D0A2 /* RCTNativeVolumeButtonListener.mm in Sources */ = {isa = PBXBuildFile; fileRef = 147488BF2DD9A14100C6D0A2 /* RCTNativeVolumeButtonListener.mm */; };
|
||||
147488C42DD9A27400C6D0A2 /* RCTNativeEpubUtil.mm in Sources */ = {isa = PBXBuildFile; fileRef = 147488C32DD9A27400C6D0A2 /* RCTNativeEpubUtil.mm */; };
|
||||
147488C82DD9A68600C6D0A2 /* RCTNativeZipArchive.mm in Sources */ = {isa = PBXBuildFile; fileRef = 147488C72DD9A68600C6D0A2 /* RCTNativeZipArchive.mm */; };
|
||||
761780ED2CA45674006654EE /* AppDelegate.swift in Sources */ = {isa = PBXBuildFile; fileRef = 761780EC2CA45674006654EE /* AppDelegate.swift */; };
|
||||
81AB9BB82411601600AC10FF /* LaunchScreen.storyboard in Resources */ = {isa = PBXBuildFile; fileRef = 81AB9BB72411601600AC10FF /* LaunchScreen.storyboard */; };
|
||||
A1FA4E89FDA7CFAD14C43693 /* ExpoModulesProvider.swift in Sources */ = {isa = PBXBuildFile; fileRef = 1F74819B237E0768BB72DED8 /* ExpoModulesProvider.swift */; };
|
||||
CF1A2676AC104DBC83DBFFF9 /* PrivacyInfo.xcprivacy in Resources */ = {isa = PBXBuildFile; fileRef = 13B07FB81A68108700A75B9A /* PrivacyInfo.xcprivacy */; };
|
||||
/* End PBXBuildFile section */
|
||||
|
||||
/* Begin PBXFileReference section */
|
||||
13B07F961A680F5B00A75B9A /* LNReader.app */ = {isa = PBXFileReference; explicitFileType = wrapper.application; includeInIndex = 0; path = LNReader.app; sourceTree = BUILT_PRODUCTS_DIR; };
|
||||
13B07FB51A68108700A75B9A /* Images.xcassets */ = {isa = PBXFileReference; lastKnownFileType = folder.assetcatalog; name = Images.xcassets; path = LNReader/Images.xcassets; sourceTree = "<group>"; };
|
||||
13B07FB61A68108700A75B9A /* Info.plist */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text.plist.xml; name = Info.plist; path = LNReader/Info.plist; sourceTree = "<group>"; };
|
||||
13B07FB81A68108700A75B9A /* PrivacyInfo.xcprivacy */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text.plist.xml; name = PrivacyInfo.xcprivacy; path = LNReader/PrivacyInfo.xcprivacy; sourceTree = "<group>"; };
|
||||
147488AA2DD8E7DE00C6D0A2 /* LNReader-Bridging-Header.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; name = "LNReader-Bridging-Header.h"; path = "LNReader/LNReader-Bridging-Header.h"; sourceTree = "<group>"; };
|
||||
147488AF2DD8E9BD00C6D0A2 /* Dynamic.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = Dynamic.swift; sourceTree = "<group>"; };
|
||||
147488B12DD8EA0700C6D0A2 /* loading.json */ = {isa = PBXFileReference; lastKnownFileType = text.json; path = loading.json; sourceTree = "<group>"; };
|
||||
147488B62DD97E8A00C6D0A2 /* RCTNativeFile.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = RCTNativeFile.swift; sourceTree = "<group>"; };
|
||||
147488BA2DD9882000C6D0A2 /* RCTNativeFile.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = RCTNativeFile.h; sourceTree = "<group>"; };
|
||||
147488BB2DD9882000C6D0A2 /* RCTNativeFile.mm */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.cpp.objcpp; path = RCTNativeFile.mm; sourceTree = "<group>"; };
|
||||
147488BE2DD9A14100C6D0A2 /* RCTNativeVolumeButtonListener.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = RCTNativeVolumeButtonListener.h; sourceTree = "<group>"; };
|
||||
147488BF2DD9A14100C6D0A2 /* RCTNativeVolumeButtonListener.mm */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.cpp.objcpp; path = RCTNativeVolumeButtonListener.mm; sourceTree = "<group>"; };
|
||||
147488C22DD9A27400C6D0A2 /* RCTNativeEpubUtil.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = RCTNativeEpubUtil.h; sourceTree = "<group>"; };
|
||||
147488C32DD9A27400C6D0A2 /* RCTNativeEpubUtil.mm */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.cpp.objcpp; path = RCTNativeEpubUtil.mm; sourceTree = "<group>"; };
|
||||
147488C62DD9A68600C6D0A2 /* RCTNativeZipArchive.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = RCTNativeZipArchive.h; sourceTree = "<group>"; };
|
||||
147488C72DD9A68600C6D0A2 /* RCTNativeZipArchive.mm */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.cpp.objcpp; path = RCTNativeZipArchive.mm; sourceTree = "<group>"; };
|
||||
1F74819B237E0768BB72DED8 /* ExpoModulesProvider.swift */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.swift; name = ExpoModulesProvider.swift; path = "Pods/Target Support Files/Pods-LNReader/ExpoModulesProvider.swift"; sourceTree = "<group>"; };
|
||||
3B4392A12AC88292D35C810B /* Pods-LNReader.debug.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "Pods-LNReader.debug.xcconfig"; path = "Target Support Files/Pods-LNReader/Pods-LNReader.debug.xcconfig"; sourceTree = "<group>"; };
|
||||
5709B34CF0A7D63546082F79 /* Pods-LNReader.release.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "Pods-LNReader.release.xcconfig"; path = "Target Support Files/Pods-LNReader/Pods-LNReader.release.xcconfig"; sourceTree = "<group>"; };
|
||||
5DCACB8F33CDC322A6C60F78 /* libPods-LNReader.a */ = {isa = PBXFileReference; explicitFileType = archive.ar; includeInIndex = 0; path = "libPods-LNReader.a"; sourceTree = BUILT_PRODUCTS_DIR; };
|
||||
761780EC2CA45674006654EE /* AppDelegate.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; name = AppDelegate.swift; path = LNReader/AppDelegate.swift; sourceTree = "<group>"; };
|
||||
81AB9BB72411601600AC10FF /* LaunchScreen.storyboard */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = file.storyboard; name = LaunchScreen.storyboard; path = LNReader/LaunchScreen.storyboard; sourceTree = "<group>"; };
|
||||
ED297162215061F000B7C4FE /* JavaScriptCore.framework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.framework; name = JavaScriptCore.framework; path = System/Library/Frameworks/JavaScriptCore.framework; sourceTree = SDKROOT; };
|
||||
/* End PBXFileReference section */
|
||||
|
||||
/* Begin PBXFrameworksBuildPhase section */
|
||||
13B07F8C1A680F5B00A75B9A /* Frameworks */ = {
|
||||
isa = PBXFrameworksBuildPhase;
|
||||
buildActionMask = 2147483647;
|
||||
files = (
|
||||
0C80B921A6F3F58F76C31292 /* libPods-LNReader.a in Frameworks */,
|
||||
);
|
||||
runOnlyForDeploymentPostprocessing = 0;
|
||||
};
|
||||
/* End PBXFrameworksBuildPhase section */
|
||||
|
||||
/* Begin PBXGroup section */
|
||||
13B07FAE1A68108700A75B9A /* LNReader */ = {
|
||||
isa = PBXGroup;
|
||||
children = (
|
||||
13B07FB51A68108700A75B9A /* Images.xcassets */,
|
||||
761780EC2CA45674006654EE /* AppDelegate.swift */,
|
||||
147488B12DD8EA0700C6D0A2 /* loading.json */,
|
||||
13B07FB61A68108700A75B9A /* Info.plist */,
|
||||
81AB9BB72411601600AC10FF /* LaunchScreen.storyboard */,
|
||||
13B07FB81A68108700A75B9A /* PrivacyInfo.xcprivacy */,
|
||||
147488AA2DD8E7DE00C6D0A2 /* LNReader-Bridging-Header.h */,
|
||||
147488AF2DD8E9BD00C6D0A2 /* Dynamic.swift */,
|
||||
147488B62DD97E8A00C6D0A2 /* RCTNativeFile.swift */,
|
||||
);
|
||||
name = LNReader;
|
||||
sourceTree = "<group>";
|
||||
};
|
||||
147488B92DD987DF00C6D0A2 /* NativeFile */ = {
|
||||
isa = PBXGroup;
|
||||
children = (
|
||||
147488BA2DD9882000C6D0A2 /* RCTNativeFile.h */,
|
||||
147488BB2DD9882000C6D0A2 /* RCTNativeFile.mm */,
|
||||
);
|
||||
path = NativeFile;
|
||||
sourceTree = "<group>";
|
||||
};
|
||||
147488BD2DD9A0E900C6D0A2 /* NativeVolumeButtonListener */ = {
|
||||
isa = PBXGroup;
|
||||
children = (
|
||||
147488BE2DD9A14100C6D0A2 /* RCTNativeVolumeButtonListener.h */,
|
||||
147488BF2DD9A14100C6D0A2 /* RCTNativeVolumeButtonListener.mm */,
|
||||
);
|
||||
path = NativeVolumeButtonListener;
|
||||
sourceTree = "<group>";
|
||||
};
|
||||
147488C12DD9A25E00C6D0A2 /* NativeEpubUtil */ = {
|
||||
isa = PBXGroup;
|
||||
children = (
|
||||
147488C22DD9A27400C6D0A2 /* RCTNativeEpubUtil.h */,
|
||||
147488C32DD9A27400C6D0A2 /* RCTNativeEpubUtil.mm */,
|
||||
);
|
||||
path = NativeEpubUtil;
|
||||
sourceTree = "<group>";
|
||||
};
|
||||
147488C52DD9A66F00C6D0A2 /* NativeZipArchive */ = {
|
||||
isa = PBXGroup;
|
||||
children = (
|
||||
147488C62DD9A68600C6D0A2 /* RCTNativeZipArchive.h */,
|
||||
147488C72DD9A68600C6D0A2 /* RCTNativeZipArchive.mm */,
|
||||
);
|
||||
path = NativeZipArchive;
|
||||
sourceTree = "<group>";
|
||||
};
|
||||
24615CBE8A657680F1260CBB /* ExpoModulesProviders */ = {
|
||||
isa = PBXGroup;
|
||||
children = (
|
||||
28BBFC4322E6EFCA529574FE /* LNReader */,
|
||||
);
|
||||
name = ExpoModulesProviders;
|
||||
sourceTree = "<group>";
|
||||
};
|
||||
28BBFC4322E6EFCA529574FE /* LNReader */ = {
|
||||
isa = PBXGroup;
|
||||
children = (
|
||||
1F74819B237E0768BB72DED8 /* ExpoModulesProvider.swift */,
|
||||
);
|
||||
name = LNReader;
|
||||
sourceTree = "<group>";
|
||||
};
|
||||
2D16E6871FA4F8E400B85C8A /* Frameworks */ = {
|
||||
isa = PBXGroup;
|
||||
children = (
|
||||
ED297162215061F000B7C4FE /* JavaScriptCore.framework */,
|
||||
5DCACB8F33CDC322A6C60F78 /* libPods-LNReader.a */,
|
||||
);
|
||||
name = Frameworks;
|
||||
sourceTree = "<group>";
|
||||
};
|
||||
832341AE1AAA6A7D00B99B32 /* Libraries */ = {
|
||||
isa = PBXGroup;
|
||||
children = (
|
||||
);
|
||||
name = Libraries;
|
||||
sourceTree = "<group>";
|
||||
};
|
||||
83CBB9F61A601CBA00E9B192 = {
|
||||
isa = PBXGroup;
|
||||
children = (
|
||||
147488C52DD9A66F00C6D0A2 /* NativeZipArchive */,
|
||||
147488C12DD9A25E00C6D0A2 /* NativeEpubUtil */,
|
||||
147488BD2DD9A0E900C6D0A2 /* NativeVolumeButtonListener */,
|
||||
147488B92DD987DF00C6D0A2 /* NativeFile */,
|
||||
13B07FAE1A68108700A75B9A /* LNReader */,
|
||||
832341AE1AAA6A7D00B99B32 /* Libraries */,
|
||||
83CBBA001A601CBA00E9B192 /* Products */,
|
||||
2D16E6871FA4F8E400B85C8A /* Frameworks */,
|
||||
BBD78D7AC51CEA395F1C20DB /* Pods */,
|
||||
24615CBE8A657680F1260CBB /* ExpoModulesProviders */,
|
||||
);
|
||||
indentWidth = 2;
|
||||
sourceTree = "<group>";
|
||||
tabWidth = 2;
|
||||
usesTabs = 0;
|
||||
};
|
||||
83CBBA001A601CBA00E9B192 /* Products */ = {
|
||||
isa = PBXGroup;
|
||||
children = (
|
||||
13B07F961A680F5B00A75B9A /* LNReader.app */,
|
||||
);
|
||||
name = Products;
|
||||
sourceTree = "<group>";
|
||||
};
|
||||
BBD78D7AC51CEA395F1C20DB /* Pods */ = {
|
||||
isa = PBXGroup;
|
||||
children = (
|
||||
3B4392A12AC88292D35C810B /* Pods-LNReader.debug.xcconfig */,
|
||||
5709B34CF0A7D63546082F79 /* Pods-LNReader.release.xcconfig */,
|
||||
);
|
||||
path = Pods;
|
||||
sourceTree = "<group>";
|
||||
};
|
||||
/* End PBXGroup section */
|
||||
|
||||
/* Begin PBXNativeTarget section */
|
||||
13B07F861A680F5B00A75B9A /* LNReader */ = {
|
||||
isa = PBXNativeTarget;
|
||||
buildConfigurationList = 13B07F931A680F5B00A75B9A /* Build configuration list for PBXNativeTarget "LNReader" */;
|
||||
buildPhases = (
|
||||
C38B50BA6285516D6DCD4F65 /* [CP] Check Pods Manifest.lock */,
|
||||
E2CB0E1FD58C9A385B2CE9DA /* [Expo] Configure project */,
|
||||
13B07F871A680F5B00A75B9A /* Sources */,
|
||||
13B07F8C1A680F5B00A75B9A /* Frameworks */,
|
||||
13B07F8E1A680F5B00A75B9A /* Resources */,
|
||||
00DD1BFF1BD5951E006B06BC /* Bundle React Native code and images */,
|
||||
00EEFC60759A1932668264C0 /* [CP] Embed Pods Frameworks */,
|
||||
E235C05ADACE081382539298 /* [CP] Copy Pods Resources */,
|
||||
);
|
||||
buildRules = (
|
||||
);
|
||||
dependencies = (
|
||||
);
|
||||
name = LNReader;
|
||||
productName = LNReader;
|
||||
productReference = 13B07F961A680F5B00A75B9A /* LNReader.app */;
|
||||
productType = "com.apple.product-type.application";
|
||||
};
|
||||
/* End PBXNativeTarget section */
|
||||
|
||||
/* Begin PBXProject section */
|
||||
83CBB9F71A601CBA00E9B192 /* Project object */ = {
|
||||
isa = PBXProject;
|
||||
attributes = {
|
||||
LastUpgradeCheck = 1210;
|
||||
TargetAttributes = {
|
||||
13B07F861A680F5B00A75B9A = {
|
||||
LastSwiftMigration = 1120;
|
||||
};
|
||||
};
|
||||
};
|
||||
buildConfigurationList = 83CBB9FA1A601CBA00E9B192 /* Build configuration list for PBXProject "LNReader" */;
|
||||
compatibilityVersion = "Xcode 12.0";
|
||||
developmentRegion = en;
|
||||
hasScannedForEncodings = 0;
|
||||
knownRegions = (
|
||||
en,
|
||||
Base,
|
||||
);
|
||||
mainGroup = 83CBB9F61A601CBA00E9B192;
|
||||
productRefGroup = 83CBBA001A601CBA00E9B192 /* Products */;
|
||||
projectDirPath = "";
|
||||
projectRoot = "";
|
||||
targets = (
|
||||
13B07F861A680F5B00A75B9A /* LNReader */,
|
||||
);
|
||||
};
|
||||
/* End PBXProject section */
|
||||
|
||||
/* Begin PBXResourcesBuildPhase section */
|
||||
13B07F8E1A680F5B00A75B9A /* Resources */ = {
|
||||
isa = PBXResourcesBuildPhase;
|
||||
buildActionMask = 2147483647;
|
||||
files = (
|
||||
81AB9BB82411601600AC10FF /* LaunchScreen.storyboard in Resources */,
|
||||
13B07FBF1A68108700A75B9A /* Images.xcassets in Resources */,
|
||||
147488B22DD8EA0700C6D0A2 /* loading.json in Resources */,
|
||||
CF1A2676AC104DBC83DBFFF9 /* PrivacyInfo.xcprivacy in Resources */,
|
||||
);
|
||||
runOnlyForDeploymentPostprocessing = 0;
|
||||
};
|
||||
/* End PBXResourcesBuildPhase section */
|
||||
|
||||
/* Begin PBXShellScriptBuildPhase section */
|
||||
00DD1BFF1BD5951E006B06BC /* Bundle React Native code and images */ = {
|
||||
isa = PBXShellScriptBuildPhase;
|
||||
buildActionMask = 2147483647;
|
||||
files = (
|
||||
);
|
||||
inputPaths = (
|
||||
"$(SRCROOT)/.xcode.env.local",
|
||||
"$(SRCROOT)/.xcode.env",
|
||||
);
|
||||
name = "Bundle React Native code and images";
|
||||
outputPaths = (
|
||||
);
|
||||
runOnlyForDeploymentPostprocessing = 0;
|
||||
shellPath = /bin/sh;
|
||||
shellScript = "set -e\n\nWITH_ENVIRONMENT=\"$REACT_NATIVE_PATH/scripts/xcode/with-environment.sh\"\nREACT_NATIVE_XCODE=\"$REACT_NATIVE_PATH/scripts/react-native-xcode.sh\"\n\n/bin/sh -c \"$WITH_ENVIRONMENT $REACT_NATIVE_XCODE\"\n";
|
||||
};
|
||||
00EEFC60759A1932668264C0 /* [CP] Embed Pods Frameworks */ = {
|
||||
isa = PBXShellScriptBuildPhase;
|
||||
buildActionMask = 2147483647;
|
||||
files = (
|
||||
);
|
||||
inputFileListPaths = (
|
||||
"${PODS_ROOT}/Target Support Files/Pods-LNReader/Pods-LNReader-frameworks-${CONFIGURATION}-input-files.xcfilelist",
|
||||
);
|
||||
name = "[CP] Embed Pods Frameworks";
|
||||
outputFileListPaths = (
|
||||
"${PODS_ROOT}/Target Support Files/Pods-LNReader/Pods-LNReader-frameworks-${CONFIGURATION}-output-files.xcfilelist",
|
||||
);
|
||||
runOnlyForDeploymentPostprocessing = 0;
|
||||
shellPath = /bin/sh;
|
||||
shellScript = "\"${PODS_ROOT}/Target Support Files/Pods-LNReader/Pods-LNReader-frameworks.sh\"\n";
|
||||
showEnvVarsInLog = 0;
|
||||
};
|
||||
C38B50BA6285516D6DCD4F65 /* [CP] Check Pods Manifest.lock */ = {
|
||||
isa = PBXShellScriptBuildPhase;
|
||||
buildActionMask = 2147483647;
|
||||
files = (
|
||||
);
|
||||
inputFileListPaths = (
|
||||
);
|
||||
inputPaths = (
|
||||
"${PODS_PODFILE_DIR_PATH}/Podfile.lock",
|
||||
"${PODS_ROOT}/Manifest.lock",
|
||||
);
|
||||
name = "[CP] Check Pods Manifest.lock";
|
||||
outputFileListPaths = (
|
||||
);
|
||||
outputPaths = (
|
||||
"$(DERIVED_FILE_DIR)/Pods-LNReader-checkManifestLockResult.txt",
|
||||
);
|
||||
runOnlyForDeploymentPostprocessing = 0;
|
||||
shellPath = /bin/sh;
|
||||
shellScript = "diff \"${PODS_PODFILE_DIR_PATH}/Podfile.lock\" \"${PODS_ROOT}/Manifest.lock\" > /dev/null\nif [ $? != 0 ] ; then\n # print error to STDERR\n echo \"error: The sandbox is not in sync with the Podfile.lock. Run 'pod install' or update your CocoaPods installation.\" >&2\n exit 1\nfi\n# This output is used by Xcode 'outputs' to avoid re-running this script phase.\necho \"SUCCESS\" > \"${SCRIPT_OUTPUT_FILE_0}\"\n";
|
||||
showEnvVarsInLog = 0;
|
||||
};
|
||||
E235C05ADACE081382539298 /* [CP] Copy Pods Resources */ = {
|
||||
isa = PBXShellScriptBuildPhase;
|
||||
buildActionMask = 2147483647;
|
||||
files = (
|
||||
);
|
||||
inputFileListPaths = (
|
||||
"${PODS_ROOT}/Target Support Files/Pods-LNReader/Pods-LNReader-resources-${CONFIGURATION}-input-files.xcfilelist",
|
||||
);
|
||||
name = "[CP] Copy Pods Resources";
|
||||
outputFileListPaths = (
|
||||
"${PODS_ROOT}/Target Support Files/Pods-LNReader/Pods-LNReader-resources-${CONFIGURATION}-output-files.xcfilelist",
|
||||
);
|
||||
runOnlyForDeploymentPostprocessing = 0;
|
||||
shellPath = /bin/sh;
|
||||
shellScript = "\"${PODS_ROOT}/Target Support Files/Pods-LNReader/Pods-LNReader-resources.sh\"\n";
|
||||
showEnvVarsInLog = 0;
|
||||
};
|
||||
E2CB0E1FD58C9A385B2CE9DA /* [Expo] Configure project */ = {
|
||||
isa = PBXShellScriptBuildPhase;
|
||||
alwaysOutOfDate = 1;
|
||||
buildActionMask = 2147483647;
|
||||
files = (
|
||||
);
|
||||
inputFileListPaths = (
|
||||
);
|
||||
inputPaths = (
|
||||
);
|
||||
name = "[Expo] Configure project";
|
||||
outputFileListPaths = (
|
||||
);
|
||||
outputPaths = (
|
||||
);
|
||||
runOnlyForDeploymentPostprocessing = 0;
|
||||
shellPath = /bin/sh;
|
||||
shellScript = "# This script configures Expo modules and generates the modules provider file.\nbash -l -c \"./Pods/Target\\ Support\\ Files/Pods-LNReader/expo-configure-project.sh\"\n";
|
||||
};
|
||||
/* End PBXShellScriptBuildPhase section */
|
||||
|
||||
/* Begin PBXSourcesBuildPhase section */
|
||||
13B07F871A680F5B00A75B9A /* Sources */ = {
|
||||
isa = PBXSourcesBuildPhase;
|
||||
buildActionMask = 2147483647;
|
||||
files = (
|
||||
147488C02DD9A14100C6D0A2 /* RCTNativeVolumeButtonListener.mm in Sources */,
|
||||
147488B02DD8E9BD00C6D0A2 /* Dynamic.swift in Sources */,
|
||||
761780ED2CA45674006654EE /* AppDelegate.swift in Sources */,
|
||||
147488BC2DD9882000C6D0A2 /* RCTNativeFile.mm in Sources */,
|
||||
147488B72DD97E8A00C6D0A2 /* RCTNativeFile.swift in Sources */,
|
||||
147488C82DD9A68600C6D0A2 /* RCTNativeZipArchive.mm in Sources */,
|
||||
A1FA4E89FDA7CFAD14C43693 /* ExpoModulesProvider.swift in Sources */,
|
||||
147488C42DD9A27400C6D0A2 /* RCTNativeEpubUtil.mm in Sources */,
|
||||
);
|
||||
runOnlyForDeploymentPostprocessing = 0;
|
||||
};
|
||||
/* End PBXSourcesBuildPhase section */
|
||||
|
||||
/* Begin XCBuildConfiguration section */
|
||||
13B07F941A680F5B00A75B9A /* Debug */ = {
|
||||
isa = XCBuildConfiguration;
|
||||
baseConfigurationReference = 3B4392A12AC88292D35C810B /* Pods-LNReader.debug.xcconfig */;
|
||||
buildSettings = {
|
||||
ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon;
|
||||
ASSETCATALOG_COMPILER_INCLUDE_ALL_APPICON_ASSETS = NO;
|
||||
CLANG_ENABLE_MODULES = YES;
|
||||
CURRENT_PROJECT_VERSION = 1;
|
||||
ENABLE_BITCODE = NO;
|
||||
INFOPLIST_FILE = LNReader/Info.plist;
|
||||
IPHONEOS_DEPLOYMENT_TARGET = 15.1;
|
||||
LD_RUNPATH_SEARCH_PATHS = (
|
||||
"$(inherited)",
|
||||
"@executable_path/Frameworks",
|
||||
);
|
||||
MARKETING_VERSION = 1.0;
|
||||
OTHER_LDFLAGS = (
|
||||
"$(inherited)",
|
||||
"-ObjC",
|
||||
"-lc++",
|
||||
);
|
||||
OTHER_SWIFT_FLAGS = "$(inherited) -D EXPO_CONFIGURATION_DEBUG";
|
||||
PRODUCT_BUNDLE_IDENTIFIER = "org.reactjs.native.example.$(PRODUCT_NAME:rfc1034identifier)";
|
||||
PRODUCT_NAME = LNReader;
|
||||
SWIFT_OBJC_BRIDGING_HEADER = "LNReader/LNReader-Bridging-Header.h";
|
||||
SWIFT_OPTIMIZATION_LEVEL = "-Onone";
|
||||
SWIFT_VERSION = 5.0;
|
||||
VERSIONING_SYSTEM = "apple-generic";
|
||||
};
|
||||
name = Debug;
|
||||
};
|
||||
13B07F951A680F5B00A75B9A /* Release */ = {
|
||||
isa = XCBuildConfiguration;
|
||||
baseConfigurationReference = 5709B34CF0A7D63546082F79 /* Pods-LNReader.release.xcconfig */;
|
||||
buildSettings = {
|
||||
ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon;
|
||||
ASSETCATALOG_COMPILER_INCLUDE_ALL_APPICON_ASSETS = NO;
|
||||
CLANG_ENABLE_MODULES = YES;
|
||||
CURRENT_PROJECT_VERSION = 1;
|
||||
INFOPLIST_FILE = LNReader/Info.plist;
|
||||
IPHONEOS_DEPLOYMENT_TARGET = 15.1;
|
||||
LD_RUNPATH_SEARCH_PATHS = (
|
||||
"$(inherited)",
|
||||
"@executable_path/Frameworks",
|
||||
);
|
||||
MARKETING_VERSION = 1.0;
|
||||
OTHER_LDFLAGS = (
|
||||
"$(inherited)",
|
||||
"-ObjC",
|
||||
"-lc++",
|
||||
);
|
||||
OTHER_SWIFT_FLAGS = "$(inherited) -D EXPO_CONFIGURATION_RELEASE";
|
||||
PRODUCT_BUNDLE_IDENTIFIER = "org.reactjs.native.example.$(PRODUCT_NAME:rfc1034identifier)";
|
||||
PRODUCT_NAME = LNReader;
|
||||
SWIFT_OBJC_BRIDGING_HEADER = "LNReader/LNReader-Bridging-Header.h";
|
||||
SWIFT_VERSION = 5.0;
|
||||
VERSIONING_SYSTEM = "apple-generic";
|
||||
};
|
||||
name = Release;
|
||||
};
|
||||
83CBBA201A601CBA00E9B192 /* Debug */ = {
|
||||
isa = XCBuildConfiguration;
|
||||
buildSettings = {
|
||||
ALWAYS_SEARCH_USER_PATHS = NO;
|
||||
CLANG_ANALYZER_LOCALIZABILITY_NONLOCALIZED = YES;
|
||||
CLANG_CXX_LANGUAGE_STANDARD = "c++20";
|
||||
CLANG_CXX_LIBRARY = "libc++";
|
||||
CLANG_ENABLE_MODULES = YES;
|
||||
CLANG_ENABLE_OBJC_ARC = YES;
|
||||
CLANG_WARN_BLOCK_CAPTURE_AUTORELEASING = YES;
|
||||
CLANG_WARN_BOOL_CONVERSION = YES;
|
||||
CLANG_WARN_COMMA = YES;
|
||||
CLANG_WARN_CONSTANT_CONVERSION = YES;
|
||||
CLANG_WARN_DEPRECATED_OBJC_IMPLEMENTATIONS = YES;
|
||||
CLANG_WARN_DIRECT_OBJC_ISA_USAGE = YES_ERROR;
|
||||
CLANG_WARN_EMPTY_BODY = YES;
|
||||
CLANG_WARN_ENUM_CONVERSION = YES;
|
||||
CLANG_WARN_INFINITE_RECURSION = YES;
|
||||
CLANG_WARN_INT_CONVERSION = YES;
|
||||
CLANG_WARN_NON_LITERAL_NULL_CONVERSION = YES;
|
||||
CLANG_WARN_OBJC_IMPLICIT_RETAIN_SELF = YES;
|
||||
CLANG_WARN_OBJC_LITERAL_CONVERSION = YES;
|
||||
CLANG_WARN_OBJC_ROOT_CLASS = YES_ERROR;
|
||||
CLANG_WARN_QUOTED_INCLUDE_IN_FRAMEWORK_HEADER = YES;
|
||||
CLANG_WARN_RANGE_LOOP_ANALYSIS = YES;
|
||||
CLANG_WARN_STRICT_PROTOTYPES = YES;
|
||||
CLANG_WARN_SUSPICIOUS_MOVE = YES;
|
||||
CLANG_WARN_UNREACHABLE_CODE = YES;
|
||||
CLANG_WARN__DUPLICATE_METHOD_MATCH = YES;
|
||||
"CODE_SIGN_IDENTITY[sdk=iphoneos*]" = "iPhone Developer";
|
||||
COPY_PHASE_STRIP = NO;
|
||||
ENABLE_STRICT_OBJC_MSGSEND = YES;
|
||||
ENABLE_TESTABILITY = YES;
|
||||
"EXCLUDED_ARCHS[sdk=iphonesimulator*]" = "";
|
||||
GCC_C_LANGUAGE_STANDARD = gnu99;
|
||||
GCC_DYNAMIC_NO_PIC = NO;
|
||||
GCC_NO_COMMON_BLOCKS = YES;
|
||||
GCC_OPTIMIZATION_LEVEL = 0;
|
||||
GCC_PREPROCESSOR_DEFINITIONS = (
|
||||
"DEBUG=1",
|
||||
"$(inherited)",
|
||||
);
|
||||
GCC_SYMBOLS_PRIVATE_EXTERN = NO;
|
||||
GCC_WARN_64_TO_32_BIT_CONVERSION = YES;
|
||||
GCC_WARN_ABOUT_RETURN_TYPE = YES_ERROR;
|
||||
GCC_WARN_UNDECLARED_SELECTOR = YES;
|
||||
GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE;
|
||||
GCC_WARN_UNUSED_FUNCTION = YES;
|
||||
GCC_WARN_UNUSED_VARIABLE = YES;
|
||||
IPHONEOS_DEPLOYMENT_TARGET = 15.1;
|
||||
LD_RUNPATH_SEARCH_PATHS = (
|
||||
/usr/lib/swift,
|
||||
"$(inherited)",
|
||||
);
|
||||
LIBRARY_SEARCH_PATHS = (
|
||||
"\"$(SDKROOT)/usr/lib/swift\"",
|
||||
"\"$(TOOLCHAIN_DIR)/usr/lib/swift/$(PLATFORM_NAME)\"",
|
||||
"\"$(inherited)\"",
|
||||
);
|
||||
MTL_ENABLE_DEBUG_INFO = YES;
|
||||
ONLY_ACTIVE_ARCH = YES;
|
||||
OTHER_CPLUSPLUSFLAGS = (
|
||||
"$(OTHER_CFLAGS)",
|
||||
"-DFOLLY_NO_CONFIG",
|
||||
"-DFOLLY_MOBILE=1",
|
||||
"-DFOLLY_USE_LIBCPP=1",
|
||||
"-DFOLLY_CFG_NO_COROUTINES=1",
|
||||
"-DFOLLY_HAVE_CLOCK_GETTIME=1",
|
||||
);
|
||||
OTHER_LDFLAGS = "$(inherited) ";
|
||||
REACT_NATIVE_PATH = "${PODS_ROOT}/../../node_modules/react-native";
|
||||
SDKROOT = iphoneos;
|
||||
SWIFT_ACTIVE_COMPILATION_CONDITIONS = "$(inherited) DEBUG";
|
||||
USE_HERMES = true;
|
||||
};
|
||||
name = Debug;
|
||||
};
|
||||
83CBBA211A601CBA00E9B192 /* Release */ = {
|
||||
isa = XCBuildConfiguration;
|
||||
buildSettings = {
|
||||
ALWAYS_SEARCH_USER_PATHS = NO;
|
||||
CLANG_ANALYZER_LOCALIZABILITY_NONLOCALIZED = YES;
|
||||
CLANG_CXX_LANGUAGE_STANDARD = "c++20";
|
||||
CLANG_CXX_LIBRARY = "libc++";
|
||||
CLANG_ENABLE_MODULES = YES;
|
||||
CLANG_ENABLE_OBJC_ARC = YES;
|
||||
CLANG_WARN_BLOCK_CAPTURE_AUTORELEASING = YES;
|
||||
CLANG_WARN_BOOL_CONVERSION = YES;
|
||||
CLANG_WARN_COMMA = YES;
|
||||
CLANG_WARN_CONSTANT_CONVERSION = YES;
|
||||
CLANG_WARN_DEPRECATED_OBJC_IMPLEMENTATIONS = YES;
|
||||
CLANG_WARN_DIRECT_OBJC_ISA_USAGE = YES_ERROR;
|
||||
CLANG_WARN_EMPTY_BODY = YES;
|
||||
CLANG_WARN_ENUM_CONVERSION = YES;
|
||||
CLANG_WARN_INFINITE_RECURSION = YES;
|
||||
CLANG_WARN_INT_CONVERSION = YES;
|
||||
CLANG_WARN_NON_LITERAL_NULL_CONVERSION = YES;
|
||||
CLANG_WARN_OBJC_IMPLICIT_RETAIN_SELF = YES;
|
||||
CLANG_WARN_OBJC_LITERAL_CONVERSION = YES;
|
||||
CLANG_WARN_OBJC_ROOT_CLASS = YES_ERROR;
|
||||
CLANG_WARN_QUOTED_INCLUDE_IN_FRAMEWORK_HEADER = YES;
|
||||
CLANG_WARN_RANGE_LOOP_ANALYSIS = YES;
|
||||
CLANG_WARN_STRICT_PROTOTYPES = YES;
|
||||
CLANG_WARN_SUSPICIOUS_MOVE = YES;
|
||||
CLANG_WARN_UNREACHABLE_CODE = YES;
|
||||
CLANG_WARN__DUPLICATE_METHOD_MATCH = YES;
|
||||
"CODE_SIGN_IDENTITY[sdk=iphoneos*]" = "iPhone Developer";
|
||||
COPY_PHASE_STRIP = YES;
|
||||
ENABLE_NS_ASSERTIONS = NO;
|
||||
ENABLE_STRICT_OBJC_MSGSEND = YES;
|
||||
"EXCLUDED_ARCHS[sdk=iphonesimulator*]" = "";
|
||||
GCC_C_LANGUAGE_STANDARD = gnu99;
|
||||
GCC_NO_COMMON_BLOCKS = YES;
|
||||
GCC_WARN_64_TO_32_BIT_CONVERSION = YES;
|
||||
GCC_WARN_ABOUT_RETURN_TYPE = YES_ERROR;
|
||||
GCC_WARN_UNDECLARED_SELECTOR = YES;
|
||||
GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE;
|
||||
GCC_WARN_UNUSED_FUNCTION = YES;
|
||||
GCC_WARN_UNUSED_VARIABLE = YES;
|
||||
IPHONEOS_DEPLOYMENT_TARGET = 15.1;
|
||||
LD_RUNPATH_SEARCH_PATHS = (
|
||||
/usr/lib/swift,
|
||||
"$(inherited)",
|
||||
);
|
||||
LIBRARY_SEARCH_PATHS = (
|
||||
"\"$(SDKROOT)/usr/lib/swift\"",
|
||||
"\"$(TOOLCHAIN_DIR)/usr/lib/swift/$(PLATFORM_NAME)\"",
|
||||
"\"$(inherited)\"",
|
||||
);
|
||||
MTL_ENABLE_DEBUG_INFO = NO;
|
||||
OTHER_CPLUSPLUSFLAGS = (
|
||||
"$(OTHER_CFLAGS)",
|
||||
"-DFOLLY_NO_CONFIG",
|
||||
"-DFOLLY_MOBILE=1",
|
||||
"-DFOLLY_USE_LIBCPP=1",
|
||||
"-DFOLLY_CFG_NO_COROUTINES=1",
|
||||
"-DFOLLY_HAVE_CLOCK_GETTIME=1",
|
||||
);
|
||||
OTHER_LDFLAGS = "$(inherited) ";
|
||||
REACT_NATIVE_PATH = "${PODS_ROOT}/../../node_modules/react-native";
|
||||
SDKROOT = iphoneos;
|
||||
USE_HERMES = true;
|
||||
VALIDATE_PRODUCT = YES;
|
||||
};
|
||||
name = Release;
|
||||
};
|
||||
/* End XCBuildConfiguration section */
|
||||
|
||||
/* Begin XCConfigurationList section */
|
||||
13B07F931A680F5B00A75B9A /* Build configuration list for PBXNativeTarget "LNReader" */ = {
|
||||
isa = XCConfigurationList;
|
||||
buildConfigurations = (
|
||||
13B07F941A680F5B00A75B9A /* Debug */,
|
||||
13B07F951A680F5B00A75B9A /* Release */,
|
||||
);
|
||||
defaultConfigurationIsVisible = 0;
|
||||
defaultConfigurationName = Release;
|
||||
};
|
||||
83CBB9FA1A601CBA00E9B192 /* Build configuration list for PBXProject "LNReader" */ = {
|
||||
isa = XCConfigurationList;
|
||||
buildConfigurations = (
|
||||
83CBBA201A601CBA00E9B192 /* Debug */,
|
||||
83CBBA211A601CBA00E9B192 /* Release */,
|
||||
);
|
||||
defaultConfigurationIsVisible = 0;
|
||||
defaultConfigurationName = Release;
|
||||
};
|
||||
/* End XCConfigurationList section */
|
||||
};
|
||||
rootObject = 83CBB9F71A601CBA00E9B192 /* Project object */;
|
||||
}
|
||||
@@ -0,0 +1,7 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<Workspace
|
||||
version = "1.0">
|
||||
<FileRef
|
||||
location = "self:">
|
||||
</FileRef>
|
||||
</Workspace>
|
||||
@@ -0,0 +1,88 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<Scheme
|
||||
LastUpgradeVersion = "1210"
|
||||
version = "1.3">
|
||||
<BuildAction
|
||||
parallelizeBuildables = "YES"
|
||||
buildImplicitDependencies = "YES">
|
||||
<BuildActionEntries>
|
||||
<BuildActionEntry
|
||||
buildForTesting = "YES"
|
||||
buildForRunning = "YES"
|
||||
buildForProfiling = "YES"
|
||||
buildForArchiving = "YES"
|
||||
buildForAnalyzing = "YES">
|
||||
<BuildableReference
|
||||
BuildableIdentifier = "primary"
|
||||
BlueprintIdentifier = "13B07F861A680F5B00A75B9A"
|
||||
BuildableName = "LNReader.app"
|
||||
BlueprintName = "LNReader"
|
||||
ReferencedContainer = "container:LNReader.xcodeproj">
|
||||
</BuildableReference>
|
||||
</BuildActionEntry>
|
||||
</BuildActionEntries>
|
||||
</BuildAction>
|
||||
<TestAction
|
||||
buildConfiguration = "Debug"
|
||||
selectedDebuggerIdentifier = "Xcode.DebuggerFoundation.Debugger.LLDB"
|
||||
selectedLauncherIdentifier = "Xcode.DebuggerFoundation.Launcher.LLDB"
|
||||
shouldUseLaunchSchemeArgsEnv = "YES">
|
||||
<Testables>
|
||||
<TestableReference
|
||||
skipped = "NO">
|
||||
<BuildableReference
|
||||
BuildableIdentifier = "primary"
|
||||
BlueprintIdentifier = "00E356ED1AD99517003FC87E"
|
||||
BuildableName = "LNReaderTests.xctest"
|
||||
BlueprintName = "LNReaderTests"
|
||||
ReferencedContainer = "container:LNReader.xcodeproj">
|
||||
</BuildableReference>
|
||||
</TestableReference>
|
||||
</Testables>
|
||||
</TestAction>
|
||||
<LaunchAction
|
||||
buildConfiguration = "Debug"
|
||||
selectedDebuggerIdentifier = "Xcode.DebuggerFoundation.Debugger.LLDB"
|
||||
selectedLauncherIdentifier = "Xcode.DebuggerFoundation.Launcher.LLDB"
|
||||
launchStyle = "0"
|
||||
useCustomWorkingDirectory = "NO"
|
||||
ignoresPersistentStateOnLaunch = "NO"
|
||||
debugDocumentVersioning = "YES"
|
||||
debugServiceExtension = "internal"
|
||||
allowLocationSimulation = "YES">
|
||||
<BuildableProductRunnable
|
||||
runnableDebuggingMode = "0">
|
||||
<BuildableReference
|
||||
BuildableIdentifier = "primary"
|
||||
BlueprintIdentifier = "13B07F861A680F5B00A75B9A"
|
||||
BuildableName = "LNReader.app"
|
||||
BlueprintName = "LNReader"
|
||||
ReferencedContainer = "container:LNReader.xcodeproj">
|
||||
</BuildableReference>
|
||||
</BuildableProductRunnable>
|
||||
</LaunchAction>
|
||||
<ProfileAction
|
||||
buildConfiguration = "Release"
|
||||
shouldUseLaunchSchemeArgsEnv = "YES"
|
||||
savedToolIdentifier = ""
|
||||
useCustomWorkingDirectory = "NO"
|
||||
debugDocumentVersioning = "YES">
|
||||
<BuildableProductRunnable
|
||||
runnableDebuggingMode = "0">
|
||||
<BuildableReference
|
||||
BuildableIdentifier = "primary"
|
||||
BlueprintIdentifier = "13B07F861A680F5B00A75B9A"
|
||||
BuildableName = "LNReader.app"
|
||||
BlueprintName = "LNReader"
|
||||
ReferencedContainer = "container:LNReader.xcodeproj">
|
||||
</BuildableReference>
|
||||
</BuildableProductRunnable>
|
||||
</ProfileAction>
|
||||
<AnalyzeAction
|
||||
buildConfiguration = "Debug">
|
||||
</AnalyzeAction>
|
||||
<ArchiveAction
|
||||
buildConfiguration = "Release"
|
||||
revealArchiveInOrganizer = "YES">
|
||||
</ArchiveAction>
|
||||
</Scheme>
|
||||
@@ -0,0 +1,10 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<Workspace
|
||||
version = "1.0">
|
||||
<FileRef
|
||||
location = "group:LNReader.xcodeproj">
|
||||
</FileRef>
|
||||
<FileRef
|
||||
location = "group:Pods/Pods.xcodeproj">
|
||||
</FileRef>
|
||||
</Workspace>
|
||||
@@ -0,0 +1,71 @@
|
||||
import UIKit
|
||||
import Expo
|
||||
import React
|
||||
import React_RCTAppDelegate
|
||||
import ReactAppDependencyProvider
|
||||
import Lottie
|
||||
|
||||
@main
|
||||
class AppDelegate: ExpoAppDelegate {
|
||||
var window: UIWindow?
|
||||
|
||||
var reactNativeDelegate: ReactNativeDelegate?
|
||||
var reactNativeFactory: RCTReactNativeFactory?
|
||||
|
||||
override func application(
|
||||
_ application: UIApplication,
|
||||
didFinishLaunchingWithOptions launchOptions: [UIApplication.LaunchOptionsKey: Any]? = nil
|
||||
) -> Bool {
|
||||
let delegate = ReactNativeDelegate()
|
||||
let factory = ExpoReactNativeFactory(delegate: delegate)
|
||||
delegate.dependencyProvider = RCTAppDependencyProvider()
|
||||
|
||||
reactNativeDelegate = delegate
|
||||
reactNativeFactory = factory
|
||||
bindReactNativeFactory(factory)
|
||||
window = UIWindow(frame: UIScreen.main.bounds)
|
||||
|
||||
factory.startReactNative(
|
||||
withModuleName: "main",
|
||||
in: window,
|
||||
launchOptions: launchOptions
|
||||
)
|
||||
|
||||
if let w = window {
|
||||
let splashScreenBackground = UIColor(red: 31/255, green: 32/255, blue: 36/255, alpha: 1)
|
||||
w.backgroundColor = splashScreenBackground
|
||||
w.rootViewController?.view.backgroundColor = splashScreenBackground
|
||||
let t = Dynamic()
|
||||
let animationSize = CGSize(width: 200, height: 200)
|
||||
let screenBounds = UIScreen.main.bounds
|
||||
let animationX = (screenBounds.width - animationSize.width) / 2
|
||||
let animationY = (screenBounds.height - animationSize.height) / 2
|
||||
let animationUIView: UIView = t.createAnimationView(rootView: UIView(frame:CGRect(x: animationX, y: animationY, width: animationSize.width, height: animationSize.height)), lottieName:"loading")
|
||||
RNSplashScreen.showLottieSplash(animationUIView, inRootView: w)
|
||||
animationUIView.backgroundColor = UIColor(white: 1, alpha:0)
|
||||
t.play(animationView: animationUIView as! AnimationView)
|
||||
RNSplashScreen.setAnimationFinished(true)
|
||||
}
|
||||
|
||||
return super.application(application, didFinishLaunchingWithOptions: launchOptions)
|
||||
}
|
||||
|
||||
override func application(_ application: UIApplication, open url: URL, options: [UIApplication.OpenURLOptionsKey : Any] = [:]) -> Bool {
|
||||
return RCTLinkingManager.application(application, open: url, options: options);
|
||||
}
|
||||
}
|
||||
|
||||
class ReactNativeDelegate: ExpoReactNativeFactoryDelegate {
|
||||
override func sourceURL(for bridge: RCTBridge) -> URL? {
|
||||
// needed to return the correct URL for expo-dev-client.
|
||||
bridge.bundleURL ?? bundleURL()
|
||||
}
|
||||
|
||||
override func bundleURL() -> URL? {
|
||||
#if DEBUG
|
||||
RCTBundleURLProvider.sharedSettings().jsBundleURL(forBundleRoot: "index")
|
||||
#else
|
||||
Bundle.main.url(forResource: "main", withExtension: "jsbundle")
|
||||
#endif
|
||||
}
|
||||
}
|
||||
|
After Width: | Height: | Size: 110 KiB |
|
After Width: | Height: | Size: 10 KiB |
|
After Width: | Height: | Size: 11 KiB |
|
After Width: | Height: | Size: 16 KiB |
|
After Width: | Height: | Size: 1.8 KiB |
|
After Width: | Height: | Size: 2.9 KiB |
|
After Width: | Height: | Size: 4.5 KiB |
|
After Width: | Height: | Size: 4.7 KiB |
|
After Width: | Height: | Size: 4.9 KiB |
|
After Width: | Height: | Size: 6.9 KiB |
|
After Width: | Height: | Size: 7.6 KiB |
@@ -0,0 +1 @@
|
||||
{"images":[{"size":"60x60","expected-size":"180","filename":"180.png","folder":"Assets.xcassets/AppIcon.appiconset/","idiom":"iphone","scale":"3x"},{"size":"40x40","expected-size":"80","filename":"80.png","folder":"Assets.xcassets/AppIcon.appiconset/","idiom":"iphone","scale":"2x"},{"size":"40x40","expected-size":"120","filename":"120.png","folder":"Assets.xcassets/AppIcon.appiconset/","idiom":"iphone","scale":"3x"},{"size":"60x60","expected-size":"120","filename":"120.png","folder":"Assets.xcassets/AppIcon.appiconset/","idiom":"iphone","scale":"2x"},{"size":"57x57","expected-size":"57","filename":"57.png","folder":"Assets.xcassets/AppIcon.appiconset/","idiom":"iphone","scale":"1x"},{"size":"29x29","expected-size":"58","filename":"58.png","folder":"Assets.xcassets/AppIcon.appiconset/","idiom":"iphone","scale":"2x"},{"size":"29x29","expected-size":"29","filename":"29.png","folder":"Assets.xcassets/AppIcon.appiconset/","idiom":"iphone","scale":"1x"},{"size":"29x29","expected-size":"87","filename":"87.png","folder":"Assets.xcassets/AppIcon.appiconset/","idiom":"iphone","scale":"3x"},{"size":"57x57","expected-size":"114","filename":"114.png","folder":"Assets.xcassets/AppIcon.appiconset/","idiom":"iphone","scale":"2x"},{"size":"20x20","expected-size":"40","filename":"40.png","folder":"Assets.xcassets/AppIcon.appiconset/","idiom":"iphone","scale":"2x"},{"size":"20x20","expected-size":"60","filename":"60.png","folder":"Assets.xcassets/AppIcon.appiconset/","idiom":"iphone","scale":"3x"},{"size":"1024x1024","filename":"1024.png","expected-size":"1024","idiom":"ios-marketing","folder":"Assets.xcassets/AppIcon.appiconset/","scale":"1x"}]}
|
||||
@@ -0,0 +1,66 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
|
||||
<plist version="1.0">
|
||||
<dict>
|
||||
<key>CFBundleDevelopmentRegion</key>
|
||||
<string>en</string>
|
||||
<key>CFBundleDisplayName</key>
|
||||
<string>LNReader</string>
|
||||
<key>CFBundleExecutable</key>
|
||||
<string>$(EXECUTABLE_NAME)</string>
|
||||
<key>CFBundleIdentifier</key>
|
||||
<string>$(PRODUCT_BUNDLE_IDENTIFIER)</string>
|
||||
<key>CFBundleInfoDictionaryVersion</key>
|
||||
<string>6.0</string>
|
||||
<key>CFBundleName</key>
|
||||
<string>$(PRODUCT_NAME)</string>
|
||||
<key>CFBundlePackageType</key>
|
||||
<string>APPL</string>
|
||||
<key>CFBundleShortVersionString</key>
|
||||
<string>$(MARKETING_VERSION)</string>
|
||||
<key>CFBundleSignature</key>
|
||||
<string>????</string>
|
||||
<key>CFBundleURLTypes</key>
|
||||
<array>
|
||||
<dict>
|
||||
<key>CFBundleTypeRole</key>
|
||||
<string>Editor</string>
|
||||
<key>CFBundleURLIconFile</key>
|
||||
<string></string>
|
||||
<key>CFBundleURLName</key>
|
||||
<string>LNReader</string>
|
||||
<key>CFBundleURLSchemes</key>
|
||||
<array>
|
||||
<string>lnreader</string>
|
||||
</array>
|
||||
</dict>
|
||||
</array>
|
||||
<key>CFBundleVersion</key>
|
||||
<string>$(CURRENT_PROJECT_VERSION)</string>
|
||||
<key>LSRequiresIPhoneOS</key>
|
||||
<true/>
|
||||
<key>NSAppTransportSecurity</key>
|
||||
<dict>
|
||||
<key>NSAllowsArbitraryLoads</key>
|
||||
<false/>
|
||||
<key>NSAllowsLocalNetworking</key>
|
||||
<true/>
|
||||
</dict>
|
||||
<key>NSLocationWhenInUseUsageDescription</key>
|
||||
<string></string>
|
||||
<key>UILaunchStoryboardName</key>
|
||||
<string>LaunchScreen</string>
|
||||
<key>UIRequiredDeviceCapabilities</key>
|
||||
<array>
|
||||
<string>arm64</string>
|
||||
</array>
|
||||
<key>UISupportedInterfaceOrientations</key>
|
||||
<array>
|
||||
<string>UIInterfaceOrientationPortrait</string>
|
||||
<string>UIInterfaceOrientationLandscapeLeft</string>
|
||||
<string>UIInterfaceOrientationLandscapeRight</string>
|
||||
</array>
|
||||
<key>UIViewControllerBasedStatusBarAppearance</key>
|
||||
<false/>
|
||||
</dict>
|
||||
</plist>
|
||||
@@ -0,0 +1,14 @@
|
||||
//
|
||||
// LNReader-Bridging-Header.h
|
||||
// LNReader
|
||||
//
|
||||
// Created by QUAN on 17/5/25.
|
||||
//
|
||||
|
||||
#ifndef LNReader_Bridging_Header_h
|
||||
#define LNReader_Bridging_Header_h
|
||||
|
||||
#import "RNSplashScreen.h"
|
||||
#import <React/RCTLinkingManager.h>
|
||||
|
||||
#endif /* LNReader_Bridging_Header_h */
|
||||
@@ -0,0 +1,28 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<document type="com.apple.InterfaceBuilder3.CocoaTouch.Storyboard.XIB" version="3.0" toolsVersion="23504" targetRuntime="iOS.CocoaTouch" propertyAccessControl="none" useAutolayout="YES" launchScreen="YES" useTraitCollections="YES" useSafeAreas="YES" colorMatched="YES" initialViewController="01J-lp-oVM">
|
||||
<device id="retina4_7" orientation="portrait" appearance="light"/>
|
||||
<dependencies>
|
||||
<deployment identifier="iOS"/>
|
||||
<plugIn identifier="com.apple.InterfaceBuilder.IBCocoaTouchPlugin" version="23506"/>
|
||||
<capability name="Safe area layout guides" minToolsVersion="9.0"/>
|
||||
<capability name="documents saved in the Xcode 8 format" minToolsVersion="8.0"/>
|
||||
</dependencies>
|
||||
<scenes>
|
||||
<!--View Controller-->
|
||||
<scene sceneID="EHf-IW-A2E">
|
||||
<objects>
|
||||
<viewController id="01J-lp-oVM" sceneMemberID="viewController">
|
||||
<view key="view" contentMode="scaleToFill" id="Ze5-6b-2t3">
|
||||
<rect key="frame" x="0.0" y="0.0" width="375" height="667"/>
|
||||
<autoresizingMask key="autoresizingMask" widthSizable="YES" heightSizable="YES"/>
|
||||
<viewLayoutGuide key="safeArea" id="Bcu-3y-fUS"/>
|
||||
<color key="backgroundColor" red="0.12156862745098039" green="0.12549019607843137" blue="0.14117647058823529" alpha="1" colorSpace="custom" customColorSpace="displayP3"/>
|
||||
<color key="tintColor" red="0.12156862745098039" green="0.12549019607843137" blue="0.14117647058823529" alpha="1" colorSpace="custom" customColorSpace="displayP3"/>
|
||||
</view>
|
||||
</viewController>
|
||||
<placeholder placeholderIdentifier="IBFirstResponder" id="iYj-Kq-Ea1" userLabel="First Responder" sceneMemberID="firstResponder"/>
|
||||
</objects>
|
||||
<point key="canvasLocation" x="52.173913043478265" y="375"/>
|
||||
</scene>
|
||||
</scenes>
|
||||
</document>
|
||||
@@ -0,0 +1,48 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
|
||||
<plist version="1.0">
|
||||
<dict>
|
||||
<key>NSPrivacyAccessedAPITypes</key>
|
||||
<array>
|
||||
<dict>
|
||||
<key>NSPrivacyAccessedAPIType</key>
|
||||
<string>NSPrivacyAccessedAPICategoryFileTimestamp</string>
|
||||
<key>NSPrivacyAccessedAPITypeReasons</key>
|
||||
<array>
|
||||
<string>C617.1</string>
|
||||
<string>0A2A.1</string>
|
||||
<string>3B52.1</string>
|
||||
</array>
|
||||
</dict>
|
||||
<dict>
|
||||
<key>NSPrivacyAccessedAPIType</key>
|
||||
<string>NSPrivacyAccessedAPICategoryUserDefaults</string>
|
||||
<key>NSPrivacyAccessedAPITypeReasons</key>
|
||||
<array>
|
||||
<string>CA92.1</string>
|
||||
</array>
|
||||
</dict>
|
||||
<dict>
|
||||
<key>NSPrivacyAccessedAPIType</key>
|
||||
<string>NSPrivacyAccessedAPICategoryDiskSpace</string>
|
||||
<key>NSPrivacyAccessedAPITypeReasons</key>
|
||||
<array>
|
||||
<string>E174.1</string>
|
||||
<string>85F4.1</string>
|
||||
</array>
|
||||
</dict>
|
||||
<dict>
|
||||
<key>NSPrivacyAccessedAPIType</key>
|
||||
<string>NSPrivacyAccessedAPICategorySystemBootTime</string>
|
||||
<key>NSPrivacyAccessedAPITypeReasons</key>
|
||||
<array>
|
||||
<string>35F9.1</string>
|
||||
</array>
|
||||
</dict>
|
||||
</array>
|
||||
<key>NSPrivacyCollectedDataTypes</key>
|
||||
<array/>
|
||||
<key>NSPrivacyTracking</key>
|
||||
<false/>
|
||||
</dict>
|
||||
</plist>
|
||||
@@ -0,0 +1,10 @@
|
||||
#import <Foundation/Foundation.h>
|
||||
#import <LNReaderSpec/LNReaderSpec.h>
|
||||
|
||||
NS_ASSUME_NONNULL_BEGIN
|
||||
|
||||
@interface RCTNativeEpubUtil : NSObject<NativeEpubUtilSpec>
|
||||
|
||||
@end
|
||||
|
||||
NS_ASSUME_NONNULL_END
|
||||
@@ -0,0 +1,22 @@
|
||||
//
|
||||
|
||||
#import "RCTNativeEpubUtil.h"
|
||||
|
||||
@implementation RCTNativeEpubUtil
|
||||
|
||||
|
||||
+ (NSString *)moduleName {
|
||||
return @"NativeEpubUtil";
|
||||
}
|
||||
|
||||
- (std::shared_ptr<facebook::react::TurboModule>)getTurboModule:(const facebook::react::ObjCTurboModule::InitParams &)params {
|
||||
return std::make_shared<facebook::react::NativeEpubUtilSpecJSI>(params);
|
||||
}
|
||||
|
||||
- (NSDictionary * _Nullable)parseNovelAndChapters:(nonnull NSString *)epubDirPath {
|
||||
NSMutableDictionary * res = [NSMutableDictionary dictionary];
|
||||
// TODO: implement parse epub
|
||||
return res;
|
||||
}
|
||||
|
||||
@end
|
||||
@@ -0,0 +1,10 @@
|
||||
#import <Foundation/Foundation.h>
|
||||
#import <LNReaderSpec/LNReaderSpec.h>
|
||||
|
||||
NS_ASSUME_NONNULL_BEGIN
|
||||
|
||||
@interface RCTNativeFile : NSObject <NativeFileSpec>
|
||||
|
||||
@end
|
||||
|
||||
NS_ASSUME_NONNULL_END
|
||||
@@ -0,0 +1,92 @@
|
||||
#import "RCTNativeFile.h"
|
||||
|
||||
@implementation RCTNativeFile
|
||||
|
||||
+ (NSString *)moduleName {
|
||||
return @"NativeFile";
|
||||
}
|
||||
|
||||
- (std::shared_ptr<facebook::react::TurboModule>)getTurboModule:(const facebook::react::ObjCTurboModule::InitParams &)params {
|
||||
return std::make_shared<facebook::react::NativeFileSpecJSI>(params);
|
||||
}
|
||||
|
||||
- (void)copyFile:(nonnull NSString *)sourcePath destPath:(nonnull NSString *)destPath {
|
||||
[[NSFileManager defaultManager] copyItemAtPath:sourcePath toPath:destPath error:nil];
|
||||
}
|
||||
|
||||
- (void)downloadFile:(nonnull NSString *)url destPath:(nonnull NSString *)destPath method:(nonnull NSString *)method headers:(nonnull NSDictionary *)headers body:(nonnull NSString *)body resolve:(nonnull RCTPromiseResolveBlock)resolve reject:(nonnull RCTPromiseRejectBlock)reject {
|
||||
}
|
||||
|
||||
- (nonnull NSNumber *)exists:(nonnull NSString *)filePath {
|
||||
|
||||
BOOL isExisted = [[NSFileManager defaultManager] fileExistsAtPath:filePath];
|
||||
|
||||
return [NSNumber numberWithBool:isExisted];
|
||||
}
|
||||
|
||||
- (NSDictionary *)getConstants {
|
||||
NSArray *paths = NSSearchPathForDirectoriesInDomains(NSDocumentDirectory, NSUserDomainMask, YES);
|
||||
NSString *documentsDirectory = [paths firstObject];
|
||||
|
||||
NSArray *cachePaths = NSSearchPathForDirectoriesInDomains(NSCachesDirectory, NSUserDomainMask, YES);
|
||||
NSString *cachesDirectory = [cachePaths firstObject];
|
||||
|
||||
return @{
|
||||
@"ExternalDirectoryPath": documentsDirectory,
|
||||
@"ExternalCachesDirectoryPath": cachesDirectory
|
||||
};
|
||||
}
|
||||
|
||||
- (void)mkdir:(nonnull NSString *)filePath {
|
||||
[
|
||||
[NSFileManager defaultManager] createDirectoryAtPath:filePath withIntermediateDirectories:YES attributes:nil error:nil
|
||||
];
|
||||
}
|
||||
|
||||
- (void)moveFile:(nonnull NSString *)sourcePath destPath:(nonnull NSString *)destPath {
|
||||
[
|
||||
[NSFileManager defaultManager] moveItemAtPath:sourcePath toPath:destPath error:nil
|
||||
];
|
||||
}
|
||||
|
||||
- (nonnull NSArray<NSDictionary *> *)readDir:(nonnull NSString *)dirPath {
|
||||
NSMutableArray<NSDictionary *>* res = [NSMutableArray array];
|
||||
NSArray *content = [
|
||||
[NSFileManager defaultManager]
|
||||
contentsOfDirectoryAtPath:dirPath error:nil
|
||||
];
|
||||
for(int i=0; i<content.count; i++){
|
||||
NSString *fileName = [content objectAtIndex:i];
|
||||
NSString *path = [dirPath stringByAppendingFormat:@"%@%@",@"/",fileName];
|
||||
BOOL isDir;
|
||||
[[NSFileManager defaultManager] fileExistsAtPath:path isDirectory:&isDir];
|
||||
[res addObject: @{
|
||||
@"name": fileName,
|
||||
@"path": path,
|
||||
@"isDirectory": @(isDir)
|
||||
}];
|
||||
}
|
||||
return res;
|
||||
}
|
||||
|
||||
- (nonnull NSString *)readFile:(nonnull NSString *)path {
|
||||
return [NSString stringWithContentsOfFile:path encoding:NSUTF8StringEncoding error:nil];
|
||||
}
|
||||
|
||||
- (void)unlink:(nonnull NSString *)filePath {
|
||||
[
|
||||
[NSFileManager defaultManager] removeItemAtPath:filePath error:nil
|
||||
];
|
||||
}
|
||||
|
||||
- (void)writeFile:(nonnull NSString *)path content:(nonnull NSString *)content {
|
||||
[content writeToFile:path atomically:YES encoding:NSUTF8StringEncoding error:nil];
|
||||
}
|
||||
|
||||
- (nonnull facebook::react::ModuleConstants<JS::NativeFile::Constants::Builder>)constantsToExport {
|
||||
return [self getConstants];
|
||||
}
|
||||
|
||||
|
||||
|
||||
@end
|
||||
@@ -0,0 +1,10 @@
|
||||
#import <Foundation/Foundation.h>
|
||||
#import <LNReaderSpec/LNReaderSpec.h>
|
||||
|
||||
NS_ASSUME_NONNULL_BEGIN
|
||||
|
||||
@interface RCTNativeVolumeButtonListener : NSObject<NativeVolumeButtonListenerSpec>
|
||||
|
||||
@end
|
||||
|
||||
NS_ASSUME_NONNULL_END
|
||||
@@ -0,0 +1,21 @@
|
||||
#import "RCTNativeVolumeButtonListener.h"
|
||||
|
||||
@implementation RCTNativeVolumeButtonListener
|
||||
|
||||
+ (NSString *)moduleName {
|
||||
return @"NativeVolumeButtonListener";
|
||||
}
|
||||
|
||||
- (std::shared_ptr<facebook::react::TurboModule>)getTurboModule:(const facebook::react::ObjCTurboModule::InitParams &)params {
|
||||
return std::make_shared<facebook::react::NativeVolumeButtonListenerSpecJSI>(params);
|
||||
}
|
||||
|
||||
- (void)addListener:(nonnull NSString *)eventName {
|
||||
// TODO: implement addlistener
|
||||
}
|
||||
|
||||
- (void)removeListeners:(double)count {
|
||||
// TODO: implement count listeners
|
||||
}
|
||||
|
||||
@end
|
||||
@@ -0,0 +1,17 @@
|
||||
//
|
||||
// RCTNativeZipArchive.h
|
||||
// LNReader
|
||||
//
|
||||
// Created by QUAN on 18/5/25.
|
||||
//
|
||||
|
||||
#import <Foundation/Foundation.h>
|
||||
#import <LNReaderSpec/LNReaderSpec.h>
|
||||
|
||||
NS_ASSUME_NONNULL_BEGIN
|
||||
|
||||
@interface RCTNativeZipArchive : NSObject<NativeZipArchiveSpec>
|
||||
|
||||
@end
|
||||
|
||||
NS_ASSUME_NONNULL_END
|
||||
@@ -0,0 +1,35 @@
|
||||
//
|
||||
// RCTNativeZipArchive.m
|
||||
// LNReader
|
||||
//
|
||||
// Created by QUAN on 18/5/25.
|
||||
//
|
||||
|
||||
#import "RCTNativeZipArchive.h"
|
||||
|
||||
@implementation RCTNativeZipArchive
|
||||
|
||||
+ (NSString *)moduleName {
|
||||
return @"NativeZipArchive";
|
||||
}
|
||||
|
||||
- (std::shared_ptr<facebook::react::TurboModule>)getTurboModule:(const facebook::react::ObjCTurboModule::InitParams &)params {
|
||||
return std::make_shared<facebook::react::NativeZipArchiveSpecJSI>(params);
|
||||
}
|
||||
|
||||
- (void)remoteUnzip:(nonnull NSString *)distDirPath url:(nonnull NSString *)url headers:(nonnull NSDictionary *)headers resolve:(nonnull RCTPromiseResolveBlock)resolve reject:(nonnull RCTPromiseRejectBlock)reject {
|
||||
// TODO: implement remoteUnzip
|
||||
}
|
||||
|
||||
- (void)remoteZip:(nonnull NSString *)sourceDirPath url:(nonnull NSString *)url headers:(nonnull NSDictionary *)headers resolve:(nonnull RCTPromiseResolveBlock)resolve reject:(nonnull RCTPromiseRejectBlock)reject {
|
||||
// TODO: implement remoteZip
|
||||
}
|
||||
|
||||
- (void)unzip:(nonnull NSString *)sourceFilePath distDirPath:(nonnull NSString *)distDirPath resolve:(nonnull RCTPromiseResolveBlock)resolve reject:(nonnull RCTPromiseRejectBlock)reject {
|
||||
// TODO: implement unzip
|
||||
}
|
||||
- (void)zip:(nonnull NSString *)sourceDirPath zipFilePath:(nonnull NSString *)zipFilePath resolve:(nonnull RCTPromiseResolveBlock)resolve reject:(nonnull RCTPromiseRejectBlock)reject {
|
||||
// TODO: implement zip
|
||||
}
|
||||
|
||||
@end
|
||||
@@ -0,0 +1,53 @@
|
||||
require File.join(File.dirname(`node --print "require.resolve('expo/package.json')"`), "scripts/autolinking")
|
||||
# Resolve react_native_pods.rb with node to allow for hoisting
|
||||
require Pod::Executable.execute_command('node', ['-p',
|
||||
'require.resolve(
|
||||
"react-native/scripts/react_native_pods.rb",
|
||||
{paths: [process.argv[1]]},
|
||||
)', __dir__]).strip
|
||||
|
||||
platform :ios, 15.5
|
||||
prepare_react_native_project!
|
||||
|
||||
linkage = ENV['USE_FRAMEWORKS']
|
||||
if linkage != nil
|
||||
Pod::UI.puts "Configuring Pod with #{linkage}ally linked Frameworks".green
|
||||
use_frameworks! :linkage => linkage.to_sym
|
||||
end
|
||||
|
||||
target 'LNReader' do
|
||||
use_expo_modules!
|
||||
|
||||
if ENV['EXPO_USE_COMMUNITY_AUTOLINKING'] == '1'
|
||||
config_command = ['node', '-e', "process.argv=['', '', 'config'];require('@react-native-community/cli').run()"];
|
||||
else
|
||||
config_command = [
|
||||
'node',
|
||||
'--no-warnings',
|
||||
'--eval',
|
||||
'require(require.resolve(\'expo-modules-autolinking\', { paths: [require.resolve(\'expo/package.json\')] }))(process.argv.slice(1))',
|
||||
'react-native-config',
|
||||
'--json',
|
||||
'--platform',
|
||||
'ios'
|
||||
]
|
||||
end
|
||||
|
||||
config = use_native_modules!(config_command)
|
||||
|
||||
use_react_native!(
|
||||
:path => config[:reactNativePath],
|
||||
# An absolute path to your application root.
|
||||
:app_path => "#{Pod::Config.instance.installation_root}/.."
|
||||
)
|
||||
|
||||
post_install do |installer|
|
||||
# https://github.com/facebook/react-native/blob/main/packages/react-native/scripts/react_native_pods.rb#L197-L202
|
||||
react_native_post_install(
|
||||
installer,
|
||||
config[:reactNativePath],
|
||||
:mac_catalyst_enabled => false,
|
||||
# :ccache_enabled => true
|
||||
)
|
||||
end
|
||||
end
|
||||
@@ -1,8 +1,15 @@
|
||||
// Learn more https://docs.expo.io/guides/customizing-metro
|
||||
|
||||
/**
|
||||
* Metro configuration
|
||||
* https://reactnative.dev/docs/metro
|
||||
*
|
||||
* @type {import('@react-native/metro-config').MetroConfig}
|
||||
*/
|
||||
|
||||
const path = require('path');
|
||||
const fs = require('fs');
|
||||
const { mergeConfig } = require('metro-config');
|
||||
const { getDefaultConfig } = require('@react-native/metro-config');
|
||||
const { getDefaultConfig, mergeConfig } = require('@react-native/metro-config');
|
||||
const defaultConfig = getDefaultConfig(__dirname);
|
||||
|
||||
const map = {
|
||||
@@ -15,6 +22,9 @@ const map = {
|
||||
'.jpg': 'image/jpeg',
|
||||
};
|
||||
const customConfig = {
|
||||
resolver: {
|
||||
unstable_enableSymlinks: true,
|
||||
},
|
||||
server: {
|
||||
port: 8081,
|
||||
enhanceMiddleware: (metroMiddleware, metroServer) => {
|
||||
|
||||
@@ -3,97 +3,131 @@
|
||||
"version": "2.0.0",
|
||||
"private": true,
|
||||
"scripts": {
|
||||
"android": "npm run prestart && react-native run-android --active-arch-only",
|
||||
"ios": "react-native run-ios",
|
||||
"prestart": "node scripts/setEnvFile.cjs Debug",
|
||||
"start": "react-native start",
|
||||
"lint": "eslint . --ext .js,.jsx,.ts,.tsx",
|
||||
"prettier": "prettier --write \"./src/**/*.{js,jsx,ts,tsx}\" ./scripts",
|
||||
"buildRelease": "node scripts/setEnvFile.cjs Release && cd android && ./gradlew clean && ./gradlew assembleRelease",
|
||||
"open": "open ./android/app/build/outputs/apk/release/",
|
||||
"strings": "node scripts/stringTypes.cjs",
|
||||
"prepare": "husky install"
|
||||
"dev:android": "pnpm run generate:env:debug && react-native run-android --appIdSuffix \"debug\" --active-arch-only",
|
||||
"dev:ios": "react-native run-ios",
|
||||
"dev:start": "react-native start",
|
||||
"dev:clean-start": "pnpm run dev:start -- --reset-cache",
|
||||
"build:release:android": "pnpm run generate:env:release && cd android && ./gradlew clean && ./gradlew assembleRelease",
|
||||
"build:open-apk": "open ./android/app/build/outputs/apk/release/",
|
||||
"generate:env:debug": "node scripts/generate-env-file.cjs Debug",
|
||||
"generate:env:release": "node scripts/generate-env-file.cjs Release",
|
||||
"generate:string-types": "node scripts/generate-string-types.cjs",
|
||||
"lint": "eslint ./src --ext .js,.jsx,.ts,.tsx",
|
||||
"lint:fix": "pnpm run lint -- --fix",
|
||||
"format": "prettier --write \"./src/**/*.{js,jsx,ts,tsx}\" ./scripts",
|
||||
"format:check": "prettier --check \"./src/**/*.{js,jsx,ts,tsx}\" ./scripts",
|
||||
"type-check": "tsc --noEmit",
|
||||
"clean:full": "rm -rf node_modules/ ./android/build/ ./android/app/build/ ./android/app/.cxx && pnpm i && cd android/ && ./gradlew clean && cd .. && pnpm run dev:start -- --reset-cache",
|
||||
"prepare": "husky install",
|
||||
"nix:shell": "nix develop --extra-experimental-features nix-command --extra-experimental-features flakes",
|
||||
"nix:emulator": "nix develop .#emulator --extra-experimental-features nix-command --extra-experimental-features flakes"
|
||||
},
|
||||
"codegenConfig": {
|
||||
"name": "LNReaderSpec",
|
||||
"type": "modules",
|
||||
"jsSrcsDir": "specs",
|
||||
"android": {
|
||||
"javaPackageName": "com.lnreader.spec"
|
||||
},
|
||||
"ios": {
|
||||
"modulesProvider": {
|
||||
"NativeEpubUtil": "RCTNativeEpubUtil",
|
||||
"NativeFile": "RCTNativeFile",
|
||||
"NativeVolumeButtonListener": "RCTNativeVolumeButtonListener",
|
||||
"NativeZipArchive": "RCTNativeZipArchive"
|
||||
}
|
||||
}
|
||||
},
|
||||
"dependencies": {
|
||||
"@cd-z/react-native-epub-creator": "^2.0.4",
|
||||
"@gorhom/bottom-sheet": "^4.4.5",
|
||||
"@react-native-community/slider": "^4.4.2",
|
||||
"@cd-z/react-native-epub-creator": "^3.0.0",
|
||||
"@gorhom/bottom-sheet": "^5.2.6",
|
||||
"@legendapp/list": "^2.0.14",
|
||||
"@react-native-community/slider": "^5.0.1",
|
||||
"@react-native-cookies/cookies": "^6.2.1",
|
||||
"@react-native-google-signin/google-signin": "^11.0.0",
|
||||
"@react-navigation/native": "^6.1.9",
|
||||
"@react-navigation/stack": "^6.0.11",
|
||||
"@shopify/flash-list": "^1.4.3",
|
||||
"cheerio": "^1.0.0-rc.12",
|
||||
"color": "^4.2.3",
|
||||
"dayjs": "^1.11.10",
|
||||
"expo": "^49.0.23",
|
||||
"expo-clipboard": "~4.3.1",
|
||||
"expo-document-picker": "~11.5.4",
|
||||
"expo-file-system": "~15.4.5",
|
||||
"expo-haptics": "~12.4.0",
|
||||
"expo-keep-awake": "~12.3.0",
|
||||
"expo-linear-gradient": "~12.3.0",
|
||||
"expo-linking": "~5.0.2",
|
||||
"expo-localization": "~14.3.0",
|
||||
"expo-navigation-bar": "^2.8.1",
|
||||
"expo-notifications": "~0.20.1",
|
||||
"expo-speech": "~11.3.0",
|
||||
"expo-sqlite": "~11.3.3",
|
||||
"expo-web-browser": "~12.3.2",
|
||||
"htmlparser2": "^9.1.0",
|
||||
"i18n-js": "^3.8.0",
|
||||
"@react-native-documents/picker": "^10.1.7",
|
||||
"@react-native-google-signin/google-signin": "^16.0.0",
|
||||
"@react-native-vector-icons/common": "^12.3.0",
|
||||
"@react-native-vector-icons/material-design-icons": "^12.3.0",
|
||||
"@react-native/codegen": "^0.81.1",
|
||||
"@react-native/gradle-plugin": "^0.81.1",
|
||||
"@react-navigation/bottom-tabs": "^7.4.7",
|
||||
"@react-navigation/native": "^7.1.17",
|
||||
"@react-navigation/native-stack": "^7.3.26",
|
||||
"@react-navigation/stack": "^7.4.8",
|
||||
"@shopify/flash-list": "2.0.2",
|
||||
"cheerio": "1.0.0-rc.12",
|
||||
"color": "^5.0.0",
|
||||
"dayjs": "^1.11.18",
|
||||
"expo": "^54.0.2",
|
||||
"expo-clipboard": "~8.0.7",
|
||||
"expo-document-picker": "~14.0.7",
|
||||
"expo-file-system": "~19.0.12",
|
||||
"expo-haptics": "~15.0.7",
|
||||
"expo-keep-awake": "~15.0.7",
|
||||
"expo-linear-gradient": "~15.0.7",
|
||||
"expo-linking": "~8.0.8",
|
||||
"expo-localization": "~17.0.7",
|
||||
"expo-navigation-bar": "~5.0.8",
|
||||
"expo-notifications": "~0.32.11",
|
||||
"expo-speech": "~14.0.7",
|
||||
"expo-sqlite": "~16.0.8",
|
||||
"expo-web-browser": "~15.0.7",
|
||||
"htmlparser2": "^10.0.0",
|
||||
"i18n-js": "^4.5.1",
|
||||
"lodash-es": "^4.17.21",
|
||||
"protobufjs": "^7.2.6",
|
||||
"react": "18.2.0",
|
||||
"react-native": "0.72.10",
|
||||
"react-native-background-actions": "^3.0.1",
|
||||
"react-native-device-info": "^8.4.7",
|
||||
"react-native-drawer-layout": "^3.2.2",
|
||||
"react-native-error-boundary": "^1.1.16",
|
||||
"react-native-gesture-handler": "~2.12.0",
|
||||
"react-native-lottie-splash-screen": "^1.0.1",
|
||||
"react-native-mmkv": "^2.11.0",
|
||||
"react-native-pager-view": "6.2.0",
|
||||
"react-native-paper": "^5.12.0",
|
||||
"react-native-reanimated": "^3.3.0",
|
||||
"lottie-ios": "^3.5.0",
|
||||
"protobufjs": "^7.5.4",
|
||||
"react": "19.1.0",
|
||||
"react-native": "^0.81.4",
|
||||
"react-native-background-actions": "^4.0.1",
|
||||
"react-native-device-info": "^14.0.4",
|
||||
"react-native-draggable-flatlist": "^4.0.3",
|
||||
"react-native-drawer-layout": "^4.1.13",
|
||||
"react-native-edge-to-edge": "^1.7.0",
|
||||
"react-native-error-boundary": "^2.0.0",
|
||||
"react-native-file-access": "^3.2.0",
|
||||
"react-native-gesture-handler": "^2.28.0",
|
||||
"react-native-lottie-splash-screen": "^1.1.2",
|
||||
"react-native-mmkv": "^3.3.1",
|
||||
"react-native-pager-view": "^6.9.1",
|
||||
"react-native-paper": "^5.14.5",
|
||||
"react-native-reanimated": "^4.1.0",
|
||||
"react-native-saf-x": "^2.2.3",
|
||||
"react-native-safe-area-context": "4.6.3",
|
||||
"react-native-screens": "^3.22.0",
|
||||
"react-native-safe-area-context": "^5.6.1",
|
||||
"react-native-screens": "^4.16.0",
|
||||
"react-native-shimmer-placeholder": "^2.0.9",
|
||||
"react-native-tab-view": "^3.5.2",
|
||||
"react-native-tab-view": "^4.1.3",
|
||||
"react-native-url-polyfill": "^2.0.0",
|
||||
"react-native-vector-icons": "^9.0.0",
|
||||
"react-native-webview": "^13.10.5",
|
||||
"react-native-zip-archive": "^7.0.0",
|
||||
"sanitize-html": "^2.13.0",
|
||||
"react-native-webview": "13.15.0",
|
||||
"react-native-worklets": "^0.5.1",
|
||||
"react-native-zip-archive": "^7.0.2",
|
||||
"sanitize-html": "^2.17.0",
|
||||
"urlencode": "^2.0.0"
|
||||
},
|
||||
"devDependencies": {
|
||||
"@babel/core": "^7.20.0",
|
||||
"@babel/runtime": "^7.20.0",
|
||||
"@react-native/eslint-config": "^0.72.2",
|
||||
"@react-native/metro-config": "^0.72.12",
|
||||
"@tsconfig/react-native": "^3.0.0",
|
||||
"@types/color": "^3.0.3",
|
||||
"@types/i18n-js": "^3.8.2",
|
||||
"@types/lodash-es": "^4.17.6",
|
||||
"@types/react": "^18.0.24",
|
||||
"@types/react-native-vector-icons": "^6.4.10",
|
||||
"@types/sanitize-html": "^2.11.0",
|
||||
"@typescript-eslint/eslint-plugin": "^5.10.2",
|
||||
"@typescript-eslint/parser": "^5.10.2",
|
||||
"babel-plugin-module-resolver": "^4.1.0",
|
||||
"eslint": "^8.19.0",
|
||||
"eslint-plugin-react-native": "^4.1.0",
|
||||
"@babel/core": "^7.28.4",
|
||||
"@babel/plugin-transform-export-namespace-from": "^7.27.1",
|
||||
"@babel/preset-env": "^7.28.3",
|
||||
"@babel/runtime": "^7.28.4",
|
||||
"@react-native-community/cli": "^20.0.2",
|
||||
"@react-native-community/cli-platform-android": "^20.0.2",
|
||||
"@react-native-community/cli-platform-ios": "^20.0.2",
|
||||
"@react-native/babel-preset": "^0.81.1",
|
||||
"@react-native/eslint-config": "^0.81.1",
|
||||
"@react-native/metro-config": "^0.81.1",
|
||||
"@react-native/typescript-config": "^0.81.1",
|
||||
"@types/color": "^4.2.0",
|
||||
"@types/lodash-es": "^4.17.12",
|
||||
"@types/react": "~19.1.12",
|
||||
"@types/sanitize-html": "^2.16.0",
|
||||
"babel-plugin-module-resolver": "^5.0.2",
|
||||
"babel-plugin-react-compiler": "19.1.0-rc.3",
|
||||
"eslint": "^8.57.1",
|
||||
"husky": "^7.0.4",
|
||||
"lint-staged": "^12.3.7",
|
||||
"metro-react-native-babel-preset": "^0.76.9",
|
||||
"prettier": "^2.4.1",
|
||||
"react-native-dotenv": "^3.4.9",
|
||||
"typescript": "^5.1.3"
|
||||
},
|
||||
"resolutions": {
|
||||
"@types/react": "^17"
|
||||
"lint-staged": "^12.5.0",
|
||||
"prettier": "2.8.8",
|
||||
"react-native-dotenv": "^3.4.11",
|
||||
"typescript": "~5.9.2"
|
||||
},
|
||||
"lint-staged": {
|
||||
"*.{js,jsx,ts,tsx}": [
|
||||
@@ -102,6 +136,7 @@
|
||||
]
|
||||
},
|
||||
"engines": {
|
||||
"node": ">=16"
|
||||
}
|
||||
"node": ">=20"
|
||||
},
|
||||
"packageManager": "pnpm@9.15.0"
|
||||
}
|
||||
|
||||
@@ -1,410 +0,0 @@
|
||||
#!/usr/bin/python -u
|
||||
|
||||
'''
|
||||
Copyright 2009, The Android Open Source Project
|
||||
|
||||
Licensed under the Apache License, Version 2.0 (the "License");
|
||||
you may not use this file except in compliance with the License.
|
||||
You may obtain a copy of the License at
|
||||
|
||||
http://www.apache.org/licenses/LICENSE-2.0
|
||||
|
||||
Unless required by applicable law or agreed to in writing, software
|
||||
distributed under the License is distributed on an "AS IS" BASIS,
|
||||
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
See the License for the specific language governing permissions and
|
||||
limitations under the License.
|
||||
'''
|
||||
|
||||
# Script to highlight adb logcat output for console
|
||||
# Originally written by Jeff Sharkey, http://jsharkey.org/
|
||||
# Piping detection and popen() added by other Android team members
|
||||
# Package filtering and output improvements by Jake Wharton, http://jakewharton.com
|
||||
|
||||
import argparse
|
||||
import sys
|
||||
import re
|
||||
import subprocess
|
||||
from subprocess import PIPE
|
||||
from ctypes import *
|
||||
from platform import win32_ver
|
||||
|
||||
__version__ = '2.1.0+Win10'
|
||||
|
||||
win_ver = win32_ver()[0]
|
||||
show_colors = (win_ver == '10' or win_ver == '') and sys.stdout.isatty()
|
||||
|
||||
LOG_LEVELS = 'VDIWEF'
|
||||
LOG_LEVELS_MAP = dict([(LOG_LEVELS[i], i) for i in range(len(LOG_LEVELS))])
|
||||
parser = argparse.ArgumentParser(description='Filter logcat by package name')
|
||||
parser.add_argument('package', nargs='*', help='Application package name(s)')
|
||||
parser.add_argument('-w', '--tag-width', metavar='N', dest='tag_width', type=int, default=23, help='Width of log tag')
|
||||
parser.add_argument('-l', '--min-level', dest='min_level', type=str, choices=LOG_LEVELS+LOG_LEVELS.lower(), default='V', help='Minimum level to be displayed')
|
||||
parser.add_argument('--color-gc', dest='color_gc', action='store_true', help='Color garbage collection')
|
||||
parser.add_argument('--always-display-tags', dest='always_tags', action='store_true',help='Always display the tag name')
|
||||
parser.add_argument('--current', dest='current_app', action='store_true',help='Filter logcat by current running app')
|
||||
parser.add_argument('-s', '--serial', dest='device_serial', help='Device serial number (adb -s option)')
|
||||
parser.add_argument('-d', '--device', dest='use_device', action='store_true', help='Use first device for log input (adb -d option)')
|
||||
parser.add_argument('-e', '--emulator', dest='use_emulator', action='store_true', help='Use first emulator for log input (adb -e option)')
|
||||
parser.add_argument('-c', '--clear', dest='clear_logcat', action='store_true', help='Clear the entire log before running')
|
||||
parser.add_argument('-t', '--tag', dest='tag', action='append', help='Filter output by specified tag(s)')
|
||||
parser.add_argument('-i', '--ignore-tag', dest='ignored_tag', action='append', help='Filter output by ignoring specified tag(s)')
|
||||
parser.add_argument('-v', '--version', action='version', version='%(prog)s ' + __version__, help='Print the version number and exit')
|
||||
parser.add_argument('-a', '--all', dest='all', action='store_true', default=False, help='Print all log messages')
|
||||
parser.add_argument('-o', '--output', dest='output', type=str, default='', help='Output filename')
|
||||
parser.add_argument('-n', '--no-color', dest='no_color', action='store_true', help='Disable colors')
|
||||
parser.add_argument('-r', '--regex', dest='regex', type=str, help='Print only when matches REGEX and REGEX is regular expression logcat -e REGEX')
|
||||
|
||||
args = parser.parse_args()
|
||||
min_level = LOG_LEVELS_MAP[args.min_level.upper()]
|
||||
|
||||
if (args.no_color):
|
||||
show_colors = False
|
||||
|
||||
package = args.package
|
||||
if len(package) == 0:
|
||||
package.append('com.rajarsheechatterjee.LNReader')
|
||||
|
||||
# enabling Windows 10 VT100 support
|
||||
def enableVT100():
|
||||
STD_OUTPUT_HANDLE = -11
|
||||
ENABLE_VIRTUAL_TERMINAL_PROCESSING = 4
|
||||
|
||||
stdout = windll.kernel32.GetStdHandle(STD_OUTPUT_HANDLE)
|
||||
if stdout == -1:
|
||||
raise WinError()
|
||||
|
||||
mode = c_uint()
|
||||
if windll.kernel32.GetConsoleMode(stdout, byref(mode)) == 0:
|
||||
raise WinError()
|
||||
|
||||
mode.value = mode.value | ENABLE_VIRTUAL_TERMINAL_PROCESSING
|
||||
|
||||
if windll.kernel32.SetConsoleMode(stdout, mode) == 0:
|
||||
raise WinError()
|
||||
|
||||
if (show_colors):
|
||||
enableVT100()
|
||||
|
||||
output_file = False
|
||||
if (args.output):
|
||||
output_file = open(args.output, 'a+')
|
||||
|
||||
base_adb_command = ['adb']
|
||||
if args.device_serial:
|
||||
base_adb_command.extend(['-s', args.device_serial])
|
||||
if args.use_device:
|
||||
base_adb_command.append('-d')
|
||||
if args.use_emulator:
|
||||
base_adb_command.append('-e')
|
||||
|
||||
if args.current_app:
|
||||
system_dump_command = base_adb_command + ["shell", "dumpsys", "activity", "activities"]
|
||||
system_dump = subprocess.Popen(system_dump_command, stdout=PIPE, stderr=PIPE).communicate()[0]
|
||||
running_package_name = re.search(".*TaskRecord.*A[= ]([^ ^}]*)", system_dump).group(1)
|
||||
package.append(running_package_name)
|
||||
|
||||
if len(package) == 0:
|
||||
args.all = True
|
||||
|
||||
# Store the names of packages for which to match all processes.
|
||||
catchall_package = list(filter(lambda package: package.find(":") == -1, package))
|
||||
# Store the name of processes to match exactly.
|
||||
named_processes = filter(lambda package: package.find(":") != -1, package)
|
||||
# Convert default process names from <package>: (cli notation) to <package> (android notation) in the exact names match group.
|
||||
named_processes = map(lambda package: package if package.find(":") != len(package) - 1 else package[:-1], named_processes)
|
||||
|
||||
header_size = args.tag_width + 1 + 3 + 1 # space, level, space
|
||||
|
||||
width = -1
|
||||
try:
|
||||
# Get the current terminal width
|
||||
import fcntl, termios, struct
|
||||
h, width = struct.unpack('hh', fcntl.ioctl(0, termios.TIOCGWINSZ, struct.pack('hh', 0, 0)))
|
||||
except:
|
||||
pass
|
||||
|
||||
BLACK, RED, GREEN, YELLOW, BLUE, MAGENTA, CYAN, WHITE = range(8)
|
||||
|
||||
RESET = '\033[0m'
|
||||
|
||||
def termcolor(fg=None, bg=None):
|
||||
codes = []
|
||||
if fg is not None: codes.append('3%d' % fg)
|
||||
if bg is not None: codes.append('10%d' % bg)
|
||||
return '\033[%sm' % ';'.join(codes) if codes else ''
|
||||
|
||||
def colorize(message, fg=None, bg=None):
|
||||
return termcolor(fg, bg) + message + RESET
|
||||
|
||||
def indent_wrap(message):
|
||||
if width == -1:
|
||||
return message
|
||||
message = message.replace('\t', ' ')
|
||||
wrap_area = width - header_size
|
||||
messagebuf = ''
|
||||
current = 0
|
||||
while current < len(message):
|
||||
next = min(current + wrap_area, len(message))
|
||||
messagebuf += message[current:next]
|
||||
if next < len(message):
|
||||
messagebuf += '\n'
|
||||
messagebuf += ' ' * header_size
|
||||
current = next
|
||||
return messagebuf
|
||||
|
||||
|
||||
LAST_USED = [RED, GREEN, YELLOW, BLUE, MAGENTA, CYAN]
|
||||
KNOWN_TAGS = {
|
||||
'dalvikvm': WHITE,
|
||||
'Process': WHITE,
|
||||
'ActivityManager': WHITE,
|
||||
'ActivityThread': WHITE,
|
||||
'AndroidRuntime': CYAN,
|
||||
'jdwp': WHITE,
|
||||
'StrictMode': WHITE,
|
||||
'DEBUG': YELLOW,
|
||||
}
|
||||
|
||||
def allocate_color(tag):
|
||||
# this will allocate a unique format for the given tag
|
||||
# since we dont have very many colors, we always keep track of the LRU
|
||||
if tag not in KNOWN_TAGS:
|
||||
KNOWN_TAGS[tag] = LAST_USED[0]
|
||||
color = KNOWN_TAGS[tag]
|
||||
if color in LAST_USED:
|
||||
LAST_USED.remove(color)
|
||||
LAST_USED.append(color)
|
||||
return color
|
||||
|
||||
NO_COLOR = re.compile(r'\033\[.*?m')
|
||||
|
||||
def output(line):
|
||||
line_nocolor = '' if show_colors and not output_file else NO_COLOR.sub('', line)
|
||||
print(line if show_colors else line_nocolor)
|
||||
if (output_file):
|
||||
output_file.write(line_nocolor + '\n')
|
||||
|
||||
RULES = {
|
||||
# StrictMode policy violation; ~duration=319 ms: android.os.StrictMode$StrictModeDiskWriteViolation: policy=31 violation=1
|
||||
re.compile(r'^(StrictMode policy violation)(; ~duration=)(\d+ ms)')
|
||||
: r'%s\1%s\2%s\3%s' % (termcolor(RED), RESET, termcolor(YELLOW), RESET),
|
||||
}
|
||||
|
||||
# Only enable GC coloring if the user opted-in
|
||||
if args.color_gc:
|
||||
# GC_CONCURRENT freed 3617K, 29% free 20525K/28648K, paused 4ms+5ms, total 85ms
|
||||
key = re.compile(r'^(GC_(?:CONCURRENT|FOR_M?ALLOC|EXTERNAL_ALLOC|EXPLICIT) )(freed <?\d+.)(, \d+\% free \d+./\d+., )(paused \d+ms(?:\+\d+ms)?)')
|
||||
val = r'\1%s\2%s\3%s\4%s' % (termcolor(GREEN), RESET, termcolor(YELLOW), RESET)
|
||||
|
||||
RULES[key] = val
|
||||
|
||||
|
||||
TAGTYPES = {
|
||||
'V': colorize(' V ', fg=WHITE, bg=BLACK),
|
||||
'D': colorize(' D ', fg=BLACK, bg=BLUE),
|
||||
'I': colorize(' I ', fg=BLACK, bg=GREEN),
|
||||
'W': colorize(' W ', fg=BLACK, bg=YELLOW),
|
||||
'E': colorize(' E ', fg=BLACK, bg=RED),
|
||||
'F': colorize(' F ', fg=BLACK, bg=RED),
|
||||
}
|
||||
|
||||
PID_LINE = re.compile(r'^\w+\s+(\w+)\s+\w+\s+\w+\s+\w+\s+\w+\s+\w+\s+\w\s([\w|\.|\/]+)$')
|
||||
PID_START = re.compile(r'^.*: Start proc ([a-zA-Z0-9._:]+) for ([a-z]+ [^:]+): pid=(\d+) uid=(\d+) gids=(.*)$')
|
||||
PID_START_5_1 = re.compile(r'^.*: Start proc (\d+):([a-zA-Z0-9._:]+)/[a-z0-9]+ for (.*)$')
|
||||
PID_START_DALVIK = re.compile(r'^E/dalvikvm\(\s*(\d+)\): >>>>> ([a-zA-Z0-9._:]+) \[ userId:0 \| appId:(\d+) \]$')
|
||||
PID_KILL = re.compile(r'^Killing (\d+):([a-zA-Z0-9._:]+)/[^:]+: (.*)$')
|
||||
PID_LEAVE = re.compile(r'^No longer want ([a-zA-Z0-9._:]+) \(pid (\d+)\): .*$')
|
||||
PID_DEATH = re.compile(r'^Process ([a-zA-Z0-9._:]+) \(pid (\d+)\) has died.?$')
|
||||
LOG_LINE = re.compile(r'^([A-Z])/(.+?)\( *(\d+)\): (.*?)$')
|
||||
BUG_LINE = re.compile(r'.*nativeGetEnabledTags.*')
|
||||
BACKTRACE_LINE = re.compile(r'^#(.*?)pc\s(.*?)$')
|
||||
|
||||
adb_command = base_adb_command[:]
|
||||
adb_command.append('logcat')
|
||||
adb_command.extend(['-v', 'brief'])
|
||||
if args.regex:
|
||||
adb_command.extend(['-e',args.regex])
|
||||
|
||||
# Clear log before starting logcat
|
||||
if args.clear_logcat:
|
||||
adb_clear_command = list(adb_command)
|
||||
adb_clear_command.append('-c')
|
||||
adb_clear = subprocess.Popen(adb_clear_command)
|
||||
|
||||
while adb_clear.poll() is None:
|
||||
pass
|
||||
|
||||
# This is a ducktype of the subprocess.Popen object
|
||||
class FakeStdinProcess():
|
||||
def __init__(self):
|
||||
self.stdout = sys.stdin
|
||||
def poll(self):
|
||||
return None
|
||||
|
||||
if sys.stdin.isatty():
|
||||
adb = subprocess.Popen(adb_command, stdin=PIPE, stdout=PIPE)
|
||||
else:
|
||||
adb = FakeStdinProcess()
|
||||
pids = set()
|
||||
last_tag = None
|
||||
app_pid = None
|
||||
|
||||
def match_packages(token):
|
||||
if len(package) == 0:
|
||||
return True
|
||||
if token in named_processes:
|
||||
return True
|
||||
index = token.find(':')
|
||||
return (token in catchall_package) if index == -1 else (token[:index] in catchall_package)
|
||||
|
||||
def parse_death(tag, message):
|
||||
if tag != 'ActivityManager':
|
||||
return None, None
|
||||
kill = PID_KILL.match(message)
|
||||
if kill:
|
||||
pid = kill.group(1)
|
||||
package_line = kill.group(2)
|
||||
if match_packages(package_line) and pid in pids:
|
||||
return pid, package_line
|
||||
leave = PID_LEAVE.match(message)
|
||||
if leave:
|
||||
pid = leave.group(2)
|
||||
package_line = leave.group(1)
|
||||
if match_packages(package_line) and pid in pids:
|
||||
return pid, package_line
|
||||
death = PID_DEATH.match(message)
|
||||
if death:
|
||||
pid = death.group(2)
|
||||
package_line = death.group(1)
|
||||
if match_packages(package_line) and pid in pids:
|
||||
return pid, package_line
|
||||
return None, None
|
||||
|
||||
def parse_start_proc(line):
|
||||
start = PID_START_5_1.match(line)
|
||||
if start is not None:
|
||||
line_pid, line_package, target = start.groups()
|
||||
return line_package, target, line_pid, '', ''
|
||||
start = PID_START.match(line)
|
||||
if start is not None:
|
||||
line_package, target, line_pid, line_uid, line_gids = start.groups()
|
||||
return line_package, target, line_pid, line_uid, line_gids
|
||||
start = PID_START_DALVIK.match(line)
|
||||
if start is not None:
|
||||
line_pid, line_package, line_uid = start.groups()
|
||||
return line_package, '', line_pid, line_uid, ''
|
||||
return None
|
||||
|
||||
def tag_in_tags_regex(tag, tags):
|
||||
return any(re.match(r'^' + t + r'$', tag) for t in map(str.strip, tags))
|
||||
|
||||
ps_command = base_adb_command + ['shell', 'ps']
|
||||
ps_pid = subprocess.Popen(ps_command, stdin=PIPE, stdout=PIPE, stderr=PIPE)
|
||||
while True:
|
||||
try:
|
||||
line = ps_pid.stdout.readline().decode('utf-8', 'replace').strip()
|
||||
except KeyboardInterrupt:
|
||||
break
|
||||
if len(line) == 0:
|
||||
break
|
||||
|
||||
pid_match = PID_LINE.match(line)
|
||||
if pid_match is not None:
|
||||
pid = pid_match.group(1)
|
||||
proc = pid_match.group(2)
|
||||
if proc in catchall_package:
|
||||
seen_pids = True
|
||||
pids.add(pid)
|
||||
|
||||
while adb.poll() is None:
|
||||
try:
|
||||
line = adb.stdout.readline().decode('utf-8', 'replace').strip()
|
||||
except KeyboardInterrupt:
|
||||
break
|
||||
if len(line) == 0:
|
||||
break
|
||||
|
||||
bug_line = BUG_LINE.match(line)
|
||||
if bug_line is not None:
|
||||
continue
|
||||
|
||||
log_line = LOG_LINE.match(line)
|
||||
if log_line is None:
|
||||
continue
|
||||
|
||||
level, tag, owner, message = log_line.groups()
|
||||
tag = tag.strip()
|
||||
start = parse_start_proc(line)
|
||||
if start:
|
||||
line_package, target, line_pid, line_uid, line_gids = start
|
||||
if match_packages(line_package):
|
||||
pids.add(line_pid)
|
||||
|
||||
app_pid = line_pid
|
||||
|
||||
linebuf = '\n'
|
||||
linebuf += colorize(' ' * (header_size - 1), bg=WHITE)
|
||||
linebuf += indent_wrap(' Process %s created for %s\n' % (line_package, target))
|
||||
linebuf += colorize(' ' * (header_size - 1), bg=WHITE)
|
||||
linebuf += ' PID: %s UID: %s GIDs: %s' % (line_pid, line_uid, line_gids)
|
||||
linebuf += '\n'
|
||||
output(linebuf)
|
||||
last_tag = None # Ensure next log gets a tag printed
|
||||
|
||||
dead_pid, dead_pname = parse_death(tag, message)
|
||||
if dead_pid:
|
||||
pids.remove(dead_pid)
|
||||
linebuf = '\n'
|
||||
linebuf += colorize(' ' * (header_size - 1), bg=RED)
|
||||
linebuf += ' Process %s (PID: %s) ended' % (dead_pname, dead_pid)
|
||||
linebuf += '\n'
|
||||
output(linebuf)
|
||||
last_tag = None # Ensure next log gets a tag printed
|
||||
|
||||
# Make sure the backtrace is printed after a native crash
|
||||
if tag == 'DEBUG':
|
||||
bt_line = BACKTRACE_LINE.match(message.lstrip())
|
||||
if bt_line is not None:
|
||||
message = message.lstrip()
|
||||
owner = app_pid
|
||||
|
||||
if not args.all and owner not in pids:
|
||||
continue
|
||||
if level in LOG_LEVELS_MAP and LOG_LEVELS_MAP[level] < min_level:
|
||||
continue
|
||||
if args.ignored_tag and tag_in_tags_regex(tag, args.ignored_tag):
|
||||
continue
|
||||
if args.tag and not tag_in_tags_regex(tag, args.tag):
|
||||
continue
|
||||
|
||||
linebuf = ''
|
||||
|
||||
if args.tag_width > 0:
|
||||
# right-align tag title and allocate color if needed
|
||||
if tag != last_tag or args.always_tags:
|
||||
last_tag = tag
|
||||
color = allocate_color(tag)
|
||||
tag = tag[-args.tag_width:].rjust(args.tag_width)
|
||||
linebuf += colorize(tag, fg=color)
|
||||
else:
|
||||
linebuf += ' ' * args.tag_width
|
||||
linebuf += ' '
|
||||
|
||||
# write out level colored edge
|
||||
if level in TAGTYPES:
|
||||
linebuf += TAGTYPES[level]
|
||||
else:
|
||||
linebuf += ' ' + level + ' '
|
||||
linebuf += ' '
|
||||
|
||||
# format tag message using rules
|
||||
for matcher in RULES:
|
||||
replace = RULES[matcher]
|
||||
message = matcher.sub(replace, message)
|
||||
|
||||
linebuf += indent_wrap(message)
|
||||
output(linebuf)
|
||||
|
||||
if (output_file):
|
||||
output_file.close()
|
||||
@@ -0,0 +1,60 @@
|
||||
const fs = require('fs');
|
||||
const os = require('os');
|
||||
const path = require('path');
|
||||
const { execSync } = require('child_process');
|
||||
|
||||
const formattedDate = new Date().getTime();
|
||||
const commitHash = execSync('git rev-parse --short HEAD').toString().trim();
|
||||
const buildType = process.argv[2] || 'Beta';
|
||||
|
||||
const newEnvVars = [
|
||||
`BUILD_TYPE=${buildType}`,
|
||||
`GIT_HASH=${commitHash}`,
|
||||
`RELEASE_DATE=${formattedDate}`,
|
||||
`NODE_ENV=${buildType === 'Release' ? 'production' : 'development'}`,
|
||||
].join(os.EOL);
|
||||
|
||||
const envFilePath = path.join(__dirname, '..', '.env');
|
||||
let existingEnvData = '';
|
||||
|
||||
try {
|
||||
if (fs.existsSync(envFilePath)) {
|
||||
const existingContent = fs.readFileSync(envFilePath, 'utf8');
|
||||
|
||||
existingEnvData = existingContent
|
||||
.split(os.EOL)
|
||||
.filter(line => {
|
||||
const trimmedLine = line.trim();
|
||||
return (
|
||||
trimmedLine &&
|
||||
!trimmedLine.startsWith('BUILD_TYPE=') &&
|
||||
!trimmedLine.startsWith('GIT_HASH=') &&
|
||||
!trimmedLine.startsWith('RELEASE_DATE=') &&
|
||||
!trimmedLine.startsWith('NODE_ENV=')
|
||||
);
|
||||
})
|
||||
.join(os.EOL);
|
||||
}
|
||||
} catch (err) {
|
||||
console.warn('Warning: Could not read existing .env file:', err.message);
|
||||
}
|
||||
|
||||
const finalContent = existingEnvData
|
||||
? `${newEnvVars}${os.EOL}${existingEnvData}${os.EOL}`
|
||||
: `${newEnvVars}${os.EOL}`;
|
||||
|
||||
try {
|
||||
fs.writeFileSync(envFilePath, finalContent, 'utf8');
|
||||
|
||||
console.log(`Generated .env file for ${buildType} build\n`);
|
||||
console.table({
|
||||
BUILD_TYPE: buildType,
|
||||
GIT_HASH: commitHash,
|
||||
RELEASE_DATE: formattedDate,
|
||||
NODE_ENV: buildType === 'Release' ? 'production' : 'development',
|
||||
});
|
||||
console.log('\n');
|
||||
} catch (err) {
|
||||
console.error('Error: Could not write .env file:', err.message);
|
||||
process.exit(1);
|
||||
}
|
||||
@@ -1,44 +0,0 @@
|
||||
const fs = require('fs');
|
||||
const os = require('os');
|
||||
const path = require('path');
|
||||
const { execSync } = require('child_process');
|
||||
|
||||
const formattedDate = new Date().getTime();
|
||||
|
||||
const commitHash = execSync('git rev-parse --short HEAD').toString().trim();
|
||||
|
||||
const buildType = process.argv[2] || 'Beta';
|
||||
|
||||
let data =
|
||||
`BUILD_TYPE=${buildType}` +
|
||||
os.EOL +
|
||||
`GIT_HASH=${commitHash}` +
|
||||
os.EOL +
|
||||
`RELEASE_DATE=${formattedDate}`;
|
||||
|
||||
let existingEnvData = '';
|
||||
|
||||
fs.readFile(path.join(__dirname, '..', '.env'), 'utf8', (err, existingData) => {
|
||||
if (err) return;
|
||||
|
||||
existingEnvData = existingData
|
||||
.split(os.EOL)
|
||||
.filter(line => {
|
||||
return (
|
||||
!line.startsWith('BUILD_TYPE=') &&
|
||||
!line.startsWith('GIT_HASH=') &&
|
||||
!line.startsWith('RELEASE_DATE=')
|
||||
);
|
||||
})
|
||||
.join(os.EOL);
|
||||
});
|
||||
|
||||
if (existingEnvData) {
|
||||
data += os.EOL + existingEnvData;
|
||||
}
|
||||
|
||||
fs.writeFile(path.join(__dirname, '..', '.env'), data, 'utf8', err => {
|
||||
if (err) {
|
||||
console.log(err);
|
||||
}
|
||||
});
|
||||
@@ -0,0 +1,303 @@
|
||||
#include <string>
|
||||
#include <pugixml.hpp>
|
||||
#include <iostream>
|
||||
#include <unordered_map>
|
||||
#include <vector>
|
||||
#include <Epub.hpp>
|
||||
#include <sstream>
|
||||
|
||||
std::string join(const std::string &folder_path, const std::string &child_path)
|
||||
{
|
||||
std::string full_path = folder_path;
|
||||
if (!full_path.empty() && full_path.back() != '/')
|
||||
{
|
||||
full_path += '/';
|
||||
}
|
||||
|
||||
std::stringstream ss(child_path);
|
||||
std::string segment;
|
||||
while (std::getline(ss, segment, '/'))
|
||||
{
|
||||
if (segment == "..")
|
||||
{
|
||||
// Go up one level
|
||||
if (!full_path.empty())
|
||||
{
|
||||
size_t lastSlash = full_path.rfind('/', full_path.length() - 2);
|
||||
if (lastSlash != std::string::npos)
|
||||
{
|
||||
full_path.resize(lastSlash + 1);
|
||||
}
|
||||
else
|
||||
{
|
||||
full_path = "";
|
||||
}
|
||||
}
|
||||
}
|
||||
else if (segment != "." && !segment.empty())
|
||||
{
|
||||
full_path += segment + '/';
|
||||
}
|
||||
}
|
||||
|
||||
if (full_path.length() > 1 && full_path.back() == '/')
|
||||
{
|
||||
full_path.pop_back();
|
||||
}
|
||||
|
||||
return full_path;
|
||||
}
|
||||
|
||||
std::string getParentPath(const std::string &path)
|
||||
{
|
||||
if (path.empty())
|
||||
return "";
|
||||
|
||||
size_t pos = path.find_last_of("/\\");
|
||||
if (pos == std::string::npos)
|
||||
{
|
||||
return "";
|
||||
}
|
||||
|
||||
return path.substr(0, pos);
|
||||
}
|
||||
|
||||
std::string find_toc_href(const pugi::xml_document &opf_doc)
|
||||
{
|
||||
auto manifest = opf_doc.child("package").child("manifest");
|
||||
for (pugi::xml_node item : manifest.children("item"))
|
||||
{
|
||||
std::string media_type = item.attribute("media-type").value();
|
||||
std::string id = item.attribute("id").value();
|
||||
if (media_type == "application/x-dtbncx+xml" || id == "ncx" || id == "nav")
|
||||
{
|
||||
return item.attribute("href").value();
|
||||
}
|
||||
}
|
||||
return "";
|
||||
}
|
||||
|
||||
void parse_navele_recursive(const pugi::xml_node &parent,
|
||||
std::unordered_map<std::string, std::string> &href_to_label, std::string &nav_folder)
|
||||
{
|
||||
for (pugi::xml_node li : parent.children("li"))
|
||||
{
|
||||
pugi::xml_node a = li.child("a");
|
||||
if (a)
|
||||
{
|
||||
std::string href = a.attribute("href").as_string();
|
||||
std::string label = a.text().as_string();
|
||||
|
||||
size_t sharp = href.find('#');
|
||||
if (sharp != std::string::npos)
|
||||
href = href.substr(0, sharp);
|
||||
|
||||
if (!href.empty() && !label.empty())
|
||||
href_to_label[join(nav_folder, href)] = label;
|
||||
}
|
||||
|
||||
if (pugi::xml_node sublist = li.child("ol"))
|
||||
{
|
||||
parse_navele_recursive(sublist, href_to_label, nav_folder);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void parse_nav_xhtml(const std::string &nav_path,
|
||||
std::unordered_map<std::string, std::string> &path_to_label)
|
||||
{
|
||||
std::string nav_folder = getParentPath(nav_path);
|
||||
pugi::xml_document nav_doc;
|
||||
if (!nav_doc.load_file(nav_path.c_str()))
|
||||
return;
|
||||
|
||||
for (pugi::xpath_node nav : nav_doc.select_nodes("//nav"))
|
||||
{
|
||||
pugi::xml_node node = nav.node();
|
||||
std::string nav_type = node.attribute("epub:type").as_string();
|
||||
pugi::xml_node ol = node.child("ol");
|
||||
if (ol)
|
||||
{
|
||||
parse_navele_recursive(ol, path_to_label, nav_folder);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void parse_navpoint_recursive(const pugi::xml_node &navPoint,
|
||||
std::unordered_map<std::string, std::string> &result, std::string &ncx_folder)
|
||||
{
|
||||
for (pugi::xml_node point : navPoint.children("navPoint"))
|
||||
{
|
||||
std::string label;
|
||||
pugi::xml_node labelNode = point.child("navLabel").child("text");
|
||||
if (labelNode)
|
||||
label = labelNode.text().as_string();
|
||||
|
||||
std::string src;
|
||||
pugi::xml_node contentNode = point.child("content");
|
||||
if (contentNode)
|
||||
src = contentNode.attribute("src").as_string();
|
||||
|
||||
if (!label.empty() && !src.empty())
|
||||
{
|
||||
size_t sharp = src.find('#');
|
||||
if (sharp != std::string::npos)
|
||||
src = src.substr(0, sharp);
|
||||
result[join(ncx_folder, src)] = label;
|
||||
}
|
||||
|
||||
parse_navpoint_recursive(point, result, ncx_folder);
|
||||
}
|
||||
}
|
||||
|
||||
void parse_toc_ncx(const std::string &ncx_path, std::unordered_map<std::string, std::string> &href_to_label)
|
||||
{
|
||||
std::string ncx_folder = getParentPath(ncx_path);
|
||||
pugi::xml_document doc;
|
||||
if (!doc.load_file(ncx_path.c_str()))
|
||||
return;
|
||||
|
||||
pugi::xml_node navMap = doc.child("ncx").child("navMap");
|
||||
if (!navMap)
|
||||
return;
|
||||
|
||||
parse_navpoint_recursive(navMap, href_to_label, ncx_folder);
|
||||
}
|
||||
|
||||
void parse_opf_from_folder(const std::string &base_dir,
|
||||
const std::string &opf_rel_path,
|
||||
EpubMetadata &meta_out)
|
||||
{
|
||||
std::string opf_path = join(base_dir, opf_rel_path);
|
||||
std::string opf_dir = getParentPath(opf_path);
|
||||
pugi::xml_document opf_doc;
|
||||
if (!opf_doc.load_file(opf_path.c_str()))
|
||||
return;
|
||||
std::string version;
|
||||
pugi::xml_node package = opf_doc.child("package");
|
||||
if (package)
|
||||
{
|
||||
version = package.attribute("version").as_string();
|
||||
}
|
||||
|
||||
std::unordered_map<std::string, std::string> path_to_label;
|
||||
std::string toc_href = find_toc_href(opf_doc);
|
||||
if (toc_href.find("ncx") != std::string::npos)
|
||||
{
|
||||
std::string ncx_path = join(opf_dir, toc_href);
|
||||
parse_toc_ncx(ncx_path, path_to_label);
|
||||
}
|
||||
else
|
||||
{
|
||||
std::string nav_path = join(opf_dir, toc_href);
|
||||
parse_nav_xhtml(nav_path, path_to_label);
|
||||
}
|
||||
|
||||
auto metadata = opf_doc.child("package").child("metadata");
|
||||
meta_out.name = metadata.child("dc:title").text().as_string();
|
||||
meta_out.author = metadata.child("dc:creator").text().as_string();
|
||||
meta_out.artist = metadata.child("dc:contributor").text().as_string();
|
||||
meta_out.summary = metadata.child("dc:description").text().as_string();
|
||||
|
||||
std::unordered_map<std::string, std::string> id_to_href;
|
||||
|
||||
std::string cover_id;
|
||||
for (pugi::xml_node meta : metadata.children("meta"))
|
||||
{
|
||||
if (std::string(meta.attribute("name").value()) == "cover")
|
||||
{
|
||||
cover_id = meta.attribute("content").value();
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
auto manifest = opf_doc.child("package").child("manifest");
|
||||
for (pugi::xml_node item : manifest.children("item"))
|
||||
{
|
||||
std::string id = item.attribute("id").value();
|
||||
std::string href = item.attribute("href").value();
|
||||
std::string media_type = item.attribute("media-type").value();
|
||||
|
||||
id_to_href[id] = href;
|
||||
if (media_type == "text/css")
|
||||
{
|
||||
meta_out.cssPaths.push_back(join(opf_dir, href));
|
||||
}
|
||||
else if (media_type == "image/jpeg" || media_type == "image/png" || media_type == "image/jpg")
|
||||
{
|
||||
meta_out.imagePaths.push_back(join(opf_dir, href));
|
||||
}
|
||||
}
|
||||
|
||||
if (!cover_id.empty() && id_to_href.count(cover_id))
|
||||
{
|
||||
meta_out.cover = join(opf_dir, id_to_href[cover_id]);
|
||||
}
|
||||
|
||||
auto spine = opf_doc.child("package").child("spine");
|
||||
std::string prev_name = "";
|
||||
int part = 2;
|
||||
for (pugi::xml_node itemref : spine.children("itemref"))
|
||||
{
|
||||
std::string idref = itemref.attribute("idref").value();
|
||||
if (id_to_href.count(idref))
|
||||
{
|
||||
std::string chapter_href = id_to_href[idref];
|
||||
Chapter chapter;
|
||||
chapter.path = join(opf_dir, chapter_href);
|
||||
|
||||
if (path_to_label.count(chapter.path))
|
||||
{
|
||||
chapter.name = path_to_label[chapter.path];
|
||||
}
|
||||
if (chapter.name.empty())
|
||||
{
|
||||
if (prev_name.empty())
|
||||
{
|
||||
int start_pos = chapter_href.find_last_of("/");
|
||||
if (start_pos == std::string::npos)
|
||||
{
|
||||
start_pos = 0;
|
||||
}
|
||||
else
|
||||
{
|
||||
start_pos++;
|
||||
}
|
||||
chapter.name = chapter_href.substr(start_pos, chapter_href.find_last_of(".") - start_pos);
|
||||
}
|
||||
else
|
||||
{
|
||||
chapter.name = prev_name + " (" + std::to_string(part) + ")";
|
||||
part += 1;
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
prev_name = chapter.name;
|
||||
part = 2;
|
||||
}
|
||||
|
||||
meta_out.chapters.push_back(chapter);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
EpubMetadata parseEpub(const std::string epub_path)
|
||||
{
|
||||
std::string container_path = join(epub_path, "META-INF/container.xml");
|
||||
|
||||
pugi::xml_document container_doc;
|
||||
if (!container_doc.load_file(container_path.c_str()))
|
||||
throw std::runtime_error("Failed to load container.xml");
|
||||
|
||||
std::string opf_path = container_doc.child("container")
|
||||
.child("rootfiles")
|
||||
.child("rootfile")
|
||||
.attribute("full-path")
|
||||
.value();
|
||||
|
||||
EpubMetadata metadata;
|
||||
parse_opf_from_folder(epub_path, opf_path, metadata);
|
||||
|
||||
return metadata;
|
||||
}
|
||||
@@ -0,0 +1,28 @@
|
||||
#include <string>
|
||||
#include <vector>
|
||||
|
||||
#ifndef Epub_H
|
||||
#define Epub_H
|
||||
|
||||
struct Chapter
|
||||
{
|
||||
std::string name;
|
||||
std::string path;
|
||||
};
|
||||
|
||||
struct EpubMetadata
|
||||
{
|
||||
std::string name;
|
||||
std::string path;
|
||||
std::string cover;
|
||||
std::string summary;
|
||||
std::string author;
|
||||
std::string artist;
|
||||
std::vector<Chapter> chapters;
|
||||
std::vector<std::string> cssPaths;
|
||||
std::vector<std::string> imagePaths;
|
||||
};
|
||||
|
||||
EpubMetadata parseEpub(const std::string epub_path);
|
||||
|
||||
#endif
|
||||
@@ -0,0 +1,45 @@
|
||||
#include <NativeEpub.hpp>
|
||||
|
||||
namespace facebook::react
|
||||
{
|
||||
|
||||
NativeEpub::NativeEpub(std::shared_ptr<CallInvoker> jsInvoker)
|
||||
: NativeEpubCxxSpec(std::move(jsInvoker)) {}
|
||||
|
||||
jsi::Object NativeEpub::parseNovelAndChapters(jsi::Runtime &rt, jsi::String epubDirPath)
|
||||
{
|
||||
jsi::Object novel(rt);
|
||||
EpubMetadata metadata = parseEpub(epubDirPath.utf8(rt).c_str());
|
||||
novel.setProperty(rt, "name", metadata.name);
|
||||
novel.setProperty(rt, "author", metadata.author);
|
||||
novel.setProperty(rt, "artist", metadata.artist);
|
||||
novel.setProperty(rt, "summary", metadata.summary);
|
||||
novel.setProperty(rt, "cover", metadata.cover);
|
||||
|
||||
jsi::Array chapters(rt, metadata.chapters.size());
|
||||
for (int i = 0; i < metadata.chapters.size(); i++)
|
||||
{
|
||||
jsi::Object chapter(rt);
|
||||
chapter.setProperty(rt, "name", metadata.chapters[i].name);
|
||||
chapter.setProperty(rt, "path", metadata.chapters[i].path);
|
||||
chapters.setValueAtIndex(rt, i, chapter);
|
||||
}
|
||||
novel.setProperty(rt, "chapters", chapters);
|
||||
jsi::Array cssPaths(rt, metadata.cssPaths.size());
|
||||
for (int i = 0; i < metadata.cssPaths.size(); i++)
|
||||
{
|
||||
cssPaths.setValueAtIndex(rt, i, metadata.cssPaths[i]);
|
||||
}
|
||||
novel.setProperty(rt, "cssPaths", cssPaths);
|
||||
|
||||
jsi::Array imagePaths(rt, metadata.imagePaths.size());
|
||||
for (int i = 0; i < metadata.imagePaths.size(); i++)
|
||||
{
|
||||
imagePaths.setValueAtIndex(rt, i, metadata.imagePaths[i]);
|
||||
}
|
||||
novel.setProperty(rt, "imagePaths", imagePaths);
|
||||
|
||||
return novel;
|
||||
}
|
||||
|
||||
} // namespace facebook::react
|
||||