├── .babelrc ├── .buckconfig ├── .eslintrc.js ├── .flowconfig ├── .gitattributes ├── .gitignore ├── .prettierrc.js ├── .watchmanconfig ├── LICENSE ├── README.md ├── Root.js ├── __tests__ └── App-test.js ├── android ├── app │ ├── BUCK │ ├── build.gradle │ ├── build_defs.bzl │ ├── debug.keystore │ ├── proguard-rules.pro │ └── src │ │ ├── debug │ │ ├── AndroidManifest.xml │ │ └── java │ │ │ └── com │ │ │ └── standardebooklauncher │ │ │ └── ReactNativeFlipper.java │ │ └── main │ │ ├── AndroidManifest.xml │ │ ├── ic_launcher-playstore.png │ │ ├── java │ │ └── com │ │ │ └── se │ │ │ └── ebookmanager │ │ │ ├── InstalledAppsModule.java │ │ │ ├── InstalledAppsPackage.java │ │ │ ├── MainActivity.java │ │ │ └── MainApplication.java │ │ └── res │ │ ├── mipmap-hdpi │ │ ├── ic_launcher.png │ │ └── ic_launcher_round.png │ │ ├── mipmap-mdpi │ │ ├── ic_launcher.png │ │ └── ic_launcher_round.png │ │ ├── mipmap-xhdpi │ │ ├── ic_launcher.png │ │ └── ic_launcher_round.png │ │ ├── mipmap-xxhdpi │ │ ├── ic_launcher.png │ │ └── ic_launcher_round.png │ │ ├── mipmap-xxxhdpi │ │ ├── ic_launcher.png │ │ └── ic_launcher_round.png │ │ └── values │ │ ├── ic_launcher_background.xml │ │ ├── strings.xml │ │ └── styles.xml ├── build.gradle ├── gradle.properties ├── gradle │ └── wrapper │ │ ├── gradle-wrapper.jar │ │ └── gradle-wrapper.properties ├── gradlew ├── gradlew.bat └── settings.gradle ├── app.json ├── babel.config.js ├── image ├── original.png └── patch.png ├── index.js ├── metro.config.js ├── package.json ├── patches ├── @react-native-community+netinfo+9.3.6.patch ├── react-native+0.63.4.patch └── react-native-popup-menu+0.16.1.patch ├── src ├── App.js ├── Component │ ├── AppDetailModal.js │ ├── AppList.js │ ├── AppVersionSelectList.js │ ├── AppVersionSelectModal.js │ ├── BottomToolbar.js │ ├── CommonHeader.js │ ├── FloatingButton.js │ ├── GlobalContext.js │ ├── OSSLicense.js │ ├── OSSLicenseStack.js │ └── TransparentCircleButton.js └── MainScreen.js └── yarn.lock /.babelrc: -------------------------------------------------------------------------------- 1 | { 2 | "env": { 3 | "production": { 4 | "plugins": ["transform-remove-console"] 5 | } 6 | } 7 | } -------------------------------------------------------------------------------- /.buckconfig: -------------------------------------------------------------------------------- 1 | 2 | [android] 3 | target = Google Inc.:Google APIs:23 4 | 5 | [maven_repositories] 6 | central = https://repo1.maven.org/maven2 7 | -------------------------------------------------------------------------------- /.eslintrc.js: -------------------------------------------------------------------------------- 1 | module.exports = { 2 | root: true, 3 | extends: '@react-native-community', 4 | }; 5 | -------------------------------------------------------------------------------- /.flowconfig: -------------------------------------------------------------------------------- 1 | [ignore] 2 | ; We fork some components by platform 3 | .*/*[.]android.js 4 | 5 | ; Ignore "BUCK" generated dirs 6 | /\.buckd/ 7 | 8 | ; Ignore polyfills 9 | node_modules/react-native/Libraries/polyfills/.* 10 | 11 | ; These should not be required directly 12 | ; require from fbjs/lib instead: require('fbjs/lib/warning') 13 | node_modules/warning/.* 14 | 15 | ; Flow doesn't support platforms 16 | .*/Libraries/Utilities/LoadingView.js 17 | 18 | [untyped] 19 | .*/node_modules/@react-native-community/cli/.*/.* 20 | 21 | [include] 22 | 23 | [libs] 24 | node_modules/react-native/interface.js 25 | node_modules/react-native/flow/ 26 | 27 | [options] 28 | emoji=true 29 | 30 | esproposal.optional_chaining=enable 31 | esproposal.nullish_coalescing=enable 32 | 33 | module.file_ext=.js 34 | module.file_ext=.json 35 | module.file_ext=.ios.js 36 | 37 | munge_underscores=true 38 | 39 | module.name_mapper='^react-native/\(.*\)$' -> '/node_modules/react-native/\1' 40 | module.name_mapper='^@?[./a-zA-Z0-9$_-]+\.\(bmp\|gif\|jpg\|jpeg\|png\|psd\|svg\|webp\|m4v\|mov\|mp4\|mpeg\|mpg\|webm\|aac\|aiff\|caf\|m4a\|mp3\|wav\|html\|pdf\)$' -> '/node_modules/react-native/Libraries/Image/RelativeImageStub' 41 | 42 | suppress_type=$FlowIssue 43 | suppress_type=$FlowFixMe 44 | suppress_type=$FlowFixMeProps 45 | suppress_type=$FlowFixMeState 46 | 47 | suppress_comment=\\(.\\|\n\\)*\\$FlowFixMe\\($\\|[^(]\\|(\\(\\)? *\\(site=[a-z,_]*react_native\\(_ios\\)?_\\(oss\\|fb\\)[a-z,_]*\\)?)\\) 48 | suppress_comment=\\(.\\|\n\\)*\\$FlowIssue\\((\\(\\)? *\\(site=[a-z,_]*react_native\\(_ios\\)?_\\(oss\\|fb\\)[a-z,_]*\\)?)\\)?:? #[0-9]+ 49 | suppress_comment=\\(.\\|\n\\)*\\$FlowExpectedError 50 | 51 | [lints] 52 | sketchy-null-number=warn 53 | sketchy-null-mixed=warn 54 | sketchy-number=warn 55 | untyped-type-import=warn 56 | nonstrict-import=warn 57 | deprecated-type=warn 58 | unsafe-getters-setters=warn 59 | unnecessary-invariant=warn 60 | signature-verification-failure=warn 61 | deprecated-utility=error 62 | 63 | [strict] 64 | deprecated-type 65 | nonstrict-import 66 | sketchy-null 67 | unclear-type 68 | unsafe-getters-setters 69 | untyped-import 70 | untyped-type-import 71 | 72 | [version] 73 | ^0.122.0 74 | -------------------------------------------------------------------------------- /.gitattributes: -------------------------------------------------------------------------------- 1 | *.pbxproj -text 2 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | # OSX 2 | # 3 | .DS_Store 4 | 5 | # Xcode 6 | # 7 | build/ 8 | *.pbxuser 9 | !default.pbxuser 10 | *.mode1v3 11 | !default.mode1v3 12 | *.mode2v3 13 | !default.mode2v3 14 | *.perspectivev3 15 | !default.perspectivev3 16 | xcuserdata 17 | *.xccheckout 18 | *.moved-aside 19 | DerivedData 20 | *.hmap 21 | *.ipa 22 | *.xcuserstate 23 | 24 | # Android/IntelliJ 25 | # 26 | build/ 27 | .idea 28 | .gradle 29 | local.properties 30 | keystore.properties 31 | *.iml 32 | 33 | # node.js 34 | # 35 | node_modules/ 36 | npm-debug.log 37 | yarn-error.log 38 | 39 | # BUCK 40 | buck-out/ 41 | \.buckd/ 42 | *.keystore 43 | !debug.keystore 44 | 45 | # fastlane 46 | # 47 | # It is recommended to not store the screenshots in the git repo. Instead, use fastlane to re-generate the 48 | # screenshots whenever they are needed. 49 | # For more information about the recommended setup visit: 50 | # https://docs.fastlane.tools/best-practices/source-control/ 51 | 52 | */fastlane/report.xml 53 | */fastlane/Preview.html 54 | */fastlane/screenshots 55 | 56 | # Bundle artifact 57 | *.jsbundle 58 | 59 | # CocoaPods 60 | /ios/Pods/ 61 | -------------------------------------------------------------------------------- /.prettierrc.js: -------------------------------------------------------------------------------- 1 | module.exports = { 2 | bracketSpacing: false, 3 | jsxBracketSameLine: true, 4 | singleQuote: true, 5 | trailingComma: 'all', 6 | }; 7 | -------------------------------------------------------------------------------- /.watchmanconfig: -------------------------------------------------------------------------------- 1 | {} -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2022 Aestia Nerious 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # 모두의 이북 2 |     3 | 4 | 5 | 6 | 모두의 이북은 안드로이드 이북 리더 기기에서 편하게 이북 뷰어 앱을 설치할 수 있는 통합 설치 앱입니다. 7 |   8 | ## 다운로드 & 설치 9 | 10 | 최신버전 다운로드 11 | 12 | Release에서 최신 버전의 apk 파일을 다운로드 받아, 설치를 원하는 기기에 apk 파일을 넣고 설치해주세요. 13 | 14 | 모두의 이북은 안드로이드 4.1 이상 기기에서 설치가 가능합니다. (국내 출시 기준 크레마 사운드, 크레마 카르타 플러스 이상) 15 | 16 |
17 |
18 | Play 프로텍트에 의해 차단됨 팝업 19 | 검사을 위해 앱 전송 팝업 20 |
21 | 22 | Google play service를 지원하는 기기 (Onyx, Boyue 등)에서 앱 설치 시, Play 프로텍트에 의해 차단됨, 검사를 위해 앱을 전송할까요? 와 같은 메시지가 나올 수 있습니다. 23 | 24 | 해당 앱이 플레이스토어에 등록되지 않은 앱이기 때문에 나타나는 메시지이며, 무시하고 설치 를 눌러 설치를 해주세요. 25 | 26 | 앱 전송은 전송 혹은 전송하지 않음 둘 중 아무 선택을 하셔도 상관이 없습니다. 27 | 28 | 29 | 30 | ## 지원하는 뷰어 앱 31 | 2023년 7월 11일 기준, 모두의 이북을 통해 아래의 이북 뷰어 앱을 설치할 수 있습니다. 32 | 33 | (지원하는 이북 뷰어 앱은 추가/변경될 수 있습니다.) 34 | 35 | - 교보 eBook 36 | - 교보 eBook EInk 37 | - 교보도서관 38 | - 밀리의서재 39 | - 밀리의서재 EInk 40 | - 북큐브 41 | - 북큐브 전자도서관 42 | - 알라딘 eBook 43 | - 알라딘 전자도서관 44 | - 알라딘 전자도서관 (구독형) 45 | - 부커스 46 | - 예스24 eBook 47 | - MY YES (예스24 eBook Eink 버전) 48 | - 예스24 도서관 49 | - 크레마 전자도서관 (예스24 도서관 Eink 버전) 50 | - 예스24 전자도서관 리뉴얼 51 | - 예스24 전자도서관 리뉴얼 (EInk) 52 | - 원스토리 완전판 53 | - 네이버 시리즈 완전판 54 | - 카카오페이지 (일반판 및 완전판) 55 | - 웹소설 노벨피아 56 | - 웹소설 문피아 57 | - 웹소설 조아라 58 | - 웹소설 톡소다 59 | - 픽코마 ピッコマ 60 | - 리디북스 (Eink 구버전 및 완전판) 61 | - 아마존 킨들 62 | - Libby 63 | - Scribd 64 | 65 | ## 스크린샷 66 |
67 | 68 | 69 |
70 |
71 | 72 | 73 |
74 | 75 | ## 빌드 가이드 76 | 작성 중입니다. 빠른 시일 내에 공개 예정입니다. 77 | 78 | ## 오류 제보, 문의사항 안내 79 | [카카오톡 오픈채팅 문의](https://open.kakao.com/o/sS5IGSff) 80 | 81 | 카카오톡 오픈채팅을 통해 앱 사용 중 발생하는 오류 및 문의, 개선사항을 전달해주세요. 82 | 83 | 모두의 이북 앱 이외의 질문(서점사 앱 이용방법, 이북리더기 관련 등)에 대해서는 답변하지 않습니다. 84 | 85 | 오류 제보의 경우 사용 중인 기기, 오류가 발생하는 과정 및 재현 방법, 오류가 발생한 사진을 첨부해 주시면 개선에 큰 도움이 됩니다. 86 | 87 | 88 | 89 | ## 개발자에게 커피 한 잔 사주기 90 | 91 | [토스로 커피 사주기](https://toss.me/nerious2/1900) 92 | 93 | 링크를 통해 개발자에게 1900원을 후원하실 수 있습니다. 대가를 바라는 후원은 받지 않으니 양해 부탁드립니다. 94 | 95 | 모두의 이북을 이용하시는 모든 분께 감사의 인사 올립니다. 96 | -------------------------------------------------------------------------------- /Root.js: -------------------------------------------------------------------------------- 1 | import React from 'react'; 2 | import { LogBox } from 'react-native'; 3 | import App from './src/App'; 4 | import { AppDetailModalProvider } from './src/Component/AppDetailModal'; 5 | import { GlobalAppStateProvider } from './src/Component/GlobalContext'; 6 | import { AppVersionSelectModalProvider } from './src/Component/AppVersionSelectModal'; 7 | import { MenuProvider } from 'react-native-popup-menu'; 8 | 9 | LogBox.ignoreLogs(['VirtualizedLists should never be nested inside plain ScrollViews with the same orientation']); 10 | 11 | const Root = () => ( 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | ); 22 | 23 | 24 | export default Root; -------------------------------------------------------------------------------- /__tests__/App-test.js: -------------------------------------------------------------------------------- 1 | /** 2 | * @format 3 | */ 4 | 5 | import 'react-native'; 6 | import React from 'react'; 7 | import App from '../App'; 8 | 9 | // Note: test renderer must be required after react-native. 10 | import renderer from 'react-test-renderer'; 11 | 12 | it('renders correctly', () => { 13 | renderer.create(); 14 | }); 15 | -------------------------------------------------------------------------------- /android/app/BUCK: -------------------------------------------------------------------------------- 1 | # To learn about Buck see [Docs](https://buckbuild.com/). 2 | # To run your application with Buck: 3 | # - install Buck 4 | # - `npm start` - to start the packager 5 | # - `cd android` 6 | # - `keytool -genkey -v -keystore keystores/debug.keystore -storepass android -alias androiddebugkey -keypass android -dname "CN=Android Debug,O=Android,C=US"` 7 | # - `./gradlew :app:copyDownloadableDepsToLibs` - make all Gradle compile dependencies available to Buck 8 | # - `buck install -r android/app` - compile, install and run application 9 | # 10 | 11 | load(":build_defs.bzl", "create_aar_targets", "create_jar_targets") 12 | 13 | lib_deps = [] 14 | 15 | create_aar_targets(glob(["libs/*.aar"])) 16 | 17 | create_jar_targets(glob(["libs/*.jar"])) 18 | 19 | android_library( 20 | name = "all-libs", 21 | exported_deps = lib_deps, 22 | ) 23 | 24 | android_library( 25 | name = "app-code", 26 | srcs = glob([ 27 | "src/main/java/**/*.java", 28 | ]), 29 | deps = [ 30 | ":all-libs", 31 | ":build_config", 32 | ":res", 33 | ], 34 | ) 35 | 36 | android_build_config( 37 | name = "build_config", 38 | package = "com.StandardEinkLauncher", 39 | ) 40 | 41 | android_resource( 42 | name = "res", 43 | package = "com.StandardEinkLauncher", 44 | res = "src/main/res", 45 | ) 46 | 47 | android_binary( 48 | name = "app", 49 | keystore = "//android/keystores:debug", 50 | manifest = "src/main/AndroidManifest.xml", 51 | package_type = "debug", 52 | deps = [ 53 | ":app-code", 54 | ], 55 | ) 56 | -------------------------------------------------------------------------------- /android/app/build.gradle: -------------------------------------------------------------------------------- 1 | apply plugin: "com.android.application" 2 | 3 | import com.android.build.OutputFile 4 | 5 | /** 6 | * The react.gradle file registers a task for each build variant (e.g. bundleDebugJsAndAssets 7 | * and bundleReleaseJsAndAssets). 8 | * These basically call `react-native bundle` with the correct arguments during the Android build 9 | * cycle. By default, bundleDebugJsAndAssets is skipped, as in debug/dev mode we prefer to load the 10 | * bundle directly from the development server. Below you can see all the possible configurations 11 | * and their defaults. If you decide to add a configuration block, make sure to add it before the 12 | * `apply from: "../../node_modules/react-native/react.gradle"` line. 13 | * 14 | * project.ext.react = [ 15 | * // the name of the generated asset file containing your JS bundle 16 | * bundleAssetName: "index.android.bundle", 17 | * 18 | * // the entry file for bundle generation. If none specified and 19 | * // "index.android.js" exists, it will be used. Otherwise "index.js" is 20 | * // default. Can be overridden with ENTRY_FILE environment variable. 21 | * entryFile: "index.android.js", 22 | * 23 | * // https://reactnative.dev/docs/performance#enable-the-ram-format 24 | * bundleCommand: "ram-bundle", 25 | * 26 | * // whether to bundle JS and assets in debug mode 27 | * bundleInDebug: false, 28 | * 29 | * // whether to bundle JS and assets in release mode 30 | * bundleInRelease: true, 31 | * 32 | * // whether to bundle JS and assets in another build variant (if configured). 33 | * // See http://tools.android.com/tech-docs/new-build-system/user-guide#TOC-Build-Variants 34 | * // The configuration property can be in the following formats 35 | * // 'bundleIn${productFlavor}${buildType}' 36 | * // 'bundleIn${buildType}' 37 | * // bundleInFreeDebug: true, 38 | * // bundleInPaidRelease: true, 39 | * // bundleInBeta: true, 40 | * 41 | * // whether to disable dev mode in custom build variants (by default only disabled in release) 42 | * // for example: to disable dev mode in the staging build type (if configured) 43 | * devDisabledInStaging: true, 44 | * // The configuration property can be in the following formats 45 | * // 'devDisabledIn${productFlavor}${buildType}' 46 | * // 'devDisabledIn${buildType}' 47 | * 48 | * // the root of your project, i.e. where "package.json" lives 49 | * root: "../../", 50 | * 51 | * // where to put the JS bundle asset in debug mode 52 | * jsBundleDirDebug: "$buildDir/intermediates/assets/debug", 53 | * 54 | * // where to put the JS bundle asset in release mode 55 | * jsBundleDirRelease: "$buildDir/intermediates/assets/release", 56 | * 57 | * // where to put drawable resources / React Native assets, e.g. the ones you use via 58 | * // require('./image.png')), in debug mode 59 | * resourcesDirDebug: "$buildDir/intermediates/res/merged/debug", 60 | * 61 | * // where to put drawable resources / React Native assets, e.g. the ones you use via 62 | * // require('./image.png')), in release mode 63 | * resourcesDirRelease: "$buildDir/intermediates/res/merged/release", 64 | * 65 | * // by default the gradle tasks are skipped if none of the JS files or assets change; this means 66 | * // that we don't look at files in android/ or ios/ to determine whether the tasks are up to 67 | * // date; if you have any other folders that you want to ignore for performance reasons (gradle 68 | * // indexes the entire tree), add them here. Alternatively, if you have JS files in android/ 69 | * // for example, you might want to remove it from here. 70 | * inputExcludes: ["android/**", "ios/**"], 71 | * 72 | * // override which node gets called and with what additional arguments 73 | * nodeExecutableAndArgs: ["node"], 74 | * 75 | * // supply additional arguments to the packager 76 | * extraPackagerArgs: [] 77 | * ] 78 | */ 79 | 80 | project.ext.react = [ 81 | enableHermes: true, // clean and rebuild if changing 82 | ] 83 | 84 | apply from: "../../node_modules/react-native/react.gradle" 85 | 86 | 87 | project.ext.vectoricons = [ 88 | iconFontNames: [ 'MaterialIcons.ttf' ] // Name of the font files you want to copy 89 | ] 90 | 91 | apply from: "../../node_modules/react-native-vector-icons/fonts.gradle" 92 | 93 | /** 94 | * Set this to true to create two separate APKs instead of one: 95 | * - An APK that only works on ARM devices 96 | * - An APK that only works on x86 devices 97 | * The advantage is the size of the APK is reduced by about 4MB. 98 | * Upload all the APKs to the Play Store and people will download 99 | * the correct one based on the CPU architecture of their device. 100 | */ 101 | def enableSeparateBuildPerCPUArchitecture = true 102 | 103 | /** 104 | * Run Proguard to shrink the Java bytecode in release builds. 105 | */ 106 | def enableProguardInReleaseBuilds = true 107 | 108 | /** 109 | * The preferred build flavor of JavaScriptCore. 110 | * 111 | * For example, to use the international variant, you can use: 112 | * `def jscFlavor = 'org.webkit:android-jsc-intl:+'` 113 | * 114 | * The international variant includes ICU i18n library and necessary data 115 | * allowing to use e.g. `Date.toLocaleString` and `String.localeCompare` that 116 | * give correct results when using with locales other than en-US. Note that 117 | * this variant is about 6MiB larger per architecture than default. 118 | */ 119 | def jscFlavor = 'org.webkit:android-jsc:+' 120 | 121 | /** 122 | * Whether to enable the Hermes VM. 123 | * 124 | * This should be set on project.ext.react and mirrored here. If it is not set 125 | * on project.ext.react, JavaScript will not be compiled to Hermes Bytecode 126 | * and the benefits of using Hermes will therefore be sharply reduced. 127 | */ 128 | def enableHermes = project.ext.react.get("enableHermes", false); 129 | 130 | 131 | 132 | 133 | 134 | android { 135 | compileSdkVersion rootProject.ext.compileSdkVersion 136 | 137 | compileOptions { 138 | sourceCompatibility JavaVersion.VERSION_1_8 139 | targetCompatibility JavaVersion.VERSION_1_8 140 | } 141 | 142 | defaultConfig { 143 | applicationId "com.se.ebookmanager" 144 | minSdkVersion rootProject.ext.minSdkVersion 145 | targetSdkVersion rootProject.ext.targetSdkVersion 146 | versionCode 15 147 | versionName "1.0.06" 148 | // Enabling multidex support. 149 | multiDexEnabled true 150 | } 151 | dexOptions { 152 | jumboMode true 153 | javaMaxHeapSize "4g" 154 | } 155 | splits { 156 | abi { 157 | reset() 158 | enable enableSeparateBuildPerCPUArchitecture 159 | universalApk false // If true, also generate a universal APK 160 | include "armeabi-v7a", "x86", "arm64-v8a", "x86_64" 161 | } 162 | } 163 | signingConfigs { 164 | debug { 165 | storeFile file('debug.keystore') 166 | storePassword 'android' 167 | keyAlias 'androiddebugkey' 168 | keyPassword 'android' 169 | } 170 | release { 171 | 172 | 173 | // Keystore Security 174 | if (rootProject.file("keystore.properties").exists()) { 175 | def keystorePropertiesFile = rootProject.file("keystore.properties") 176 | def keystoreProperties = new Properties() 177 | keystoreProperties.load(new FileInputStream(keystorePropertiesFile)) 178 | 179 | keyAlias keystoreProperties['APP_KEY_ALIAS'] 180 | keyPassword keystoreProperties['APP_KEY_PASSWORD'] 181 | storeFile file(keystoreProperties['APP_STORE_FILE']) 182 | storePassword keystoreProperties['APP_STORE_PASSWORD'] 183 | } 184 | 185 | } 186 | } 187 | buildTypes { 188 | debug { 189 | signingConfig signingConfigs.debug 190 | } 191 | release { 192 | // Caution! In production, you need to generate your own keystore file. 193 | // see https://reactnative.dev/docs/signed-apk-android. 194 | signingConfig signingConfigs.release 195 | minifyEnabled enableProguardInReleaseBuilds 196 | proguardFiles getDefaultProguardFile("proguard-android.txt"), "proguard-rules.pro" 197 | shrinkResources true 198 | } 199 | } 200 | 201 | // applicationVariants are e.g. debug, release 202 | applicationVariants.all { variant -> 203 | variant.outputs.each { output -> 204 | // For each separate APK per architecture, set a unique version code as described here: 205 | // https://developer.android.com/studio/build/configure-apk-splits.html 206 | def versionCodes = ["armeabi-v7a": 1, "x86": 2, "arm64-v8a": 3, "x86_64": 4] 207 | def abi = output.getFilter(OutputFile.ABI) 208 | if (abi != null) { // null for the universal-debug, universal-release variants 209 | output.versionCodeOverride = 210 | versionCodes.get(abi) * 1048576 + defaultConfig.versionCode 211 | } 212 | 213 | } 214 | } 215 | } 216 | 217 | dependencies { 218 | implementation fileTree(dir: "libs", include: ["*.jar"]) 219 | //noinspection GradleDynamicVersion 220 | implementation "com.facebook.react:react-native:+" // From node_modules 221 | 222 | implementation "androidx.swiperefreshlayout:swiperefreshlayout:1.0.0" 223 | implementation project(':react-native-fs') // react-native-fs 224 | implementation project(':react-native-fast-image') 225 | implementation project(':react-native-vector-icons') 226 | 227 | debugImplementation("com.facebook.flipper:flipper:${FLIPPER_VERSION}") { 228 | exclude group:'com.facebook.fbjni' 229 | } 230 | 231 | debugImplementation("com.facebook.flipper:flipper-network-plugin:${FLIPPER_VERSION}") { 232 | exclude group:'com.facebook.flipper' 233 | exclude group:'com.squareup.okhttp3', module:'okhttp' 234 | } 235 | 236 | debugImplementation("com.facebook.flipper:flipper-fresco-plugin:0.99.0") { 237 | exclude group:'com.facebook.flipper' 238 | } 239 | 240 | if (enableHermes) { 241 | def hermesPath = "../../node_modules/hermes-engine/android/"; 242 | debugImplementation files(hermesPath + "hermes-debug.aar") 243 | releaseImplementation files(hermesPath + "hermes-release.aar") 244 | } else { 245 | implementation jscFlavor 246 | } 247 | implementation 'com.android.support:multidex:1.0.3' 248 | implementation 'org.conscrypt:conscrypt-android:2.1.0' 249 | } 250 | 251 | // Run this once to be able to run the application with BUCK 252 | // puts all compile dependencies into folder libs for BUCK to use 253 | task copyDownloadableDepsToLibs(type: Copy) { 254 | from configurations.compile 255 | into 'libs' 256 | } 257 | 258 | apply from: file("../../node_modules/@react-native-community/cli-platform-android/native_modules.gradle"); applyNativeModulesAppBuildGradle(project) -------------------------------------------------------------------------------- /android/app/build_defs.bzl: -------------------------------------------------------------------------------- 1 | """Helper definitions to glob .aar and .jar targets""" 2 | 3 | def create_aar_targets(aarfiles): 4 | for aarfile in aarfiles: 5 | name = "aars__" + aarfile[aarfile.rindex("/") + 1:aarfile.rindex(".aar")] 6 | lib_deps.append(":" + name) 7 | android_prebuilt_aar( 8 | name = name, 9 | aar = aarfile, 10 | ) 11 | 12 | def create_jar_targets(jarfiles): 13 | for jarfile in jarfiles: 14 | name = "jars__" + jarfile[jarfile.rindex("/") + 1:jarfile.rindex(".jar")] 15 | lib_deps.append(":" + name) 16 | prebuilt_jar( 17 | name = name, 18 | binary_jar = jarfile, 19 | ) 20 | -------------------------------------------------------------------------------- /android/app/debug.keystore: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/nerious2/SEM/9dc3905aab32ecf2792d069aeb93b88c81021ee7/android/app/debug.keystore -------------------------------------------------------------------------------- /android/app/proguard-rules.pro: -------------------------------------------------------------------------------- 1 | # Add project specific ProGuard rules here. 2 | # By default, the flags in this file are appended to flags specified 3 | # in /usr/local/Cellar/android-sdk/24.3.3/tools/proguard/proguard-android.txt 4 | # You can edit the include path and order by changing the proguardFiles 5 | # directive in build.gradle. 6 | # 7 | # For more details, see 8 | # http://developer.android.com/guide/developing/tools/proguard.html 9 | 10 | # Add any project specific keep options here: 11 | -keep class com.facebook.hermes.unicode.** { *; } 12 | -keep class com.facebook.jni.** { *; } 13 | 14 | -keep public class com.dylanvann.fastimage.* {*;} 15 | -keep public class com.dylanvann.fastimage.** {*;} 16 | -keep public class * implements com.bumptech.glide.module.GlideModule 17 | -keep public class * extends com.bumptech.glide.module.AppGlideModule 18 | -keep public enum com.bumptech.glide.load.ImageHeaderParser$** { 19 | **[] $VALUES; 20 | public *; 21 | } 22 | -keep class com.swmansion.reanimated.** { *; } 23 | -keep class com.facebook.react.turbomodule.** { *; } -------------------------------------------------------------------------------- /android/app/src/debug/AndroidManifest.xml: -------------------------------------------------------------------------------- 1 | 2 | 4 | 5 | 6 | 7 | 8 | 9 | -------------------------------------------------------------------------------- /android/app/src/debug/java/com/standardebooklauncher/ReactNativeFlipper.java: -------------------------------------------------------------------------------- 1 | /** 2 | * Copyright (c) Facebook, Inc. and its affiliates. 3 | * 4 | *

