Skip to content

Commit

Permalink
Feat: Onboarding screen & FileManager (LNReader#1093)
Browse files Browse the repository at this point in the history
* base onboard screen

* pick theme step

* cleanup

* refactor: TextFile -> FileManager

* pick storage folder

* default root storage

* refactor: fixed storage -> custom storage

* replace rnfs by native module FileManager

* clean up

* refactor: corresponding theme list
  • Loading branch information
nyagami authored Jun 3, 2024
1 parent aec6c34 commit 4f23e18
Show file tree
Hide file tree
Showing 30 changed files with 897 additions and 302 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -20,13 +20,13 @@

import java.io.File;
import java.io.FileInputStream;
import java.io.FileNotFoundException;
import java.io.IOException;
import java.util.HashMap;
import java.util.Objects;

public class EpubUtil extends ReactContextBaseJavaModule {
EpubUtil(ReactApplicationContext context){super(context);}
EpubUtil(ReactApplicationContext context) {
super(context);
}

@NonNull
@Override
Expand All @@ -51,8 +51,8 @@ private String readText(XmlPullParser parser) throws IOException, XmlPullParserE
return result;
}

private String cleanUrl(String url){
if(url != null){
private String cleanUrl(String url) {
if (url != null) {
return url.replaceFirst("#[^.]+?$", "");
}
return null;
Expand All @@ -74,42 +74,43 @@ public void parseNovelAndChapters(String epubDirPath, Promise promise) {

private String getContentMetaFilePath(File file) throws XmlPullParserException, IOException {
XmlPullParser parser = initParse(file);
while (parser.next() != XmlPullParser.END_TAG){
while (parser.next() != XmlPullParser.END_TAG) {
@Nullable String tag = parser.getName();
if(tag != null && tag.equals("rootfile")) {
if (tag != null && tag.equals("rootfile")) {
return parser.getAttributeValue(null, "full-path");
}
}
return "OEBPS/content.opf"; // default
}

private ReadableMap getNovelMetadata(File file, String contentDir) throws XmlPullParserException, IOException {
WritableMap novel = new WritableNativeMap();
WritableArray chapters = new WritableNativeArray();
XmlPullParser parser = initParse(file);
HashMap<String, String> refMap = new HashMap<>();
HashMap<String, String> tocMap = new HashMap<>();
File tocFile = new File(contentDir, "toc.ncx");
if(tocFile.exists()){
if (tocFile.exists()) {
XmlPullParser tocParser = initParse(tocFile);
String label = null;
while (tocParser.next() != XmlPullParser.END_DOCUMENT){
while (tocParser.next() != XmlPullParser.END_DOCUMENT) {
String tag = tocParser.getName();
if(tag != null){
if(tag.equals("text")){
if (tag != null) {
if (tag.equals("text")) {
label = readText(tocParser);
}else if(tag.equals("content")){
} else if (tag.equals("content")) {
String href = cleanUrl(tocParser.getAttributeValue(null, "src"));
if(href != null){
if (href != null) {
tocMap.put(href, label);
}
}
}
}
}
String cover = null;
while (parser.next() != XmlPullParser.END_DOCUMENT){
while (parser.next() != XmlPullParser.END_DOCUMENT) {
@Nullable String tag = parser.getName();
if(tag != null){
if (tag != null) {
switch (tag) {
case "item": {
String id = parser.getAttributeValue(null, "id");
Expand Down Expand Up @@ -145,15 +146,15 @@ private ReadableMap getNovelMetadata(File file, String contentDir) throws XmlPul
break;
case "meta":
String metaName = parser.getAttributeValue(null, "name");
if(metaName != null && metaName.equals("cover")){
if (metaName != null && metaName.equals("cover")) {
cover = parser.getAttributeValue(null, "content");
}
break;
}
parser.next();
}
}
if(cover != null){
if (cover != null) {
String coverPath = contentDir + "/" + refMap.get(cover);
novel.putString("cover", coverPath);
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,6 @@
import com.facebook.react.bridge.NativeModule;
import com.facebook.react.bridge.ReactApplicationContext;
import com.facebook.react.uimanager.ViewManager;
import com.rajarsheechatterjee.ZipArchive.ZipArchive;

import java.util.ArrayList;
import java.util.Collections;
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,277 @@
package com.rajarsheechatterjee.FileManager;

import android.annotation.SuppressLint;
import android.net.Uri;
import android.os.AsyncTask;
import android.os.Environment;
import android.provider.DocumentsContract;
import android.util.Base64;

import androidx.annotation.NonNull;

import com.facebook.react.bridge.Promise;
import com.facebook.react.bridge.ReactApplicationContext;
import com.facebook.react.bridge.ReactContextBaseJavaModule;
import com.facebook.react.bridge.ReactMethod;
import com.facebook.react.bridge.WritableArray;
import com.facebook.react.bridge.WritableMap;
import com.facebook.react.bridge.WritableNativeArray;
import com.facebook.react.bridge.WritableNativeMap;

import java.io.BufferedReader;
import java.io.File;
import java.io.FileReader;
import java.io.FileWriter;
import java.io.InputStream;
import java.io.OutputStream;
import java.util.HashMap;
import java.util.Map;

public class FileManager extends ReactContextBaseJavaModule {
FileManager(ReactApplicationContext context) {
super(context);
}

@NonNull
@Override
public String getName() {
return "FileManager";
}

private Uri getFileUri(String filepath) throws Exception {
Uri uri = Uri.parse(filepath);
if (uri.getScheme() == null) {
// No prefix, assuming that provided path is absolute path to file
File file = new File(filepath);
if(file.isDirectory()){
throw new Exception("Invalid file, folder found!");
}
uri = Uri.parse("file://" + filepath);
}
return uri;
}

private InputStream getInputStream(String filepath) throws Exception {
Uri uri = getFileUri(filepath);
InputStream stream;
stream = getReactApplicationContext().getContentResolver().openInputStream(uri);
if (stream == null) {
throw new Exception("ENOENT: could not open an input stream for '" + filepath + "'");
}
return stream;
}

private String getWriteAccessByAPILevel() {
return android.os.Build.VERSION.SDK_INT <= android.os.Build.VERSION_CODES.P ? "w" : "rwt";
}

private OutputStream getOutputStream(String filepath) throws Exception {
Uri uri = getFileUri(filepath);
OutputStream stream;
stream = getReactApplicationContext().getContentResolver().openOutputStream(uri, getWriteAccessByAPILevel());
if (stream == null) {
throw new Exception("ENOENT: could not open an output stream for '" + filepath + "'");
}
return stream;
}

@ReactMethod
public void writeFile(String path, String content, String encoding, Promise promise) {
try {
if(encoding == null || encoding.equals("utf8")){
FileWriter fw = new FileWriter(path);
fw.write(content);
fw.close();
}else{
byte[] bytes = Base64.decode(content, Base64.DEFAULT);
OutputStream os = getOutputStream(path);
os.write(bytes);
os.close();
}
promise.resolve(null);
} catch (Exception e) {
promise.reject(e);
}
}

@ReactMethod
public void readFile(String path, Promise promise) {
try {
StringBuilder sb = new StringBuilder();
BufferedReader br = new BufferedReader(new FileReader(path));
String line;
while ((line = br.readLine()) != null) sb.append(line).append('\n');
promise.resolve(sb.toString());
} catch (Exception e) {
promise.reject(e);
}
}

@SuppressLint("StaticFieldLeak")
@ReactMethod
public void copyFile(final String filepath, final String destPath, final Promise promise) {
new CopyFileTask() {
@Override
protected void onPostExecute (Exception e) {
if (e == null) {
promise.resolve(null);
} else {
promise.reject(e);
}
}
}.execute(filepath, destPath);
}

@SuppressLint("StaticFieldLeak")
@ReactMethod
public void moveFile(final String filepath, String destPath, final Promise promise) {
try {
final File inFile = new File(filepath);
if (!inFile.renameTo(new File(destPath))) {
new CopyFileTask() {
@Override
protected void onPostExecute (Exception e) {
if (e == null) {
inFile.delete();
promise.resolve(true);
} else {
promise.reject(e);
}
}
}.execute(filepath, destPath);
} else {
promise.resolve(true);
}
} catch (Exception e) {
promise.reject(e);
}
}

private class CopyFileTask extends AsyncTask<String, Void, Exception> {
protected Exception doInBackground(String... paths) {
try {
String filepath = paths[0];
String destPath = paths[1];

InputStream in = getInputStream(filepath);
OutputStream out = getOutputStream(destPath);

byte[] buffer = new byte[1024];
int length;
while ((length = in.read(buffer)) > 0) {
out.write(buffer, 0, length);
Thread.yield();
}
in.close();
out.close();
return null;
} catch (Exception ex) {
return ex;
}
}
}

@ReactMethod
public void resolveExternalContentUri(String uriString, Promise promise){
Uri uri = Uri.parse(uriString);
try{
final String docId = DocumentsContract.getTreeDocumentId(uri);
final String[] split = docId.split(":");
if("primary".equals(split[0])){
promise.resolve(Environment.getExternalStorageDirectory() + "/" + split[1]);
}else{
promise.resolve(null);
}
}catch (Exception e){
promise.resolve(null);
}
}

@ReactMethod
public void exists(String filepath, Promise promise) {
try {
File file = new File(filepath);
promise.resolve(file.exists());
} catch (Exception e) {
promise.reject(e);
}
}

@ReactMethod
public void mkdir(String filepath, Promise promise) {
try {
File file = new File(filepath);
boolean created = file.mkdirs();
if (!created) throw new Exception("Directory could not be created");
promise.resolve(null);
} catch (Exception e) {
promise.reject(e);
}
}

private void deleteRecursive(File fileOrDirectory) {
if (fileOrDirectory.isDirectory()) {
for (File child : fileOrDirectory.listFiles()) {
deleteRecursive(child);
}
}
fileOrDirectory.delete();
}
@ReactMethod
public void unlink(String filepath, Promise promise) {
try {
File file = new File(filepath);

if (!file.exists()) throw new Exception("File does not exist");

deleteRecursive(file);

promise.resolve(null);
} catch (Exception e) {
promise.reject(e);
}
}

@ReactMethod
public void readDir(String directory, Promise promise){
try {
File file = new File(directory);
if (!file.exists()) throw new Exception("Folder does not exist");
File[] files = file.listFiles();
WritableArray fileMaps = new WritableNativeArray();

for (File childFile : files) {
WritableMap fileMap = new WritableNativeMap();

fileMap.putString("name", childFile.getName());
fileMap.putString("path", childFile.getAbsolutePath());
fileMap.putBoolean("isDirectory", childFile.isDirectory());
fileMaps.pushMap(fileMap);
}
promise.resolve(fileMaps);
} catch (Exception e) {
promise.reject(e);
}
}

@Override
public Map<String, Object> getConstants() {
final Map<String, Object> constants = new HashMap<>();

File externalDirectory = this.getReactApplicationContext().getExternalFilesDir(null);
if (externalDirectory != null) {
constants.put("ExternalDirectoryPath", externalDirectory.getAbsolutePath());
} else {
constants.put("ExternalDirectoryPath", null);
}

File externalCachesDirectory = this.getReactApplicationContext().getExternalCacheDir();
if (externalCachesDirectory != null) {
constants.put("ExternalCachesDirectoryPath", externalCachesDirectory.getAbsolutePath());
} else {
constants.put("ExternalCachesDirectoryPath", null);
}

return constants;
}
}
Loading

0 comments on commit 4f23e18

Please sign in to comment.