This source code is licensed under the MIT license found in the LICENSE file in the root 5 | * directory of this source tree. 6 | */ 7 | package com.StandardEinkLauncher; 8 | 9 | import android.content.Context; 10 | import com.facebook.flipper.android.AndroidFlipperClient; 11 | import com.facebook.flipper.android.utils.FlipperUtils; 12 | import com.facebook.flipper.core.FlipperClient; 13 | import com.facebook.flipper.plugins.crashreporter.CrashReporterPlugin; 14 | import com.facebook.flipper.plugins.databases.DatabasesFlipperPlugin; 15 | import com.facebook.flipper.plugins.fresco.FrescoFlipperPlugin; 16 | import com.facebook.flipper.plugins.inspector.DescriptorMapping; 17 | import com.facebook.flipper.plugins.inspector.InspectorFlipperPlugin; 18 | import com.facebook.flipper.plugins.network.FlipperOkhttpInterceptor; 19 | import com.facebook.flipper.plugins.network.NetworkFlipperPlugin; 20 | import com.facebook.flipper.plugins.react.ReactFlipperPlugin; 21 | import com.facebook.flipper.plugins.sharedpreferences.SharedPreferencesFlipperPlugin; 22 | import com.facebook.react.ReactInstanceManager; 23 | import com.facebook.react.bridge.ReactContext; 24 | import com.facebook.react.modules.network.NetworkingModule; 25 | import okhttp3.OkHttpClient; 26 | 27 | public class ReactNativeFlipper { 28 | public static void initializeFlipper(Context context, ReactInstanceManager reactInstanceManager) { 29 | if (FlipperUtils.shouldEnableFlipper(context)) { 30 | final FlipperClient client = AndroidFlipperClient.getInstance(context); 31 | 32 | client.addPlugin(new InspectorFlipperPlugin(context, DescriptorMapping.withDefaults())); 33 | client.addPlugin(new ReactFlipperPlugin()); 34 | client.addPlugin(new DatabasesFlipperPlugin(context)); 35 | client.addPlugin(new SharedPreferencesFlipperPlugin(context)); 36 | client.addPlugin(CrashReporterPlugin.getInstance()); 37 | 38 | NetworkFlipperPlugin networkFlipperPlugin = new NetworkFlipperPlugin(); 39 | NetworkingModule.setCustomClientBuilder( 40 | new NetworkingModule.CustomClientBuilder() { 41 | @Override 42 | public void apply(OkHttpClient.Builder builder) { 43 | builder.addNetworkInterceptor(new FlipperOkhttpInterceptor(networkFlipperPlugin)); 44 | } 45 | }); 46 | client.addPlugin(networkFlipperPlugin); 47 | client.start(); 48 | 49 | // Fresco Plugin needs to ensure that ImagePipelineFactory is initialized 50 | // Hence we run if after all native modules have been initialized 51 | ReactContext reactContext = reactInstanceManager.getCurrentReactContext(); 52 | if (reactContext == null) { 53 | reactInstanceManager.addReactInstanceEventListener( 54 | new ReactInstanceManager.ReactInstanceEventListener() { 55 | @Override 56 | public void onReactContextInitialized(ReactContext reactContext) { 57 | reactInstanceManager.removeReactInstanceEventListener(this); 58 | reactContext.runOnNativeModulesQueueThread( 59 | new Runnable() { 60 | @Override 61 | public void run() { 62 | client.addPlugin(new FrescoFlipperPlugin()); 63 | } 64 | }); 65 | } 66 | }); 67 | } else { 68 | client.addPlugin(new FrescoFlipperPlugin()); 69 | } 70 | } 71 | } 72 | } 73 | -------------------------------------------------------------------------------- /android/app/src/main/AndroidManifest.xml: -------------------------------------------------------------------------------- 1 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 20 | 21 | 28 | 29 | 30 | 31 | 33 | 34 | 35 | 36 | 37 | 38 | 39 | 40 | 41 | 42 | 43 | 44 | 45 | 46 | 47 | 48 | -------------------------------------------------------------------------------- /android/app/src/main/ic_launcher-playstore.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/nerious2/SEM/9dc3905aab32ecf2792d069aeb93b88c81021ee7/android/app/src/main/ic_launcher-playstore.png -------------------------------------------------------------------------------- /android/app/src/main/java/com/se/ebookmanager/InstalledAppsModule.java: -------------------------------------------------------------------------------- 1 | package com.se.ebookmanager; 2 | 3 | 4 | import android.content.pm.PackageInfo; 5 | import android.content.pm.ApplicationInfo; 6 | import android.content.Intent; 7 | import android.os.Build; 8 | import android.net.Uri; 9 | import android.provider.Settings; 10 | 11 | import com.facebook.react.bridge.ReactApplicationContext; 12 | import com.facebook.react.bridge.ReactContextBaseJavaModule; 13 | import com.facebook.react.bridge.ReactMethod; 14 | import com.facebook.react.bridge.Callback; 15 | import com.facebook.react.bridge.Promise; 16 | 17 | import java.io.ByteArrayOutputStream; 18 | 19 | import java.util.ArrayList; 20 | import java.util.List; 21 | import java.util.Map; 22 | import java.util.HashMap; 23 | 24 | import javax.annotation.Nullable; 25 | 26 | import android.graphics.Bitmap; 27 | import android.graphics.BitmapFactory; 28 | import android.graphics.Canvas; 29 | import android.graphics.drawable.BitmapDrawable; 30 | import android.graphics.drawable.Drawable; 31 | import android.util.Base64; 32 | import android.app.Instrumentation; 33 | import android.view.KeyEvent; 34 | 35 | import org.json.JSONArray; 36 | import org.json.JSONException; 37 | import org.json.JSONObject; 38 | 39 | 40 | public class InstalledAppsModule extends ReactContextBaseJavaModule { 41 | 42 | private final ReactApplicationContext reactContext; 43 | private Instrumentation inst = new Instrumentation(); 44 | 45 | 46 | private class AppDetail { 47 | CharSequence label; 48 | CharSequence name; 49 | Drawable icon; 50 | //CharSequence icon; 51 | public String toString() { 52 | Bitmap icon; 53 | if(this.icon.getIntrinsicWidth() <= 0 || this.icon.getIntrinsicHeight() <= 0) { 54 | icon = Bitmap.createBitmap(1, 1, Bitmap.Config.ARGB_8888); // Single color bitmap will be created of 1x1 pixel 55 | } else { 56 | icon = Bitmap.createBitmap(this.icon.getIntrinsicWidth(), this.icon.getIntrinsicHeight(), Bitmap.Config.ARGB_8888); 57 | } 58 | final Canvas canvas = new Canvas(icon); 59 | this.icon.setBounds(0, 0, canvas.getWidth(), canvas.getHeight()); 60 | this.icon.draw(canvas); 61 | 62 | ByteArrayOutputStream byteArrayOutputStream = new ByteArrayOutputStream(); 63 | icon.compress(Bitmap.CompressFormat.PNG, 100, byteArrayOutputStream); 64 | byte[] byteArray = byteArrayOutputStream.toByteArray(); 65 | String encoded = Base64.encodeToString(byteArray, Base64.NO_WRAP); 66 | 67 | return "{\"label\":\"" + this.label + "\",\"name\":\"" + this.name + "\",\"icon\":\"" + encoded + "\"}"; 68 | } 69 | } 70 | 71 | public InstalledAppsModule(ReactApplicationContext reactContext) { 72 | super(reactContext); 73 | this.reactContext = reactContext; 74 | } 75 | 76 | @Override 77 | public String getName() { 78 | return "InstalledApps"; 79 | } 80 | 81 | 82 | 83 | @ReactMethod 84 | private void inputKey(){ 85 | new Thread(new Runnable() { 86 | @Override 87 | public void run() { 88 | try { 89 | inst.sendKeyDownUpSync(KeyEvent.KEYCODE_PAGE_DOWN); 90 | } catch (Exception e) {} 91 | } 92 | }).start(); 93 | 94 | } 95 | 96 | // Backup 97 | @ReactMethod 98 | public void getApps(Promise promise){ 99 | //List apps = new ArrayList<>(); 100 | try { 101 | JSONArray apps = new JSONArray(); 102 | 103 | List packages = this.reactContext 104 | .getPackageManager() 105 | .getInstalledPackages(0); 106 | 107 | for(final PackageInfo p: packages){ 108 | if (this.reactContext.getPackageManager().getLaunchIntentForPackage(p.packageName) != null) { 109 | //AppDetail app = new AppDetail(); 110 | // app.label = p.applicationInfo.loadLabel(this.reactContext.getPackageManager()); 111 | // app.name = p.packageName; 112 | // app.icon = p.applicationInfo.loadIcon(this.reactContext.getPackageManager()); 113 | // apps.add(app); 114 | try { 115 | JSONObject app = new JSONObject(); 116 | app.put("label", p.applicationInfo.loadLabel(this.reactContext.getPackageManager())); 117 | app.put("name", p.packageName); 118 | 119 | // versionName, versionCode 추가 120 | app.put("versionName", p.versionName); 121 | 122 | // if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.P) { 123 | // app.put("versionCode", p.getLongVersionCode()); 124 | // } else { 125 | // app.put("versionCode", p.versionCode); 126 | // } 127 | app.put("versionCode", Build.VERSION.SDK_INT >= Build.VERSION_CODES.P ? p.getLongVersionCode() : p.versionCode); 128 | 129 | //app.put("icon", p.applicationInfo.loadIcon(this.reactContext.getPackageManager()); 130 | 131 | // icon disabled 132 | // Drawable origIcon = p.applicationInfo.loadIcon(this.reactContext.getPackageManager()); 133 | // Bitmap icon; 134 | // if(origIcon.getIntrinsicWidth() <= 0 || origIcon.getIntrinsicHeight() <= 0) { 135 | // icon = Bitmap.createBitmap(1, 1, Bitmap.Config.ARGB_8888); // Single color bitmap will be created of 1x1 pixel 136 | // } else { 137 | // icon = Bitmap.createBitmap(origIcon.getIntrinsicWidth(), origIcon.getIntrinsicHeight(), Bitmap.Config.ARGB_8888); 138 | // } 139 | 140 | // final Canvas canvas = new Canvas(icon); 141 | // origIcon.setBounds(0, 0, canvas.getWidth(), canvas.getHeight()); 142 | // origIcon.draw(canvas); 143 | 144 | // ByteArrayOutputStream byteArrayOutputStream = new ByteArrayOutputStream(); 145 | // icon.compress(Bitmap.CompressFormat.PNG, 100, byteArrayOutputStream); 146 | // byte[] byteArray = byteArrayOutputStream.toByteArray(); 147 | // String encoded = Base64.encodeToString(byteArray, Base64.NO_WRAP); 148 | 149 | // app.put("icon", encoded); 150 | 151 | apps.put(app); 152 | } catch (JSONException e) { 153 | throw new RuntimeException(e); 154 | } 155 | } 156 | } 157 | // return apps.toString(); 158 | //return apps.toJSONString(); 159 | promise.resolve(apps.toString()); 160 | } catch (Exception e) { 161 | promise.reject("INSTALLEDAPPS_ERROR", e); 162 | } 163 | 164 | } 165 | 166 | 167 | 168 | private List getAllApps() { 169 | List packages = this.reactContext 170 | .getPackageManager() 171 | .getInstalledPackages(0); 172 | 173 | List ret = new ArrayList<>(); 174 | for (final PackageInfo p: packages) { 175 | ret.add(p.packageName); 176 | } 177 | return ret; 178 | } 179 | 180 | private List getNonSystemApps() { 181 | List packages = this.reactContext 182 | .getPackageManager() 183 | .getInstalledPackages(0); 184 | 185 | List ret = new ArrayList<>(); 186 | for (final PackageInfo p: packages) { 187 | if ((p.applicationInfo.flags & ApplicationInfo.FLAG_SYSTEM) == 0) { 188 | ret.add(p.packageName); 189 | } 190 | } 191 | return ret; 192 | } 193 | 194 | @ReactMethod 195 | private void launchApplication(String packageName){ 196 | Intent launchIntent = this.reactContext.getPackageManager().getLaunchIntentForPackage(packageName); 197 | if (launchIntent != null) { 198 | this.reactContext.startActivity(launchIntent);//null pointer check in case package name was not found 199 | } 200 | } 201 | 202 | @ReactMethod 203 | private void deleteApplication(String packageName){ 204 | Intent intent = new Intent(Intent.ACTION_DELETE); 205 | intent.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK); 206 | intent.setData(Uri.parse("package:" + packageName)); 207 | this.reactContext.startActivity(intent); 208 | } 209 | 210 | @ReactMethod 211 | private void showApplicationSetting(String packageName){ 212 | Intent intent = new Intent(Settings.ACTION_APPLICATION_DETAILS_SETTINGS); 213 | intent.addFlags(Intent.FLAG_ACTIVITY_NO_ANIMATION); 214 | intent.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK); 215 | Uri uri = Uri.fromParts("package", packageName, null); 216 | intent.setData(uri); 217 | this.reactContext.startActivity(intent); 218 | } 219 | 220 | 221 | @ReactMethod 222 | private String getAndroidRelease(){ 223 | return Build.VERSION.RELEASE; 224 | } 225 | 226 | @Override 227 | public @Nullable Map getConstants() { 228 | Map constants = new HashMap<>(); 229 | 230 | // constants.put("getApps", getApps()); 231 | // constants.put("getNonSystemApps", getNonSystemApps()); 232 | return constants; 233 | } 234 | } -------------------------------------------------------------------------------- /android/app/src/main/java/com/se/ebookmanager/InstalledAppsPackage.java: -------------------------------------------------------------------------------- 1 | package com.se.ebookmanager; 2 | 3 | import java.util.Arrays; 4 | import java.util.Collections; 5 | import java.util.List; 6 | 7 | import com.facebook.react.ReactPackage; 8 | import com.facebook.react.bridge.NativeModule; 9 | import com.facebook.react.bridge.ReactApplicationContext; 10 | import com.facebook.react.uimanager.ViewManager; 11 | import com.facebook.react.bridge.JavaScriptModule; 12 | public class InstalledAppsPackage implements ReactPackage { 13 | @Override 14 | public List createNativeModules(ReactApplicationContext reactContext) { 15 | return Arrays.asList(new InstalledAppsModule(reactContext)); 16 | } 17 | 18 | //deprecated 19 | public List> createJSModules() { 20 | return Collections.emptyList(); 21 | } 22 | 23 | @Override 24 | public List createViewManagers(ReactApplicationContext reactContext) { 25 | return Collections.emptyList(); 26 | } 27 | } -------------------------------------------------------------------------------- /android/app/src/main/java/com/se/ebookmanager/MainActivity.java: -------------------------------------------------------------------------------- 1 | package com.se.ebookmanager; 2 | 3 | import android.os.Bundle; 4 | import com.facebook.react.ReactActivity; 5 | import com.facebook.react.ReactActivityDelegate; 6 | import com.facebook.react.ReactRootView; 7 | 8 | public class MainActivity extends ReactActivity { 9 | 10 | @Override 11 | protected void onCreate(Bundle savedInstanceState) { 12 | super.onCreate(null); 13 | } 14 | 15 | /** 16 | * Returns the name of the main component registered from JavaScript. This is used to schedule 17 | * rendering of the component. 18 | */ 19 | @Override 20 | protected String getMainComponentName() { 21 | return "StandardEinkLauncher"; 22 | } 23 | } 24 | -------------------------------------------------------------------------------- /android/app/src/main/java/com/se/ebookmanager/MainApplication.java: -------------------------------------------------------------------------------- 1 | package com.se.ebookmanager; 2 | 3 | import com.rnfs.RNFSPackage; 4 | import android.app.Application; 5 | import android.content.Context; 6 | import com.facebook.react.PackageList; 7 | import com.facebook.react.ReactApplication; 8 | import com.facebook.react.ReactInstanceManager; 9 | import com.facebook.react.ReactNativeHost; 10 | import com.facebook.react.ReactPackage; 11 | import com.facebook.soloader.SoLoader; 12 | import java.lang.reflect.InvocationTargetException; 13 | import java.util.List; 14 | import androidx.multidex.MultiDexApplication; 15 | 16 | import com.se.ebookmanager.InstalledAppsPackage; 17 | import com.facebook.react.bridge.JSIModulePackage; // <- add 18 | import com.swmansion.reanimated.ReanimatedJSIModulePackage; // <- add 19 | 20 | import java.security.Security; 21 | import javax.net.ssl.SSLContext; 22 | import org.conscrypt.OpenSSLProvider; 23 | 24 | public class MainApplication extends MultiDexApplication implements ReactApplication { 25 | 26 | private final ReactNativeHost mReactNativeHost = 27 | new ReactNativeHost(this) { 28 | @Override 29 | public boolean getUseDeveloperSupport() { 30 | return BuildConfig.DEBUG; 31 | } 32 | 33 | @Override 34 | protected List getPackages() { 35 | @SuppressWarnings("UnnecessaryLocalVariable") 36 | List packages = new PackageList(this).getPackages(); 37 | // Packages that cannot be autolinked yet can be added manually here, for example: 38 | // packages.add(new MyReactNativePackage()); 39 | packages.add(new InstalledAppsPackage()); 40 | return packages; 41 | } 42 | 43 | @Override 44 | protected String getJSMainModuleName() { 45 | return "index"; 46 | } 47 | 48 | @Override 49 | protected JSIModulePackage getJSIModulePackage() { 50 | return new ReanimatedJSIModulePackage(); // <- add 51 | } 52 | }; 53 | 54 | @Override 55 | public ReactNativeHost getReactNativeHost() { 56 | return mReactNativeHost; 57 | } 58 | 59 | @Override 60 | public void onCreate() { 61 | super.onCreate(); 62 | Security.insertProviderAt(new org.conscrypt.OpenSSLProvider(), 1); 63 | 64 | SoLoader.init(this, /* native exopackage */ false); 65 | initializeFlipper(this, getReactNativeHost().getReactInstanceManager()); 66 | } 67 | 68 | /** 69 | * Loads Flipper in React Native templates. Call this in the onCreate method with something like 70 | * initializeFlipper(this, getReactNativeHost().getReactInstanceManager()); 71 | * 72 | * @param context 73 | * @param reactInstanceManager 74 | */ 75 | private static void initializeFlipper( 76 | Context context, ReactInstanceManager reactInstanceManager) { 77 | if (BuildConfig.DEBUG) { 78 | try { 79 | /* 80 | We use reflection here to pick up the class that initializes Flipper, 81 | since Flipper library is not available in release mode 82 | */ 83 | Class aClass = Class.forName("com.StandardEinkLauncher.ReactNativeFlipper"); 84 | aClass 85 | .getMethod("initializeFlipper", Context.class, ReactInstanceManager.class) 86 | .invoke(null, context, reactInstanceManager); 87 | } catch (ClassNotFoundException e) { 88 | e.printStackTrace(); 89 | } catch (NoSuchMethodException e) { 90 | e.printStackTrace(); 91 | } catch (IllegalAccessException e) { 92 | e.printStackTrace(); 93 | } catch (InvocationTargetException e) { 94 | e.printStackTrace(); 95 | } 96 | } 97 | } 98 | } 99 | -------------------------------------------------------------------------------- /android/app/src/main/res/mipmap-hdpi/ic_launcher.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/nerious2/SEM/9dc3905aab32ecf2792d069aeb93b88c81021ee7/android/app/src/main/res/mipmap-hdpi/ic_launcher.png -------------------------------------------------------------------------------- /android/app/src/main/res/mipmap-hdpi/ic_launcher_round.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/nerious2/SEM/9dc3905aab32ecf2792d069aeb93b88c81021ee7/android/app/src/main/res/mipmap-hdpi/ic_launcher_round.png -------------------------------------------------------------------------------- /android/app/src/main/res/mipmap-mdpi/ic_launcher.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/nerious2/SEM/9dc3905aab32ecf2792d069aeb93b88c81021ee7/android/app/src/main/res/mipmap-mdpi/ic_launcher.png -------------------------------------------------------------------------------- /android/app/src/main/res/mipmap-mdpi/ic_launcher_round.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/nerious2/SEM/9dc3905aab32ecf2792d069aeb93b88c81021ee7/android/app/src/main/res/mipmap-mdpi/ic_launcher_round.png -------------------------------------------------------------------------------- /android/app/src/main/res/mipmap-xhdpi/ic_launcher.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/nerious2/SEM/9dc3905aab32ecf2792d069aeb93b88c81021ee7/android/app/src/main/res/mipmap-xhdpi/ic_launcher.png -------------------------------------------------------------------------------- /android/app/src/main/res/mipmap-xhdpi/ic_launcher_round.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/nerious2/SEM/9dc3905aab32ecf2792d069aeb93b88c81021ee7/android/app/src/main/res/mipmap-xhdpi/ic_launcher_round.png -------------------------------------------------------------------------------- /android/app/src/main/res/mipmap-xxhdpi/ic_launcher.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/nerious2/SEM/9dc3905aab32ecf2792d069aeb93b88c81021ee7/android/app/src/main/res/mipmap-xxhdpi/ic_launcher.png -------------------------------------------------------------------------------- /android/app/src/main/res/mipmap-xxhdpi/ic_launcher_round.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/nerious2/SEM/9dc3905aab32ecf2792d069aeb93b88c81021ee7/android/app/src/main/res/mipmap-xxhdpi/ic_launcher_round.png -------------------------------------------------------------------------------- /android/app/src/main/res/mipmap-xxxhdpi/ic_launcher.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/nerious2/SEM/9dc3905aab32ecf2792d069aeb93b88c81021ee7/android/app/src/main/res/mipmap-xxxhdpi/ic_launcher.png -------------------------------------------------------------------------------- /android/app/src/main/res/mipmap-xxxhdpi/ic_launcher_round.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/nerious2/SEM/9dc3905aab32ecf2792d069aeb93b88c81021ee7/android/app/src/main/res/mipmap-xxxhdpi/ic_launcher_round.png -------------------------------------------------------------------------------- /android/app/src/main/res/values/ic_launcher_background.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | #01A5B8 4 | -------------------------------------------------------------------------------- /android/app/src/main/res/values/strings.xml: -------------------------------------------------------------------------------- 1 | 2 | 모두의 이북 3 | 4 | -------------------------------------------------------------------------------- /android/app/src/main/res/values/styles.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 8 | 9 | 10 | -------------------------------------------------------------------------------- /android/build.gradle: -------------------------------------------------------------------------------- 1 | // Top-level build file where you can add configuration options common to all sub-projects/modules. 2 | 3 | buildscript { 4 | ext { 5 | kotlinVersion = "1.6.0" 6 | buildToolsVersion = "30.0.2" 7 | minSdkVersion = 16 8 | compileSdkVersion = 29 9 | targetSdkVersion = 29 10 | } 11 | repositories { 12 | google() 13 | mavenCentral() 14 | } 15 | dependencies { 16 | classpath("com.android.tools.build:gradle:4.2.2") 17 | // NOTE: Do not place your application dependencies here; they belong 18 | // in the individual module build.gradle files 19 | } 20 | } 21 | 22 | allprojects { 23 | repositories { 24 | maven { 25 | // All of React Native (JS, Obj-C sources, Android binaries) is installed from npm 26 | url("$rootDir/../node_modules/react-native/android") 27 | } 28 | maven { 29 | // Android JSC is installed from npm 30 | url("$rootDir/../node_modules/jsc-android/dist") 31 | } 32 | mavenCentral { 33 | // We don't want to fetch react-native from Maven Central as there are 34 | // older versions over there. 35 | content { 36 | excludeGroup "com.facebook.react" 37 | } 38 | } 39 | google() 40 | maven { url 'https://www.jitpack.io' } 41 | } 42 | // react-native-reanimated issue 43 | configurations.all { 44 | resolutionStrategy { 45 | force 'com.facebook.react:react-native:0.63.4' 46 | } 47 | } 48 | } 49 | 50 | project.ext { 51 | excludeAppGlideModule = true 52 | } -------------------------------------------------------------------------------- /android/gradle.properties: -------------------------------------------------------------------------------- 1 | # Project-wide Gradle settings. 2 | 3 | # IDE (e.g. Android Studio) users: 4 | # Gradle settings configured through the IDE *will override* 5 | # any settings specified in this file. 6 | 7 | # For more details on how to configure your build environment visit 8 | # http://www.gradle.org/docs/current/userguide/build_environment.html 9 | 10 | # Specifies the JVM arguments used for the daemon process. 11 | # The setting is particularly useful for tweaking memory settings. 12 | # Default value: -Xmx10248m -XX:MaxPermSize=256m 13 | # org.gradle.jvmargs=-Xmx2048m -XX:MaxPermSize=512m -XX:+HeapDumpOnOutOfMemoryError -Dfile.encoding=UTF-8 14 | 15 | # When configured, Gradle will run in incubating parallel mode. 16 | # This option should only be used with decoupled projects. More details, visit 17 | # http://www.gradle.org/docs/current/userguide/multi_project_builds.html#sec:decoupled_projects 18 | # org.gradle.parallel=true 19 | 20 | # AndroidX package structure to make it clearer which packages are bundled with the 21 | # Android operating system, and which are packaged with your app's APK 22 | # https://developer.android.com/topic/libraries/support-library/androidx-rn 23 | android.useAndroidX=true 24 | # Automatically convert third-party libraries to use AndroidX 25 | android.enableJetifier=true 26 | 27 | # Version of flipper SDK to use with React Native 28 | FLIPPER_VERSION=0.54.0 29 | 30 | org.gradle.jvmargs=-Xmx4608m -------------------------------------------------------------------------------- /android/gradle/wrapper/gradle-wrapper.jar: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/nerious2/SEM/9dc3905aab32ecf2792d069aeb93b88c81021ee7/android/gradle/wrapper/gradle-wrapper.jar -------------------------------------------------------------------------------- /android/gradle/wrapper/gradle-wrapper.properties: -------------------------------------------------------------------------------- 1 | distributionBase=GRADLE_USER_HOME 2 | distributionPath=wrapper/dists 3 | distributionUrl=https\://services.gradle.org/distributions/gradle-6.9-all.zip 4 | zipStoreBase=GRADLE_USER_HOME 5 | zipStorePath=wrapper/dists 6 | -------------------------------------------------------------------------------- /android/gradlew: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env sh 2 | 3 | # 4 | # Copyright 2015 the original author or authors. 5 | # 6 | # Licensed under the Apache License, Version 2.0 (the "License"); 7 | # you may not use this file except in compliance with the License. 8 | # You may obtain a copy of the License at 9 | # 10 | # https://www.apache.org/licenses/LICENSE-2.0 11 | # 12 | # Unless required by applicable law or agreed to in writing, software 13 | # distributed under the License is distributed on an "AS IS" BASIS, 14 | # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 15 | # See the License for the specific language governing permissions and 16 | # limitations under the License. 17 | # 18 | 19 | ############################################################################## 20 | ## 21 | ## Gradle start up script for UN*X 22 | ## 23 | ############################################################################## 24 | 25 | # Attempt to set APP_HOME 26 | # Resolve links: $0 may be a link 27 | PRG="$0" 28 | # Need this for relative symlinks. 29 | while [ -h "$PRG" ] ; do 30 | ls=`ls -ld "$PRG"` 31 | link=`expr "$ls" : '.*-> \(.*\)$'` 32 | if expr "$link" : '/.*' > /dev/null; then 33 | PRG="$link" 34 | else 35 | PRG=`dirname "$PRG"`"/$link" 36 | fi 37 | done 38 | SAVED="`pwd`" 39 | cd "`dirname \"$PRG\"`/" >/dev/null 40 | APP_HOME="`pwd -P`" 41 | cd "$SAVED" >/dev/null 42 | 43 | APP_NAME="Gradle" 44 | APP_BASE_NAME=`basename "$0"` 45 | 46 | # Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script. 47 | DEFAULT_JVM_OPTS='"-Xmx64m" "-Xms64m"' 48 | 49 | # Use the maximum available, or set MAX_FD != -1 to use that value. 50 | MAX_FD="maximum" 51 | 52 | warn () { 53 | echo "$*" 54 | } 55 | 56 | die () { 57 | echo 58 | echo "$*" 59 | echo 60 | exit 1 61 | } 62 | 63 | # OS specific support (must be 'true' or 'false'). 64 | cygwin=false 65 | msys=false 66 | darwin=false 67 | nonstop=false 68 | case "`uname`" in 69 | CYGWIN* ) 70 | cygwin=true 71 | ;; 72 | Darwin* ) 73 | darwin=true 74 | ;; 75 | MINGW* ) 76 | msys=true 77 | ;; 78 | NONSTOP* ) 79 | nonstop=true 80 | ;; 81 | esac 82 | 83 | CLASSPATH=$APP_HOME/gradle/wrapper/gradle-wrapper.jar 84 | 85 | # Determine the Java command to use to start the JVM. 86 | if [ -n "$JAVA_HOME" ] ; then 87 | if [ -x "$JAVA_HOME/jre/sh/java" ] ; then 88 | # IBM's JDK on AIX uses strange locations for the executables 89 | JAVACMD="$JAVA_HOME/jre/sh/java" 90 | else 91 | JAVACMD="$JAVA_HOME/bin/java" 92 | fi 93 | if [ ! -x "$JAVACMD" ] ; then 94 | die "ERROR: JAVA_HOME is set to an invalid directory: $JAVA_HOME 95 | 96 | Please set the JAVA_HOME variable in your environment to match the 97 | location of your Java installation." 98 | fi 99 | else 100 | JAVACMD="java" 101 | which java >/dev/null 2>&1 || die "ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH. 102 | 103 | Please set the JAVA_HOME variable in your environment to match the 104 | location of your Java installation." 105 | fi 106 | 107 | # Increase the maximum file descriptors if we can. 108 | if [ "$cygwin" = "false" -a "$darwin" = "false" -a "$nonstop" = "false" ] ; then 109 | MAX_FD_LIMIT=`ulimit -H -n` 110 | if [ $? -eq 0 ] ; then 111 | if [ "$MAX_FD" = "maximum" -o "$MAX_FD" = "max" ] ; then 112 | MAX_FD="$MAX_FD_LIMIT" 113 | fi 114 | ulimit -n $MAX_FD 115 | if [ $? -ne 0 ] ; then 116 | warn "Could not set maximum file descriptor limit: $MAX_FD" 117 | fi 118 | else 119 | warn "Could not query maximum file descriptor limit: $MAX_FD_LIMIT" 120 | fi 121 | fi 122 | 123 | # For Darwin, add options to specify how the application appears in the dock 124 | if $darwin; then 125 | GRADLE_OPTS="$GRADLE_OPTS \"-Xdock:name=$APP_NAME\" \"-Xdock:icon=$APP_HOME/media/gradle.icns\"" 126 | fi 127 | 128 | # For Cygwin or MSYS, switch paths to Windows format before running java 129 | if [ "$cygwin" = "true" -o "$msys" = "true" ] ; then 130 | APP_HOME=`cygpath --path --mixed "$APP_HOME"` 131 | CLASSPATH=`cygpath --path --mixed "$CLASSPATH"` 132 | JAVACMD=`cygpath --unix "$JAVACMD"` 133 | 134 | # We build the pattern for arguments to be converted via cygpath 135 | ROOTDIRSRAW=`find -L / -maxdepth 1 -mindepth 1 -type d 2>/dev/null` 136 | SEP="" 137 | for dir in $ROOTDIRSRAW ; do 138 | ROOTDIRS="$ROOTDIRS$SEP$dir" 139 | SEP="|" 140 | done 141 | OURCYGPATTERN="(^($ROOTDIRS))" 142 | # Add a user-defined pattern to the cygpath arguments 143 | if [ "$GRADLE_CYGPATTERN" != "" ] ; then 144 | OURCYGPATTERN="$OURCYGPATTERN|($GRADLE_CYGPATTERN)" 145 | fi 146 | # Now convert the arguments - kludge to limit ourselves to /bin/sh 147 | i=0 148 | for arg in "$@" ; do 149 | CHECK=`echo "$arg"|egrep -c "$OURCYGPATTERN" -` 150 | CHECK2=`echo "$arg"|egrep -c "^-"` ### Determine if an option 151 | 152 | if [ $CHECK -ne 0 ] && [ $CHECK2 -eq 0 ] ; then ### Added a condition 153 | eval `echo args$i`=`cygpath --path --ignore --mixed "$arg"` 154 | else 155 | eval `echo args$i`="\"$arg\"" 156 | fi 157 | i=`expr $i + 1` 158 | done 159 | case $i in 160 | 0) set -- ;; 161 | 1) set -- "$args0" ;; 162 | 2) set -- "$args0" "$args1" ;; 163 | 3) set -- "$args0" "$args1" "$args2" ;; 164 | 4) set -- "$args0" "$args1" "$args2" "$args3" ;; 165 | 5) set -- "$args0" "$args1" "$args2" "$args3" "$args4" ;; 166 | 6) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" ;; 167 | 7) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" ;; 168 | 8) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" "$args7" ;; 169 | 9) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" "$args7" "$args8" ;; 170 | esac 171 | fi 172 | 173 | # Escape application args 174 | save () { 175 | for i do printf %s\\n "$i" | sed "s/'/'\\\\''/g;1s/^/'/;\$s/\$/' \\\\/" ; done 176 | echo " " 177 | } 178 | APP_ARGS=`save "$@"` 179 | 180 | # Collect all arguments for the java command, following the shell quoting and substitution rules 181 | eval set -- $DEFAULT_JVM_OPTS $JAVA_OPTS $GRADLE_OPTS "\"-Dorg.gradle.appname=$APP_BASE_NAME\"" -classpath "\"$CLASSPATH\"" org.gradle.wrapper.GradleWrapperMain "$APP_ARGS" 182 | 183 | exec "$JAVACMD" "$@" 184 | -------------------------------------------------------------------------------- /android/gradlew.bat: -------------------------------------------------------------------------------- 1 | @rem 2 | @rem Copyright 2015 the original author or authors. 3 | @rem 4 | @rem Licensed under the Apache License, Version 2.0 (the "License"); 5 | @rem you may not use this file except in compliance with the License. 6 | @rem You may obtain a copy of the License at 7 | @rem 8 | @rem https://www.apache.org/licenses/LICENSE-2.0 9 | @rem 10 | @rem Unless required by applicable law or agreed to in writing, software 11 | @rem distributed under the License is distributed on an "AS IS" BASIS, 12 | @rem WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | @rem See the License for the specific language governing permissions and 14 | @rem limitations under the License. 15 | @rem 16 | 17 | @if "%DEBUG%" == "" @echo off 18 | @rem ########################################################################## 19 | @rem 20 | @rem Gradle startup script for Windows 21 | @rem 22 | @rem ########################################################################## 23 | 24 | @rem Set local scope for the variables with windows NT shell 25 | if "%OS%"=="Windows_NT" setlocal 26 | 27 | set DIRNAME=%~dp0 28 | if "%DIRNAME%" == "" set DIRNAME=. 29 | set APP_BASE_NAME=%~n0 30 | set APP_HOME=%DIRNAME% 31 | 32 | @rem Resolve any "." and ".." in APP_HOME to make it shorter. 33 | for %%i in ("%APP_HOME%") do set APP_HOME=%%~fi 34 | 35 | @rem Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script. 36 | set DEFAULT_JVM_OPTS="-Xmx64m" "-Xms64m" 37 | 38 | @rem Find java.exe 39 | if defined JAVA_HOME goto findJavaFromJavaHome 40 | 41 | set JAVA_EXE=java.exe 42 | %JAVA_EXE% -version >NUL 2>&1 43 | if "%ERRORLEVEL%" == "0" goto init 44 | 45 | echo. 46 | echo ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH. 47 | echo. 48 | echo Please set the JAVA_HOME variable in your environment to match the 49 | echo location of your Java installation. 50 | 51 | goto fail 52 | 53 | :findJavaFromJavaHome 54 | set JAVA_HOME=%JAVA_HOME:"=% 55 | set JAVA_EXE=%JAVA_HOME%/bin/java.exe 56 | 57 | if exist "%JAVA_EXE%" goto init 58 | 59 | echo. 60 | echo ERROR: JAVA_HOME is set to an invalid directory: %JAVA_HOME% 61 | echo. 62 | echo Please set the JAVA_HOME variable in your environment to match the 63 | echo location of your Java installation. 64 | 65 | goto fail 66 | 67 | :init 68 | @rem Get command-line arguments, handling Windows variants 69 | 70 | if not "%OS%" == "Windows_NT" goto win9xME_args 71 | 72 | :win9xME_args 73 | @rem Slurp the command line arguments. 74 | set CMD_LINE_ARGS= 75 | set _SKIP=2 76 | 77 | :win9xME_args_slurp 78 | if "x%~1" == "x" goto execute 79 | 80 | set CMD_LINE_ARGS=%* 81 | 82 | :execute 83 | @rem Setup the command line 84 | 85 | set CLASSPATH=%APP_HOME%\gradle\wrapper\gradle-wrapper.jar 86 | 87 | @rem Execute Gradle 88 | "%JAVA_EXE%" %DEFAULT_JVM_OPTS% %JAVA_OPTS% %GRADLE_OPTS% "-Dorg.gradle.appname=%APP_BASE_NAME%" -classpath "%CLASSPATH%" org.gradle.wrapper.GradleWrapperMain %CMD_LINE_ARGS% 89 | 90 | :end 91 | @rem End local scope for the variables with windows NT shell 92 | if "%ERRORLEVEL%"=="0" goto mainEnd 93 | 94 | :fail 95 | rem Set variable GRADLE_EXIT_CONSOLE if you need the _script_ return code instead of 96 | rem the _cmd.exe /c_ return code! 97 | if not "" == "%GRADLE_EXIT_CONSOLE%" exit 1 98 | exit /b 1 99 | 100 | :mainEnd 101 | if "%OS%"=="Windows_NT" endlocal 102 | 103 | :omega 104 | -------------------------------------------------------------------------------- /android/settings.gradle: -------------------------------------------------------------------------------- 1 | rootProject.name = 'StandardEinkLauncher' 2 | apply from: file("../node_modules/@react-native-community/cli-platform-android/native_modules.gradle"); applyNativeModulesSettingsGradle(settings) 3 | include ':app' 4 | include ':react-native-fs' 5 | project(':react-native-fs').projectDir = new File(settingsDir, '../node_modules/react-native-fs/android') 6 | include ':react-native-fast-image' 7 | project(':react-native-fast-image').projectDir = new File(rootProject.projectDir, '../node_modules/react-native-fast-image/android') 8 | -------------------------------------------------------------------------------- /app.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "StandardEinkLauncher", 3 | "displayName": "모두의 이북" 4 | } -------------------------------------------------------------------------------- /babel.config.js: -------------------------------------------------------------------------------- 1 | module.exports = { 2 | presets: ['module:metro-react-native-babel-preset'], 3 | plugins: ['react-native-reanimated/plugin'], 4 | }; 5 | -------------------------------------------------------------------------------- /image/original.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/nerious2/SEM/9dc3905aab32ecf2792d069aeb93b88c81021ee7/image/original.png -------------------------------------------------------------------------------- /image/patch.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/nerious2/SEM/9dc3905aab32ecf2792d069aeb93b88c81021ee7/image/patch.png -------------------------------------------------------------------------------- /index.js: -------------------------------------------------------------------------------- 1 | /** 2 | * @format 3 | */ 4 | 5 | import {AppRegistry, Text, TextInput} from 'react-native'; 6 | import Root from './Root'; 7 | import {name as appName} from './app.json'; 8 | 9 | Text.defaultProps = Text.defaultProps || {}; 10 | Text.defaultProps.allowFontScaling = false; 11 | 12 | TextInput.defaultProps = TextInput.defaultProps || {}; 13 | TextInput.defaultProps.autoCorrect = false; 14 | TextInput.defaultProps.allowFontScaling = false; 15 | 16 | 17 | AppRegistry.registerComponent(appName, () => Root); 18 | -------------------------------------------------------------------------------- /metro.config.js: -------------------------------------------------------------------------------- 1 | /** 2 | * Metro configuration for React Native 3 | * https://github.com/facebook/react-native 4 | * 5 | * @format 6 | */ 7 | 8 | module.exports = { 9 | transformer: { 10 | getTransformOptions: async () => ({ 11 | transform: { 12 | experimentalImportSupport: false, 13 | inlineRequires: false, 14 | }, 15 | }), 16 | }, 17 | }; 18 | -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "StandardEinkLauncher", 3 | "version": "0.0.1", 4 | "private": true, 5 | "scripts": { 6 | "postinstall": "jetify && patch-package", 7 | "android": "react-native run-android", 8 | "ios": "react-native run-ios", 9 | "start": "react-native start", 10 | "test": "jest", 11 | "lint": "eslint ." 12 | }, 13 | "dependencies": { 14 | "@react-native-async-storage/async-storage": "^1.17.10", 15 | "@react-native-community/netinfo": "9.3.6", 16 | "@react-navigation/native": "^6.0.8", 17 | "@react-navigation/stack": "5.14.9", 18 | "react": "16.14.0", 19 | "react-dom": "16.14.0", 20 | "react-native": "0.63.4", 21 | "react-native-alert-async": "^1.0.5", 22 | "react-native-animated-progress": "^1.0.2", 23 | "react-native-apk-installer-n": "^2.1.2", 24 | "react-native-device-info": "^10.3.0", 25 | "react-native-exit-app": "^1.1.0", 26 | "react-native-fast-image": "5.4.2", 27 | "react-native-fs": "^2.19.0", 28 | "react-native-gesture-handler": "1.10.3", 29 | "react-native-popup-menu": "^0.16.1", 30 | "react-native-reanimated": "^2.13.0", 31 | "react-native-safe-area-context": "3.3.2", 32 | "react-native-screens": "3.11.1", 33 | "react-native-vector-icons": "^9.1.0", 34 | "react-native-version-check": "^3.4.7", 35 | "react-native-zip-archive": "^6.0.9", 36 | "react-redux": "^7.2.6", 37 | "redux": "^4.1.2", 38 | "redux-persist": "^6.0.0", 39 | "rn-fetch-blob": "^0.12.0" 40 | }, 41 | "devDependencies": { 42 | "@babel/core": "^7.17.5", 43 | "@babel/runtime": "^7.17.2", 44 | "@react-native-community/eslint-config": "^3.0.1", 45 | "babel-jest": "^27.5.1", 46 | "babel-plugin-transform-remove-console": "^6.9.4", 47 | "eslint": "^8.9.0", 48 | "jest": "^27.5.1", 49 | "metro-react-native-babel-preset": "^0.68.0", 50 | "patch-package": "^6.5.0", 51 | "postinstall-postinstall": "^2.1.0", 52 | "react-test-renderer": "16.13.1" 53 | }, 54 | "jest": { 55 | "preset": "react-native" 56 | } 57 | } 58 | -------------------------------------------------------------------------------- /patches/@react-native-community+netinfo+9.3.6.patch: -------------------------------------------------------------------------------- 1 | diff --git a/node_modules/@react-native-community/netinfo/android/src/main/java/com/reactnativecommunity/netinfo/ConnectivityReceiver.java b/node_modules/@react-native-community/netinfo/android/src/main/java/com/reactnativecommunity/netinfo/ConnectivityReceiver.java 2 | index baeff73..e9b8549 100644 3 | --- a/node_modules/@react-native-community/netinfo/android/src/main/java/com/reactnativecommunity/netinfo/ConnectivityReceiver.java 4 | +++ b/node_modules/@react-native-community/netinfo/android/src/main/java/com/reactnativecommunity/netinfo/ConnectivityReceiver.java 5 | @@ -125,9 +125,8 @@ public abstract class ConnectivityReceiver { 6 | mConnectionType = connectionType; 7 | mCellularGeneration = cellularGeneration; 8 | mIsInternetReachable = isInternetReachable; 9 | - if (hasListener) { 10 | - sendConnectivityChangedEvent(); 11 | - } 12 | + 13 | + sendConnectivityChangedEvent(); 14 | } 15 | } 16 | 17 | -------------------------------------------------------------------------------- /patches/react-native+0.63.4.patch: -------------------------------------------------------------------------------- 1 | # Fix nativeBackgroundAndroid issue/crash on android 4.3 and 4.4 on TouchableNativeFeedback 2 | # https://github.com/facebook/react-native/issues/29414 3 | 4 | diff --git a/node_modules/react-native/Libraries/Components/Touchable/TouchableNativeFeedback.js b/node_modules/react-native/Libraries/Components/Touchable/TouchableNativeFeedback.js 5 | index 5dc03df..e526092 100644 6 | --- a/node_modules/react-native/Libraries/Components/Touchable/TouchableNativeFeedback.js 7 | +++ b/node_modules/react-native/Libraries/Components/Touchable/TouchableNativeFeedback.js 8 | @@ -338,7 +338,7 @@ class TouchableNativeFeedback extends React.Component { 9 | } 10 | 11 | const getBackgroundProp = 12 | - Platform.OS === 'android' 13 | + Platform.OS === 'android' && Platform.Version >= 21 14 | ? (background, useForeground) => 15 | useForeground && TouchableNativeFeedback.canUseNativeForeground() 16 | ? {nativeForegroundAndroid: background} 17 | -------------------------------------------------------------------------------- /src/App.js: -------------------------------------------------------------------------------- 1 | import React from 'react'; 2 | 3 | import { NavigationContainer } from '@react-navigation/native'; 4 | import { createStackNavigator } from '@react-navigation/stack'; 5 | 6 | import MainScreen from './MainScreen'; 7 | import OSSLicenseStack from './Component/OSSLicenseStack'; 8 | 9 | const App = () => { 10 | const Stack = createStackNavigator(); 11 | 12 | return ( 13 | 14 | 15 | 20 | 29 | 38 | 39 | 40 | ); 41 | } 42 | 43 | export default App; -------------------------------------------------------------------------------- /src/Component/AppDetailModal.js: -------------------------------------------------------------------------------- 1 | import React, {createContext, useContext, useReducer, useState, useRef, createRef, useEffect} from 'react'; 2 | 3 | import { 4 | SafeAreaView, 5 | StyleSheet, 6 | ScrollView, 7 | View, 8 | Image, 9 | NativeModules, 10 | Text, 11 | Alert, 12 | StatusBar, 13 | Animated, 14 | Pressable, 15 | Button, 16 | Dimensions, 17 | Platform, 18 | AppState, 19 | ToastAndroid, 20 | PermissionsAndroid, 21 | Modal, 22 | } from 'react-native'; 23 | 24 | import RNFS from 'react-native-fs'; 25 | import MaterialIcons from 'react-native-vector-icons/MaterialIcons'; 26 | import BottomToolbar from './BottomToolbar'; 27 | import FastImage from 'react-native-fast-image'; 28 | import { androidAPItoVersion } from './GlobalContext'; 29 | 30 | /* Peer List Context */ 31 | const appDetailContext = createContext(); 32 | 33 | 34 | // #################### DB list Reducer ##################### 35 | 36 | //initial state 37 | const initialAppDetailState = { 38 | modalVisible: false, 39 | package: '', 40 | label: '', 41 | iconPath: '', 42 | description: '', 43 | patch_description: '', 44 | version: '', 45 | date: '', 46 | update_log: [], 47 | }; 48 | 49 | // create reducer 50 | const reducerAppDetail = (state = initialAppDetailState, action) => { 51 | let savedData = new Array(); 52 | switch (action.type) { 53 | case 'INIT_APP_DETAIL': 54 | return action.payload; 55 | case 'SET_MODAL_VISIBLE': 56 | return { 57 | ...state, 58 | modalVisible: action.payload, 59 | }; 60 | default: 61 | return state; 62 | } 63 | }; 64 | 65 | const AppDetailModalProvider = ({children}) => { 66 | const [state, dispatch] = useReducer(reducerAppDetail, initialAppDetailState); 67 | const value = {state, dispatch}; 68 | return ( 69 | {children} 70 | ); 71 | }; 72 | 73 | export {AppDetailModalProvider, appDetailContext}; 74 | 75 | 76 | 77 | 78 | export default function AppDetailModal () { 79 | 80 | const appDetailContextState = useContext(appDetailContext).state; 81 | const appDetailContextDispatch = useContext(appDetailContext).dispatch; 82 | 83 | const [appLatestUpdateLog, setAppLatestUpdateLog] = useState(''); 84 | const [appUpdateLog, setAppUpdateLog] = useState([]); 85 | 86 | // 스크롤뷰 Ref 87 | const scrollViewRef = useRef(); 88 | 89 | useEffect(() => { 90 | // 내림차순 정렬 91 | function descend(a, b) { 92 | return a.index > b.index ? -1 : a.index < b.index ? 1 : 0; 93 | } 94 | 95 | 96 | if (!appDetailContextState.update_log) return; 97 | const result = appDetailContextState.update_log.filter((item) => item?.version === appDetailContextState.version); 98 | console.log('ssssssssssssssss result length :: ', result.length) 99 | if (result.length > 0) { 100 | setAppLatestUpdateLog(result[0].contents === '' ? '업데이트 정보가 없습니다.' : result[0].contents); 101 | console.log('ssssssssssssssss :: ', result[0].contents) 102 | } else { 103 | console.log('ssssssssssssssss :: failed'); 104 | setAppLatestUpdateLog('업데이트 정보가 없습니다.'); 105 | } 106 | 107 | // 이전 버전 기록 리스트 생성 108 | const result2 = appDetailContextState.update_log.filter((item) => item?.version !== appDetailContextState.version).sort(descend); 109 | console.log('ssssssssssssssss result2 length :: ', result2.length) 110 | console.log(JSON.stringify(result2)); 111 | 112 | setAppUpdateLog(result2); 113 | 114 | 115 | }, [appDetailContextState]); 116 | 117 | const [scrollDimensionHeight, setScrollDimensionHeight] = useState(0); 118 | const [scrollCurrentY, setScrollCurrentY] = useState(0); 119 | 120 | const handleScroll = (event) => { 121 | console.log(event.nativeEvent.contentOffset.y); 122 | // scrollCurrentY = event.nativeEvent.contentOffset.y; 123 | setScrollCurrentY(event.nativeEvent.contentOffset.y); 124 | } 125 | 126 | const handleScrollLayout = (event) => { 127 | setScrollDimensionHeight(event.nativeEvent.layout.height); 128 | // scrollDimensionHeight = event.nativeEvent.layout.height; 129 | console.log('event nativeEvent layout'); 130 | console.log(event.nativeEvent.layout); 131 | } 132 | 133 | 134 | 135 | return ( 136 | { 141 | appDetailContextDispatch({ 142 | type: 'SET_MODAL_VISIBLE', 143 | payload: false, 144 | }); 145 | }} 146 | > 147 | 148 | 149 | {/* Modal Header */} 150 | 151 | 152 | {/* Icon */} 153 | {/* */} 159 | 160 | 161 | {/* 패치 마크 */} 162 | { 163 | appDetailContextState?.isPatched && ( 164 | 165 | 170 | 171 | ) 172 | } 173 | 174 | {/* Text */} 175 | 176 | {appDetailContextState.label} 177 | 세부정보 178 | 179 | 180 | 181 | 182 | [ 184 | { 185 | width: 55, 186 | // height: 50, 187 | justifyContent: "center", 188 | alignItems: "center", 189 | // backgroundColor: '#a0a0a0', 190 | } 191 | ]} 192 | onPress={() => 193 | appDetailContextDispatch({ 194 | type: 'SET_MODAL_VISIBLE', 195 | payload: false, 196 | }) 197 | } 198 | > 199 | 200 | 201 | 202 | 203 | 204 | {/* Modal Body */} 205 | 211 | {/* 새로운 기능 */} 212 | 213 | 214 | 새로운 기능 215 | {appDetailContextState.isInstalledToNew && 216 | 현재 기기에 설치된 버전이 모두의 이북에서 제공하는 버전보다 최신입니다. 217 | } 218 | {`[버전 : ${appDetailContextState.version} / 업데이트 날짜 : ${appDetailContextState.date}]`} 219 | 220 | 221 | {/* 필요 안드로이드 버전 */} 222 | 223 | 224 | Android {androidAPItoVersion(appDetailContextState.minimumAndroidSdk)} 225 | 226 | 227 | 228 | 229 | {appLatestUpdateLog} 230 | 231 | 232 | 233 | 234 | {/* 패치 정보 (있을 경우에만 표시) */} 235 | { 236 | appDetailContextState?.patch_description?.length > 0 && 237 | ( 238 | <> 239 | 240 | 패치 정보 241 | {appDetailContextState.patch_description} 242 | 243 | 244 | 245 | 246 | ) 247 | } 248 | 249 | 250 | {/* 앱 정보 */} 251 | 252 | 앱 정보 253 | {appDetailContextState.description} 254 | 255 | 256 | 257 | {/* 이전 버전 내역 (있을 경우에만) */} 258 | 259 | { 260 | appUpdateLog?.length > 0 && 261 | ( 262 | <> 263 | 264 | 265 | 266 | 이전 버전 내역 267 | { 268 | appUpdateLog.map((log, index, logArr) => { 269 | return ( 270 | <> 271 | {`[버전 : ${log.version} / 업데이트 날짜 : ${log.date}]`} 272 | {log.contents.length != 0 ? log.contents : "업데이트 내용 없음"} 273 | { 274 | logArr.length !== index + 1 && () 275 | } 276 | 277 | ); 278 | }) 279 | } 280 | 281 | 282 | ) 283 | } 284 | 285 | 286 | 287 | 288 | 289 | 290 | 291 | 292 | 293 | {/* 하단 페이지 이동 바 */} 294 | 295 | 296 | 297 | ); 298 | } 299 | 300 | 301 | const styles = StyleSheet.create({ 302 | centeredView: { 303 | flex: 1, 304 | justifyContent: "center", 305 | alignItems: "center", 306 | // marginBottom: 22 307 | }, 308 | modalView: { 309 | margin: 20, 310 | width: '90%', 311 | height: '95%', 312 | backgroundColor: "white", 313 | borderRadius: 10, 314 | // padding: 35, 315 | alignItems: "center", 316 | borderWidth: 2, 317 | borderColor: '#000000', 318 | // 그림자 319 | // shadowColor: "#000", 320 | // shadowOffset: { 321 | // width: 0, 322 | // height: 2 323 | // }, 324 | // shadowOpacity: 0.25, 325 | // shadowRadius: 4, 326 | // elevation: 5 327 | }, 328 | modalHeader: { 329 | flexDirection: 'row', 330 | justifyContent: 'space-between', 331 | // paddingBottom: 10, 332 | borderBottomColor: '#000000', 333 | borderBottomWidth: 2, 334 | 335 | }, 336 | modalHeaderInner: { 337 | flexDirection: 'row', 338 | flex: 1, 339 | height: 50, 340 | // backgroundColor: '#707070', 341 | alignItems: 'center', 342 | paddingLeft: 10, 343 | // paddingTop: 10, 344 | marginTop: 5, 345 | marginBottom: 5, 346 | }, 347 | separator: { 348 | borderBottomWidth: 1.7, 349 | borderBottomColor: '#000000', 350 | width: '100%', 351 | // marginVertical: 10, 352 | }, 353 | updateLogSeparator: { 354 | borderBottomWidth: 1, 355 | borderBottomColor: '#000000', 356 | width: '100%', 357 | marginBottom: 10, 358 | }, 359 | }); -------------------------------------------------------------------------------- /src/Component/AppVersionSelectList.js: -------------------------------------------------------------------------------- 1 | import React, {useEffect, useState, useRef, useContext} from 'react'; 2 | import { 3 | FlatList, 4 | StyleSheet, 5 | View, 6 | Pressable, 7 | Text, 8 | ToastAndroid, 9 | Image, 10 | AppState, 11 | Platform, 12 | NativeModules, 13 | } from 'react-native'; 14 | import ProgressBar from "react-native-animated-progress"; 15 | 16 | import FastImage from 'react-native-fast-image'; 17 | import RNFS from 'react-native-fs'; 18 | import { useNetInfo } from '@react-native-community/netinfo'; 19 | import { appDetailContext } from './AppDetailModal'; 20 | import { globalContext, formatBytes, androidAPItoVersion } from './GlobalContext'; 21 | import DeviceInfo from 'react-native-device-info'; 22 | 23 | export default function AppVersionSelectList({item, packageName, packageLabel, onPressAppButton}) { 24 | 25 | return ( 26 | 30 | 37 | } 38 | keyExtractor={(item, index) => index.toString()} 39 | ItemSeparatorComponent={() => } 40 | ListFooterComponent={() => } 41 | // ListFooterComponentStyle={{height: 20,}} 42 | /> 43 | ); 44 | } 45 | 46 | 47 | 48 | // ==================== AppVersionSelectListItem ==================== 49 | // item : [ 50 | // { 51 | // "index": 0, 52 | // "version": "20.1.2", 53 | // "version_show": "20.1.2 E-Ink noTTS", 54 | // "apk_url": "https://repo2.senia.kr/sem/app/com.initialcoms.ridi/RIDI-20.1.2-E-Ink-noTTS.apk", 55 | // "minimum_android_sdk": 16, 56 | // "contents": "안드로이드 4.1 이상 지원 마지막 버전" 57 | // },,... 58 | // ] 59 | 60 | function AppVersionSelectListItem({item, index, packageName, label, onPressAppButton}) { 61 | 62 | const appState = useRef(AppState.currentState); 63 | 64 | const isInternetReachable = useNetInfo().isInternetReachable; 65 | const [progressBar, setProgressBar] = useState(-1); 66 | const [nowDownloadJobId, setNowDownloadJobId] = useState(-1); 67 | // 다운로드 버튼 텍스트 68 | const [actionButtonText, setActionButtonText] = useState(''); 69 | 70 | 71 | // app detail modal context 72 | const appDetailContextState = useContext(appDetailContext).state; 73 | const appDetailContextDispatch = useContext(appDetailContext).dispatch; 74 | 75 | // Global Context 76 | const globalContextState = useContext(globalContext).state; 77 | const globalContextDispatch = useContext(globalContext).dispatch; 78 | 79 | // icon path 80 | const iconPath = `file://${RNFS.DocumentDirectoryPath}/${item.package}/ic_launcher.png`; 81 | 82 | // jobId 83 | // const [jobId, setJobId] = useState(-1); 84 | 85 | 86 | // const handleAppStateChange = nextAppState => { 87 | // console.log(`-----appState nextAppState ${appState.current} -> ${nextAppState}`); 88 | // if ( 89 | // appState.current.match(/inactive|background/) && 90 | // nextAppState === 'active' 91 | // ) { 92 | // // 인스톨 패키지 기록 93 | // globalContextDispatch({ 94 | // type: 'SET_INSTALLING_PACKAGE', 95 | // payload: { 96 | // package: item.package, 97 | // version: item.version, 98 | // } 99 | // }); 100 | 101 | // AppState.removeEventListener('change', handleAppStateChange); 102 | 103 | // } 104 | 105 | // } 106 | 107 | 108 | useEffect(() => { 109 | let str = ''; 110 | if (Platform.Version < item.minimum_android_sdk) { 111 | // if (16 < item.minimum_android_sdk) { 112 | str = '설치불가'; 113 | } else { 114 | str = '다운로드'; 115 | } 116 | setActionButtonText(str); 117 | }, [item]); 118 | 119 | 120 | return ( 121 | 122 | 123 | 124 | 125 | {/* 버전명 */} 126 | {item.version_show} 127 | {/* 필요 안드로이드 버전 */} 128 | 129 | 130 | Android {androidAPItoVersion(item.minimum_android_sdk)} 131 | 132 | 133 | 134 | {/* 설명 */} 135 | 136 | 137 | {item.contents} 138 | 139 | 140 | 141 | 142 | 143 | 144 | 145 | {/* 실행/업데이트/다운로드 버튼 */} 146 | 147 | [ 149 | { 150 | backgroundColor: actionButtonText == '설치불가' 151 | ? '#404040' 152 | : pressed 153 | ? '#a0a0a0' 154 | : 'white' 155 | }, 156 | styles.appButton 157 | ]} 158 | onPress={() => { 159 | if (actionButtonText == '설치불가') { 160 | // const version = NativeModules.InstalledApps.getAndroidRelease(); 161 | ToastAndroid.show(`해당 기기의 안드로이드 버전과 호환되지 않습니다.\n현재 Android 버전 : ${DeviceInfo.getSystemVersion()}\n필요 Android 버전 : ${androidAPItoVersion(item.minimum_android_sdk)}`, ToastAndroid.LONG); 162 | } else { 163 | onPressAppButton(packageName, item.version, item.apk_url, [actionButtonText, setActionButtonText], [progressBar, setProgressBar], [nowDownloadJobId, setNowDownloadJobId]); 164 | } 165 | }} 166 | > 167 | {actionButtonText} 174 | { 175 | nowDownloadJobId !== -1 && ( 176 | 177 | 184 | 185 | ) 186 | } 187 | 188 | 189 | 190 | {/* 앱 용량 표시 */} 191 | { 192 | item?.apk_size !== undefined && ( 193 | {formatBytes(item.apk_size, 1)} 194 | ) 195 | } 196 | 197 | 198 | ); 199 | } 200 | 201 | const styles = StyleSheet.create({ 202 | block: {backgroundColor: '#ffffff'}, 203 | appBlock : { 204 | marginHorizontal: 10, 205 | // marginVertical: 5, 206 | // paddingRight: 10, 207 | // width: '100%', 208 | // height: 100, 209 | 210 | flexDirection: 'row', 211 | justifyContent: 'space-between', 212 | }, 213 | icon: { 214 | //marginRight: 16, 215 | width: 60, 216 | // height: 60, 217 | // backgroundColor: '#a0a0a0', 218 | marginHorizontal: 10, 219 | justifyContent: 'center', 220 | // alignItems: 'center', 221 | }, 222 | appLabel: { 223 | fontSize: 16, 224 | marginBottom: -2, 225 | lineHeight: 19, 226 | }, 227 | appContents: { 228 | fontWeight: 'normal', 229 | textDecorationLine: 'none', 230 | fontSize: 13, 231 | lineHeight: 16, 232 | flex: 1, 233 | }, 234 | appButton: { 235 | width: 70, 236 | height: 50, 237 | borderRadius: 5, 238 | borderWidth: 1, 239 | borderColor: '#000000', 240 | justifyContent: 'center', 241 | alignItems: 'center', 242 | }, 243 | appSize: { 244 | fontSize: 13, 245 | textAlign: 'center', 246 | lineHeight: 16, 247 | }, 248 | }); -------------------------------------------------------------------------------- /src/Component/AppVersionSelectModal.js: -------------------------------------------------------------------------------- 1 | import React, {createContext, useContext, useReducer, useState, useRef, createRef, useEffect} from 'react'; 2 | 3 | import { 4 | SafeAreaView, 5 | StyleSheet, 6 | ScrollView, 7 | View, 8 | Image, 9 | NativeModules, 10 | Text, 11 | Alert, 12 | StatusBar, 13 | Animated, 14 | Pressable, 15 | Button, 16 | Dimensions, 17 | Platform, 18 | AppState, 19 | ToastAndroid, 20 | PermissionsAndroid, 21 | Modal, 22 | } from 'react-native'; 23 | 24 | import RNFS from 'react-native-fs'; 25 | import MaterialIcons from 'react-native-vector-icons/MaterialIcons'; 26 | import BottomToolbar from './BottomToolbar'; 27 | import FastImage from 'react-native-fast-image'; 28 | import AppVersionSelectList from './AppVersionSelectList'; 29 | import { globalContext, formatBytes, androidAPItoVersion } from './GlobalContext'; 30 | import { useNetInfo } from '@react-native-community/netinfo'; 31 | import ProgressBar from "react-native-animated-progress"; 32 | import DeviceInfo from 'react-native-device-info'; 33 | import RNFetchBlob from "rn-fetch-blob"; 34 | import RNApkInstallerN from 'react-native-apk-installer-n'; 35 | 36 | /* Peer List Context */ 37 | const appVersionSelectContext = createContext(); 38 | 39 | 40 | // app 41 | 42 | 43 | // #################### DB list Reducer ##################### 44 | 45 | //initial state 46 | const initialAppVersionSelectState = { 47 | modalVisible: false, 48 | package: '', 49 | label: '', 50 | iconPath: '', 51 | latestVersion: '', 52 | latestDate: '', 53 | latestApkUrl: '', 54 | latestUpdateLog: '', 55 | minimumAndroidSdk: 0, 56 | versionCode: 0, 57 | versionList: [], 58 | }; 59 | 60 | // create reducer 61 | const reducerAppVersionSelect = (state = initialAppVersionSelectState, action) => { 62 | let savedData = new Array(); 63 | switch (action.type) { 64 | case 'INIT_APP_VERSION_LIST': 65 | return action.payload; 66 | case 'SET_MODAL_VISIBLE': 67 | return { 68 | ...state, 69 | modalVisible: action.payload, 70 | }; 71 | default: 72 | return state; 73 | } 74 | }; 75 | 76 | const AppVersionSelectModalProvider = ({children}) => { 77 | const [state, dispatch] = useReducer(reducerAppVersionSelect, initialAppVersionSelectState); 78 | const value = {state, dispatch}; 79 | return ( 80 | {children} 81 | ); 82 | }; 83 | 84 | export {AppVersionSelectModalProvider, appVersionSelectContext, initialAppVersionSelectState }; 85 | 86 | 87 | 88 | 89 | 90 | 91 | export default function AppVersionSelectModal () { 92 | 93 | const appVSContextState = useContext(appVersionSelectContext).state; 94 | const appVSContextDispatch = useContext(appVersionSelectContext).dispatch; 95 | 96 | const [appLatestUpdateLog, setAppLatestUpdateLog] = useState(''); 97 | 98 | 99 | // 스크롤뷰 Ref 100 | const scrollViewRef = useRef(); 101 | 102 | // Global Context 103 | const globalContextState = useContext(globalContext).state; 104 | const globalContextDispatch = useContext(globalContext).dispatch; 105 | 106 | // 다운로드 버튼 텍스트 107 | const [actionButtonText, setActionButtonText] = useState(''); 108 | const [progressBar, setProgressBar] = useState(-1); 109 | const [nowDownloadJobId, setNowDownloadJobId] = useState(-1); 110 | 111 | const isInternetReachable = useNetInfo().isInternetReachable; 112 | 113 | const [scrollDimensionHeight, setScrollDimensionHeight] = useState(0); 114 | const [scrollCurrentY, setScrollCurrentY] = useState(0); 115 | 116 | 117 | 118 | const handleScroll = (event) => { 119 | console.log(event.nativeEvent.contentOffset.y); 120 | // scrollCurrentY = event.nativeEvent.contentOffset.y; 121 | setScrollCurrentY(event.nativeEvent.contentOffset.y); 122 | } 123 | 124 | const handleScrollLayout = (event) => { 125 | setScrollDimensionHeight(event.nativeEvent.layout.height); 126 | // scrollDimensionHeight = event.nativeEvent.layout.height; 127 | console.log('event nativeEvent layout'); 128 | console.log(event.nativeEvent.layout); 129 | } 130 | 131 | useEffect(() => { 132 | // 내림차순 정렬 133 | function descend(a, b) { 134 | return a.index > b.index ? -1 : a.index < b.index ? 1 : 0; 135 | } 136 | 137 | 138 | if (!appVSContextState.latestUpdateLog) return; 139 | const result = appVSContextState.latestUpdateLog.filter((item) => item?.version === appVSContextState.latestVersion); 140 | 141 | if (result.length > 0) { 142 | setAppLatestUpdateLog(result[0].contents === '' ? '업데이트 정보가 없습니다.' : result[0].contents); 143 | } else { 144 | setAppLatestUpdateLog('업데이트 정보가 없습니다.'); 145 | } 146 | 147 | let str = ''; 148 | if (Platform.Version < appVSContextState.minimumAndroidSdk) { 149 | // if (16 < item.minimum_android_sdk) { 150 | str = '설치불가'; 151 | } else { 152 | str = '다운로드'; 153 | } 154 | setActionButtonText(str); 155 | 156 | }, [appVSContextState]); 157 | 158 | 159 | 160 | const onPressAppButton = async (appPackage, newVersion, url, [actionButtonText, setActionButtonText], [progressBar, setProgressBar], [nowDownloadJobId, setNowDownloadJobId]) => { 161 | 162 | console.log('====appPackage : ', appPackage); 163 | console.log('====newVersion : ', newVersion); 164 | 165 | const downloadfilePath = `${RNFS.ExternalCachesDirectoryPath}/@sem_temp.apk`; 166 | console.log('============= nowDownloadJobId :: ', nowDownloadJobId); 167 | 168 | // 다른 다운로드 작업 진행중 or 새로고침 중일 경우 예외처리 169 | if (globalContextState.isAppRefresh) { 170 | ToastAndroid.show('앱 새로고침 작업이 끝나고 시도해주세요.', ToastAndroid.SHORT); 171 | return; 172 | } else if (nowDownloadJobId != -1) { 173 | // 다운로드 취소 174 | setActionButtonText('취소 중'); 175 | // RNFS.stopDownload(nowDownloadJobId); 176 | nowDownloadJobId.cancel((err) => {console.log("DOWNLOAD CANCELED ERROR : ", err)}); 177 | console.log('stopDownload ', nowDownloadJobId); 178 | 179 | if (nowDownloadJobId == globalContextState.nowDownloadJobId) { 180 | console.log('current job ID (GlobalContext)'); 181 | globalContextDispatch({ 182 | type: 'SET_NOW_DOWNLOAD_JOBID', 183 | payload: -1 184 | }); 185 | } 186 | 187 | // 임시파일 삭제 넣어야됨 188 | try { 189 | // update apk 파일이 이미 있는 경우 삭제 190 | 191 | 192 | const updateApkExist = await RNFS.exists(downloadfilePath); 193 | console.log('update temp APK exists : ', updateApkExist); 194 | if (updateApkExist) { 195 | console.log('unlink update APK'); 196 | await RNFS.unlink(downloadfilePath); 197 | console.log('unlink update APK OK'); 198 | } 199 | } catch (e) { 200 | console.log('onPressAppButton : temp apk check error === ', e); 201 | } 202 | 203 | return; 204 | } else if (globalContextState.nowDownloadJobId != -1) { 205 | ToastAndroid.show('이미 다른 다운로드가 진행 중입니다.', ToastAndroid.SHORT); 206 | return; 207 | } 208 | 209 | 210 | console.log('url : ', url); 211 | console.log('DOWOLOAD ', downloadfilePath); 212 | 213 | if (!isInternetReachable) { 214 | ToastAndroid.show('인터넷이 연결되어 있지 않습니다. 와이파이를 연결해주세요.', ToastAndroid.SHORT); 215 | return; 216 | } 217 | 218 | try { 219 | const latestActionButtonText = actionButtonText; 220 | setActionButtonText('진행 중'); 221 | setProgressBar(0); 222 | // let jobId = -1; 223 | 224 | // update apk 파일이 이미 있는 경우 삭제 225 | const updateApkExist = await RNFS.exists(downloadfilePath); 226 | console.log('update temp APK exists : ', updateApkExist); 227 | if (updateApkExist) { 228 | console.log('unlink update APK'); 229 | await RNFS.unlink(downloadfilePath); 230 | console.log('unlink update APK OK'); 231 | } 232 | 233 | 234 | // // 자체 타임아웃 구현 235 | // const downloadTimeout = setTimeout(() => { 236 | // console.log('jobid :: ', jobId); 237 | // if (jobId != -1) { 238 | // RNFS.stopDownload(jobId); 239 | // console.log('stopDownload ', jobId); 240 | 241 | // globalContextDispatch({ 242 | // type: 'SET_NOW_DOWNLOAD_JOBID', 243 | // payload: -1 244 | // }); 245 | // } 246 | 247 | // setNowDownloadJobId(-1); 248 | // setProgressBar(0); 249 | 250 | // ToastAndroid.show('서버에 연결하는데 실패했습니다.', ToastAndroid.SHORT); 251 | // setActionButtonText(latestActionButtonText); 252 | 253 | // console.log('====== timeout :'); 254 | // }, 30000); 255 | 256 | // const ret = RNFS.downloadFile({ 257 | // fromUrl: url, 258 | // toFile: downloadfilePath, 259 | // connectionTimeout: 30000, 260 | // readTimeout: 30000, 261 | // progressInterval: 500, 262 | // // progressDivider: 10, 263 | // begin: (res) => { 264 | // clearTimeout(downloadTimeout); 265 | // console.log("Response begin ===\n\n"); 266 | // console.log(res); 267 | // if (res.statusCode == 200) { 268 | // setProgressBar(0); 269 | 270 | // } else { 271 | // ToastAndroid.show('다운로드에 실패했습니다. 잠시 후 다시 시도하세요.', ToastAndroid.SHORT); 272 | // setActionButtonText(latestActionButtonText); 273 | // setNowDownloadJobId(-1); 274 | // setProgressBar(0); 275 | // } 276 | // }, 277 | // progress: (res) => { 278 | // //here you can calculate your progress for file download 279 | 280 | // console.log("Response written ===\n\n"); 281 | // let progressPercent = (res.bytesWritten / res.contentLength) * 100; // to calculate in percentage 282 | // console.log("\n\nprogress===", progressPercent) 283 | // setProgressBar(progressPercent); 284 | // // this.setState({ progress: progressPercent.toString() }); 285 | // // item.downloadProgress = progressPercent; 286 | // console.log(res); 287 | // } 288 | // }); 289 | // console.log('set jobId ::: ', ret.jobId); 290 | 291 | 292 | const ret = RNFetchBlob.config({ 293 | trusty: true, 294 | path: downloadfilePath, 295 | }).fetch( 296 | "GET", 297 | url, 298 | ).progress((received, total) => { 299 | // console.log("Response written ===\n\n"); 300 | let progressPercent = (received / total) * 100; // to calculate in percentage 301 | console.log("progress===", progressPercent) 302 | setProgressBar(progressPercent); 303 | // this.setState({ progress: progressPercent.toString() }); 304 | // item.downloadProgress = progressPercent; 305 | }); 306 | 307 | // === react-native-fs 308 | // setNowDownloadJobId(ret.jobId); 309 | // === rn-fetch-blob 310 | setNowDownloadJobId(ret); 311 | 312 | globalContextDispatch({ 313 | type: 'SET_NOW_DOWNLOAD_JOBID', 314 | // === react-native-fs 315 | // payload: ret.jobId 316 | // === rn-fetch-blob 317 | payload: ret 318 | }); 319 | // jobId = ret.jobId; 320 | 321 | // ret.promise.then(res => { 322 | ret.then((res) => { 323 | 324 | globalContextDispatch({ 325 | type: 'SET_NOW_DOWNLOAD_JOBID', 326 | payload: -1 327 | }); 328 | 329 | setNowDownloadJobId(-1); 330 | setProgressBar(0); 331 | 332 | // === react-native-fs 333 | // if(res.statusCode == 200) { 334 | // === rn-fetch-blob 335 | if(res?.respInfo.status == 200) { 336 | // AppState.addEventListener('change', handleAppStateChange); 337 | // setProgressBar(100); 338 | setActionButtonText('설치 중'); 339 | console.log("res for saving file===", res); 340 | console.log('globalContext installing package : ', appPackage, newVersion); 341 | 342 | // 인스톨 패키지 기록 343 | globalContextDispatch({ 344 | type: 'SET_INSTALLING_PACKAGE', 345 | payload: { 346 | package: appPackage, 347 | version: newVersion, 348 | setActionButtonText: setActionButtonText, 349 | latestActionButtonText: latestActionButtonText, 350 | } 351 | }); 352 | 353 | RNApkInstallerN.install(downloadfilePath); 354 | // RNFetchBlob.android.actionViewIntent( 355 | // res.path(), 356 | // "application/vnd.android.package-archive" 357 | // ); 358 | } 359 | }).catch(error => { 360 | 361 | globalContextDispatch({ 362 | type: 'SET_NOW_DOWNLOAD_JOBID', 363 | payload: -1 364 | }); 365 | 366 | // === react-native-fs 367 | // if (error.message.includes('Download has been aborted')) { 368 | // === rn-fetch-blob 369 | if (error.message.includes('canceled')) { 370 | ToastAndroid.show('다운로드를 취소했습니다.', ToastAndroid.SHORT); 371 | } else { 372 | ToastAndroid.show('다운로드에 실패했습니다. 잠시 후 다시 시도하세요.', ToastAndroid.SHORT); 373 | } 374 | 375 | setActionButtonText(latestActionButtonText); 376 | setNowDownloadJobId(-1); 377 | setProgressBar(0); 378 | 379 | console.log('_____ERROR ', error.message, error.code); 380 | }); 381 | 382 | } catch (e) { 383 | console.log('error : ', e); 384 | if (globalContextState.nowDownloadJobId != -1) { 385 | RNFS.stopDownload(globalContextState.nowDownloadJobId); 386 | console.log('stopDownload ', globalContextState.nowDownloadJobId); 387 | 388 | globalContextDispatch({ 389 | type: 'SET_NOW_DOWNLOAD_JOBID', 390 | payload: -1 391 | }); 392 | } 393 | setNowDownloadJobId(-1); 394 | setProgressBar(0); 395 | 396 | } 397 | } 398 | 399 | 400 | 401 | 402 | return ( 403 | { 408 | if (globalContextState.nowDownloadJobId !== -1) { 409 | ToastAndroid.show('앱 다운로드가 진행 중입니다. 다운로드가 끝난 후 시도하세요.', ToastAndroid.SHORT); 410 | } else { 411 | appVSContextDispatch({ 412 | type: 'SET_MODAL_VISIBLE', 413 | payload: false, 414 | }); 415 | } 416 | 417 | }} 418 | > 419 | 420 | 421 | {/* Modal Header */} 422 | 423 | 424 | {/* Icon */} 425 | {/* */} 431 | 432 | 433 | {/* 패치 마크 */} 434 | { 435 | appVSContextState?.isPatched && ( 436 | 437 | 442 | 443 | ) 444 | } 445 | 446 | 447 | {/* Text */} 448 | 449 | {appVSContextState.label} 450 | 버전 선택 451 | 452 | 453 | 454 | {/* X 버튼 */} 455 | [ 457 | { 458 | width: 55, 459 | // height: 50, 460 | justifyContent: "center", 461 | alignItems: "center", 462 | // backgroundColor: '#a0a0a0', 463 | } 464 | ]} 465 | onPress={() => { 466 | if (globalContextState.nowDownloadJobId !== -1) { 467 | ToastAndroid.show('앱 다운로드가 진행 중입니다. 다운로드가 끝난 후 시도하세요.', ToastAndroid.SHORT); 468 | } else { 469 | appVSContextDispatch({ 470 | type: 'SET_MODAL_VISIBLE', 471 | payload: false, 472 | }); 473 | } 474 | }} 475 | > 476 | 477 | 478 | 479 | 480 | 481 | {/* Modal Body */} 482 | 488 | 489 | {/* 최신 버전 */} 490 | 491 | 최신 버전 492 | 493 | 494 | 495 | {`[버전 : ${appVSContextState.latestVersion} / 업데이트 날짜 : ${appVSContextState.latestDate}]`} 496 | 497 | {/* 필요 안드로이드 버전 */} 498 | 499 | 500 | Android {androidAPItoVersion(appVSContextState.minimumAndroidSdk)} 501 | 502 | 503 | 504 | 505 | {appLatestUpdateLog} 506 | 507 | 508 | 509 | 510 | {/* 실행/업데이트/다운로드 버튼 */} 511 | 512 | [ 514 | { 515 | backgroundColor: actionButtonText == '설치불가' 516 | ? '#404040' 517 | : pressed 518 | ? '#a0a0a0' 519 | : 'white' 520 | }, 521 | styles.appButton 522 | ]} 523 | onPress={() => { 524 | if (actionButtonText == '설치불가') { 525 | // const version = NativeModules.InstalledApps.getAndroidRelease(); 526 | ToastAndroid.show(`해당 기기의 안드로이드 버전과 호환되지 않습니다.\n현재 Android 버전 : ${DeviceInfo.getSystemVersion()}\n필요 Android 버전 : ${androidAPItoVersion(appVSContextState.minimumAndroidSdk)}`, ToastAndroid.LONG); 527 | } else { 528 | onPressAppButton(appVSContextState.package, appVSContextState.latestVersion, appVSContextState.latestApkUrl, [actionButtonText, setActionButtonText], [progressBar, setProgressBar], [nowDownloadJobId, setNowDownloadJobId]); 529 | } 530 | }} 531 | > 532 | {actionButtonText} 539 | { 540 | nowDownloadJobId !== -1 && ( 541 | 542 | 549 | 550 | ) 551 | } 552 | 553 | 554 | 555 | {/* 앱 용량 표시 */} 556 | { 557 | appVSContextState?.apk_size !== undefined && ( 558 | {formatBytes(appVSContextState.apk_size, 1)} 559 | ) 560 | } 561 | 562 | 563 | 564 | 565 | 566 | 567 | 568 | 569 | 570 | {/* 이전 버전 */} 571 | 572 | 573 | 이전 버전 574 | 579 | 580 | 581 | 582 | 583 | 584 | 585 | 586 | 587 | {/* 하단 페이지 이동 바 */} 588 | 589 | 590 | 591 | ); 592 | } 593 | 594 | 595 | const styles = StyleSheet.create({ 596 | centeredView: { 597 | flex: 1, 598 | justifyContent: "center", 599 | alignItems: "center", 600 | // marginBottom: 22 601 | }, 602 | modalView: { 603 | margin: 20, 604 | width: '90%', 605 | height: '95%', 606 | backgroundColor: "white", 607 | borderRadius: 10, 608 | // padding: 35, 609 | alignItems: "center", 610 | borderWidth: 2, 611 | borderColor: '#000000', 612 | // 그림자 613 | // shadowColor: "#000", 614 | // shadowOffset: { 615 | // width: 0, 616 | // height: 2 617 | // }, 618 | // shadowOpacity: 0.25, 619 | // shadowRadius: 4, 620 | // elevation: 5 621 | }, 622 | modalHeader: { 623 | flexDirection: 'row', 624 | justifyContent: 'space-between', 625 | // paddingBottom: 10, 626 | borderBottomColor: '#000000', 627 | borderBottomWidth: 2, 628 | 629 | }, 630 | modalHeaderInner: { 631 | flexDirection: 'row', 632 | flex: 1, 633 | height: 50, 634 | // backgroundColor: '#707070', 635 | alignItems: 'center', 636 | paddingLeft: 10, 637 | // paddingTop: 10, 638 | marginTop: 5, 639 | marginBottom: 5, 640 | }, 641 | separator: { 642 | borderBottomWidth: 1.7, 643 | borderBottomColor: '#000000', 644 | width: '100%', 645 | // marginVertical: 10, 646 | }, 647 | appSize: { 648 | fontSize: 13, 649 | textAlign: 'center', 650 | lineHeight: 16, 651 | }, 652 | appButton: { 653 | width: 70, 654 | height: 50, 655 | borderRadius: 5, 656 | borderWidth: 1, 657 | borderColor: '#000000', 658 | justifyContent: 'center', 659 | alignItems: 'center', 660 | }, 661 | }); -------------------------------------------------------------------------------- /src/Component/BottomToolbar.js: -------------------------------------------------------------------------------- 1 | import React, {useState, useEffect} from 'react'; 2 | import {StyleSheet, View, Pressable, Button, Dimensions, Alert} from 'react-native'; 3 | import MaterialIcons from 'react-native-vector-icons/MaterialIcons'; 4 | 5 | export default function BottomToolbar ({mainScrollViewRef ,scrollCurrentY, scrollDimensionHeight}) { 6 | 7 | const windowWidth = Dimensions.get("window").width; 8 | 9 | return ( 10 | 11 | {/*