Compare commits
51 Commits
94c947305b
...
fee1b1516f
Author | SHA1 | Date |
---|---|---|
Daniel Micay | fee1b1516f | |
dependabot[bot] | bc77ecbcc6 | |
Daniel Micay | 955bed3614 | |
Daniel Micay | ee3605428e | |
Daniel Micay | e967af2397 | |
Daniel Micay | 9ea5cb1802 | |
Daniel Micay | a307f2892c | |
Pratyush | 8c795d4552 | |
Daniel Micay | d7ca71770f | |
Daniel Micay | a29ec202a7 | |
Pratyush | ea9a57fabe | |
Pratyush | 68fc48d3a9 | |
quh4gko8 | 3ebdfd0a11 | |
quh4gko8 | ff11df007f | |
Daniel Micay | fa2db54804 | |
Patryk Mis | 7634c00f59 | |
Daniel Micay | f67021e975 | |
dependabot[bot] | adb87cbaef | |
Daniel Micay | 09ccfb069d | |
Daniel Micay | 2a91d95eef | |
dependabot[bot] | a48213c450 | |
dependabot[bot] | 2f4c1ebfed | |
Daniel Micay | 22b400c68f | |
Daniel Micay | 6a6a263b0b | |
Daniel Micay | 8cd685c8c7 | |
Daniel Micay | be3f43e7a4 | |
Daniel Micay | 5eb98841d6 | |
Daniel Micay | e297fdf7e4 | |
Daniel Micay | 9355f54956 | |
dependabot[bot] | 5dfdaa2c17 | |
June | 3411fb0426 | |
dependabot[bot] | af567a5c0c | |
Daniel Micay | 6a7982ce80 | |
Daniel Micay | 1a3e816cfc | |
Daniel Micay | c6113df31d | |
Daniel Micay | db98371c9d | |
Pratyush | 87c71ddac2 | |
Daniel Micay | 7dab1cdc91 | |
Daniel Micay | 61d204d188 | |
Daniel Micay | 96651d02af | |
Daniel Micay | 21d7b9d76e | |
Pratyush | eb6eb8046c | |
dependabot[bot] | c1a1263bd5 | |
smdyv | d71f804b5a | |
Pratyush | ba9a6c8206 | |
Daniel Micay | c0eea02bb1 | |
Patryk Mis | eb6c14740f | |
dependabot[bot] | 6847883605 | |
dependabot[bot] | 975dfb966c | |
dependabot[bot] | ec965ddaaa | |
June | 087a01cf72 |
|
@ -10,11 +10,11 @@ jobs:
|
||||||
- uses: actions/checkout@v3
|
- uses: actions/checkout@v3
|
||||||
with:
|
with:
|
||||||
submodules: true
|
submodules: true
|
||||||
- name: Set up JDK 17
|
- name: Set up JDK 18
|
||||||
uses: actions/setup-java@v2
|
uses: actions/setup-java@v3
|
||||||
with:
|
with:
|
||||||
distribution: 'temurin'
|
distribution: 'temurin'
|
||||||
java-version: 17
|
java-version: 18
|
||||||
cache: gradle
|
cache: gradle
|
||||||
- name: Build with Gradle
|
- name: Build with Gradle
|
||||||
run: ./gradlew build --no-daemon
|
run: ./gradlew build --no-daemon
|
||||||
|
|
|
@ -32,14 +32,16 @@ android {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
compileSdk = 32
|
compileSdk = 33
|
||||||
buildToolsVersion = "32.0.0"
|
buildToolsVersion = "33.0.0"
|
||||||
|
|
||||||
|
namespace = "app.grapheneos.pdfviewer"
|
||||||
|
|
||||||
defaultConfig {
|
defaultConfig {
|
||||||
applicationId = "app.grapheneos.pdfviewer"
|
applicationId = "app.grapheneos.pdfviewer"
|
||||||
minSdk = 26
|
minSdk = 26
|
||||||
targetSdk = 32
|
targetSdk = 33
|
||||||
versionCode = 13
|
versionCode = 15
|
||||||
versionName = versionCode.toString()
|
versionName = versionCode.toString()
|
||||||
resourceConfigurations.add("en")
|
resourceConfigurations.add("en")
|
||||||
}
|
}
|
||||||
|
@ -82,6 +84,6 @@ android {
|
||||||
}
|
}
|
||||||
|
|
||||||
dependencies {
|
dependencies {
|
||||||
implementation("androidx.appcompat:appcompat:1.4.1")
|
implementation("androidx.appcompat:appcompat:1.5.1")
|
||||||
implementation("com.google.android.material:material:1.5.0")
|
implementation("com.google.android.material:material:1.6.1")
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,7 +1,8 @@
|
||||||
<lint>
|
<lint>
|
||||||
<!-- full backups are desired -->
|
<!-- overly aggressive check -->
|
||||||
<issue id="AllowBackup">
|
<issue id="VectorPath">
|
||||||
<ignore path="src/main/AndroidManifest.xml"/>
|
<ignore path="src/main/res/drawable/ic_rotate_left_24dp.xml"/>
|
||||||
|
<ignore path="src/main/res/drawable/ic_rotate_right_24dp.xml"/>
|
||||||
</issue>
|
</issue>
|
||||||
|
|
||||||
<!-- Google app indexing doesn't make any sense for this app -->
|
<!-- Google app indexing doesn't make any sense for this app -->
|
||||||
|
|
|
@ -1,11 +1,10 @@
|
||||||
<?xml version="1.0" encoding="utf-8"?>
|
<?xml version="1.0" encoding="utf-8"?>
|
||||||
<manifest xmlns:android="http://schemas.android.com/apk/res/android"
|
<manifest xmlns:android="http://schemas.android.com/apk/res/android"
|
||||||
package="app.grapheneos.pdfviewer"
|
|
||||||
android:targetSandboxVersion="2">
|
android:targetSandboxVersion="2">
|
||||||
<original-package android:name="org.grapheneos.pdfviewer" />
|
<original-package android:name="org.grapheneos.pdfviewer" />
|
||||||
|
|
||||||
<application android:icon="@mipmap/ic_launcher"
|
<application android:icon="@mipmap/ic_launcher"
|
||||||
android:roundIcon="@mipmap/ic_launcher_round"
|
android:roundIcon="@mipmap/ic_launcher"
|
||||||
android:label="@string/app_name"
|
android:label="@string/app_name"
|
||||||
android:theme="@style/AppTheme"
|
android:theme="@style/AppTheme"
|
||||||
android:allowBackup="true">
|
android:allowBackup="true">
|
||||||
|
|
|
@ -198,15 +198,28 @@ function isTextSelected() {
|
||||||
return window.getSelection().toString() !== "";
|
return window.getSelection().toString() !== "";
|
||||||
}
|
}
|
||||||
|
|
||||||
pdfjsLib.getDocument("https://localhost/placeholder.pdf").promise.then(function(newDoc) {
|
function loadDocument() {
|
||||||
|
const pdfPassword = channel.getPassword();
|
||||||
|
const loadingTask = pdfjsLib.getDocument({ url: "https://localhost/placeholder.pdf", password: pdfPassword });
|
||||||
|
loadingTask.onPassword = (_, error) => {
|
||||||
|
if (error === pdfjsLib.PasswordResponses.NEED_PASSWORD) {
|
||||||
|
channel.showPasswordPrompt();
|
||||||
|
} else if (error === pdfjsLib.PasswordResponses.INCORRECT_PASSWORD) {
|
||||||
|
channel.invalidPassword();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
loadingTask.promise.then(function (newDoc) {
|
||||||
|
channel.onLoaded();
|
||||||
pdfDoc = newDoc;
|
pdfDoc = newDoc;
|
||||||
channel.setNumPages(pdfDoc.numPages);
|
channel.setNumPages(pdfDoc.numPages);
|
||||||
pdfDoc.getMetadata().then(function(data) {
|
pdfDoc.getMetadata().then(function (data) {
|
||||||
channel.setDocumentProperties(JSON.stringify(data.info));
|
channel.setDocumentProperties(JSON.stringify(data.info));
|
||||||
}).catch(function(error) {
|
}).catch(function (error) {
|
||||||
console.log("getMetadata error: " + error);
|
console.log("getMetadata error: " + error);
|
||||||
});
|
});
|
||||||
renderPage(channel.getPage(), false, false);
|
renderPage(channel.getPage(), false, false);
|
||||||
}).catch(function(error) {
|
}, function (reason) {
|
||||||
console.log("getDocument error: " + error);
|
console.error(reason.name + ": " + reason.message);
|
||||||
});
|
});
|
||||||
|
}
|
||||||
|
|
Binary file not shown.
Before Width: | Height: | Size: 15 KiB After Width: | Height: | Size: 6.7 KiB |
|
@ -0,0 +1,25 @@
|
||||||
|
package app.grapheneos.pdfviewer
|
||||||
|
|
||||||
|
import android.content.Context
|
||||||
|
import android.net.Uri
|
||||||
|
import java.io.FileNotFoundException
|
||||||
|
import java.io.IOException
|
||||||
|
import java.io.InputStream
|
||||||
|
import java.io.OutputStream
|
||||||
|
|
||||||
|
@Throws(FileNotFoundException::class, IOException::class)
|
||||||
|
fun saveAs(context: Context, existingUri: Uri, saveAs: Uri) {
|
||||||
|
|
||||||
|
context.asInputStream(existingUri)?.use { inputStream ->
|
||||||
|
context.asOutputStream(saveAs)?.use { outputStream ->
|
||||||
|
outputStream.write(inputStream.readBytes())
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
@Throws(FileNotFoundException::class)
|
||||||
|
private fun Context.asInputStream(uri: Uri): InputStream? = contentResolver.openInputStream(uri)
|
||||||
|
|
||||||
|
@Throws(FileNotFoundException::class)
|
||||||
|
private fun Context.asOutputStream(uri: Uri): OutputStream? = contentResolver.openOutputStream(uri)
|
|
@ -1,12 +1,12 @@
|
||||||
package app.grapheneos.pdfviewer;
|
package app.grapheneos.pdfviewer;
|
||||||
|
|
||||||
import android.annotation.SuppressLint;
|
import android.annotation.SuppressLint;
|
||||||
import android.app.Activity;
|
|
||||||
import android.content.Intent;
|
import android.content.Intent;
|
||||||
import android.content.pm.PackageInfo;
|
import android.content.pm.PackageInfo;
|
||||||
import android.content.res.ColorStateList;
|
import android.content.res.ColorStateList;
|
||||||
import android.graphics.Color;
|
import android.graphics.Color;
|
||||||
import android.net.Uri;
|
import android.net.Uri;
|
||||||
|
import android.os.Build;
|
||||||
import android.os.Bundle;
|
import android.os.Bundle;
|
||||||
import android.util.Log;
|
import android.util.Log;
|
||||||
import android.view.Gravity;
|
import android.view.Gravity;
|
||||||
|
@ -25,12 +25,16 @@ import android.webkit.WebViewClient;
|
||||||
import android.widget.TextView;
|
import android.widget.TextView;
|
||||||
import android.widget.Toast;
|
import android.widget.Toast;
|
||||||
|
|
||||||
|
import androidx.activity.result.ActivityResultLauncher;
|
||||||
|
import androidx.activity.result.contract.ActivityResultContracts;
|
||||||
import androidx.annotation.NonNull;
|
import androidx.annotation.NonNull;
|
||||||
import androidx.appcompat.app.AppCompatActivity;
|
import androidx.appcompat.app.AppCompatActivity;
|
||||||
import androidx.core.graphics.Insets;
|
import androidx.core.graphics.Insets;
|
||||||
import androidx.core.view.ViewCompat;
|
import androidx.core.view.ViewCompat;
|
||||||
import androidx.core.view.WindowCompat;
|
import androidx.core.view.WindowCompat;
|
||||||
import androidx.core.view.WindowInsetsCompat;
|
import androidx.core.view.WindowInsetsCompat;
|
||||||
|
import androidx.fragment.app.Fragment;
|
||||||
|
import androidx.lifecycle.ViewModelProvider;
|
||||||
import androidx.loader.app.LoaderManager;
|
import androidx.loader.app.LoaderManager;
|
||||||
import androidx.loader.content.Loader;
|
import androidx.loader.content.Loader;
|
||||||
|
|
||||||
|
@ -38,11 +42,14 @@ import com.google.android.material.snackbar.Snackbar;
|
||||||
|
|
||||||
import app.grapheneos.pdfviewer.databinding.PdfviewerBinding;
|
import app.grapheneos.pdfviewer.databinding.PdfviewerBinding;
|
||||||
import app.grapheneos.pdfviewer.fragment.DocumentPropertiesFragment;
|
import app.grapheneos.pdfviewer.fragment.DocumentPropertiesFragment;
|
||||||
|
import app.grapheneos.pdfviewer.fragment.PasswordPromptFragment;
|
||||||
import app.grapheneos.pdfviewer.fragment.JumpToPageFragment;
|
import app.grapheneos.pdfviewer.fragment.JumpToPageFragment;
|
||||||
import app.grapheneos.pdfviewer.loader.DocumentPropertiesLoader;
|
import app.grapheneos.pdfviewer.loader.DocumentPropertiesLoader;
|
||||||
|
import app.grapheneos.pdfviewer.viewModel.PasswordStatus;
|
||||||
|
|
||||||
import java.io.IOException;
|
import java.io.IOException;
|
||||||
import java.io.InputStream;
|
import java.io.InputStream;
|
||||||
|
import java.io.FileNotFoundException;
|
||||||
import java.util.HashMap;
|
import java.util.HashMap;
|
||||||
import java.util.List;
|
import java.util.List;
|
||||||
|
|
||||||
|
@ -53,6 +60,7 @@ public class PdfViewer extends AppCompatActivity implements LoaderManager.Loader
|
||||||
private static final String STATE_PAGE = "page";
|
private static final String STATE_PAGE = "page";
|
||||||
private static final String STATE_ZOOM_RATIO = "zoomRatio";
|
private static final String STATE_ZOOM_RATIO = "zoomRatio";
|
||||||
private static final String STATE_DOCUMENT_ORIENTATION_DEGREES = "documentOrientationDegrees";
|
private static final String STATE_DOCUMENT_ORIENTATION_DEGREES = "documentOrientationDegrees";
|
||||||
|
private static final String STATE_ENCRYPTED_DOCUMENT_PASSWORD = "encrypted_document_password";
|
||||||
private static final String KEY_PROPERTIES = "properties";
|
private static final String KEY_PROPERTIES = "properties";
|
||||||
private static final int MIN_WEBVIEW_RELEASE = 89;
|
private static final int MIN_WEBVIEW_RELEASE = 89;
|
||||||
|
|
||||||
|
@ -78,6 +86,7 @@ public class PdfViewer extends AppCompatActivity implements LoaderManager.Loader
|
||||||
"document-domain=(), " +
|
"document-domain=(), " +
|
||||||
"encrypted-media=(), " +
|
"encrypted-media=(), " +
|
||||||
"fullscreen=(), " +
|
"fullscreen=(), " +
|
||||||
|
"gamepad=(), " +
|
||||||
"geolocation=(), " +
|
"geolocation=(), " +
|
||||||
"gyroscope=(), " +
|
"gyroscope=(), " +
|
||||||
"hid=(), " +
|
"hid=(), " +
|
||||||
|
@ -91,6 +100,7 @@ public class PdfViewer extends AppCompatActivity implements LoaderManager.Loader
|
||||||
"publickey-credentials-get=(), " +
|
"publickey-credentials-get=(), " +
|
||||||
"screen-wake-lock=(), " +
|
"screen-wake-lock=(), " +
|
||||||
"serial=(), " +
|
"serial=(), " +
|
||||||
|
"speaker-selection=(), " +
|
||||||
"sync-xhr=(), " +
|
"sync-xhr=(), " +
|
||||||
"usb=(), " +
|
"usb=(), " +
|
||||||
"xr-spatial-tracking=()";
|
"xr-spatial-tracking=()";
|
||||||
|
@ -99,7 +109,6 @@ public class PdfViewer extends AppCompatActivity implements LoaderManager.Loader
|
||||||
private static final float MAX_ZOOM_RATIO = 1.5f;
|
private static final float MAX_ZOOM_RATIO = 1.5f;
|
||||||
private static final int ALPHA_LOW = 130;
|
private static final int ALPHA_LOW = 130;
|
||||||
private static final int ALPHA_HIGH = 255;
|
private static final int ALPHA_HIGH = 255;
|
||||||
private static final int ACTION_OPEN_DOCUMENT_REQUEST_CODE = 1;
|
|
||||||
private static final int STATE_LOADED = 1;
|
private static final int STATE_LOADED = 1;
|
||||||
private static final int STATE_END = 2;
|
private static final int STATE_END = 2;
|
||||||
private static final int PADDING = 10;
|
private static final int PADDING = 10;
|
||||||
|
@ -110,6 +119,7 @@ public class PdfViewer extends AppCompatActivity implements LoaderManager.Loader
|
||||||
private float mZoomRatio = 1f;
|
private float mZoomRatio = 1f;
|
||||||
private int mDocumentOrientationDegrees;
|
private int mDocumentOrientationDegrees;
|
||||||
private int mDocumentState;
|
private int mDocumentState;
|
||||||
|
private String mEncryptedDocumentPassword;
|
||||||
private List<CharSequence> mDocumentProperties;
|
private List<CharSequence> mDocumentProperties;
|
||||||
private InputStream mInputStream;
|
private InputStream mInputStream;
|
||||||
|
|
||||||
|
@ -117,6 +127,36 @@ public class PdfViewer extends AppCompatActivity implements LoaderManager.Loader
|
||||||
private TextView mTextView;
|
private TextView mTextView;
|
||||||
private Toast mToast;
|
private Toast mToast;
|
||||||
private Snackbar snackbar;
|
private Snackbar snackbar;
|
||||||
|
private PasswordPromptFragment mPasswordPromptFragment;
|
||||||
|
public PasswordStatus passwordValidationViewModel;
|
||||||
|
|
||||||
|
private final ActivityResultLauncher<Intent> openDocumentLauncher = registerForActivityResult(
|
||||||
|
new ActivityResultContracts.StartActivityForResult(), result -> {
|
||||||
|
if (result == null) return;
|
||||||
|
if (result.getResultCode() != RESULT_OK) return;
|
||||||
|
Intent resultData = result.getData();
|
||||||
|
if (resultData != null) {
|
||||||
|
mUri = result.getData().getData();
|
||||||
|
mPage = 1;
|
||||||
|
mDocumentProperties = null;
|
||||||
|
mEncryptedDocumentPassword = "";
|
||||||
|
loadPdf();
|
||||||
|
invalidateOptionsMenu();
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
private final ActivityResultLauncher<Intent> saveAsLauncher = registerForActivityResult(
|
||||||
|
new ActivityResultContracts.StartActivityForResult(), result -> {
|
||||||
|
if (result == null) return;
|
||||||
|
if (result.getResultCode() != RESULT_OK) return;
|
||||||
|
Intent resultData = result.getData();
|
||||||
|
if (resultData != null) {
|
||||||
|
Uri path = resultData.getData();
|
||||||
|
if (path != null) {
|
||||||
|
saveDocumentAs(path);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
private class Channel {
|
private class Channel {
|
||||||
@JavascriptInterface
|
@JavascriptInterface
|
||||||
|
@ -150,6 +190,32 @@ public class PdfViewer extends AppCompatActivity implements LoaderManager.Loader
|
||||||
args.putString(KEY_PROPERTIES, properties);
|
args.putString(KEY_PROPERTIES, properties);
|
||||||
runOnUiThread(() -> LoaderManager.getInstance(PdfViewer.this).restartLoader(DocumentPropertiesLoader.ID, args, PdfViewer.this));
|
runOnUiThread(() -> LoaderManager.getInstance(PdfViewer.this).restartLoader(DocumentPropertiesLoader.ID, args, PdfViewer.this));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@JavascriptInterface
|
||||||
|
public void showPasswordPrompt() {
|
||||||
|
if (!getPasswordPromptFragment().isAdded()){
|
||||||
|
getPasswordPromptFragment().show(getSupportFragmentManager(), PasswordPromptFragment.class.getName());
|
||||||
|
}
|
||||||
|
passwordValidationViewModel.passwordMissing();
|
||||||
|
}
|
||||||
|
|
||||||
|
@JavascriptInterface
|
||||||
|
public void invalidPassword() {
|
||||||
|
runOnUiThread(() -> passwordValidationViewModel.invalid());
|
||||||
|
}
|
||||||
|
|
||||||
|
@JavascriptInterface
|
||||||
|
public void onLoaded() {
|
||||||
|
passwordValidationViewModel.validated();
|
||||||
|
if (getPasswordPromptFragment().isAdded()) {
|
||||||
|
getPasswordPromptFragment().dismiss();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@JavascriptInterface
|
||||||
|
public String getPassword() {
|
||||||
|
return mEncryptedDocumentPassword != null ? mEncryptedDocumentPassword : "";
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
|
@ -159,6 +225,7 @@ public class PdfViewer extends AppCompatActivity implements LoaderManager.Loader
|
||||||
binding = PdfviewerBinding.inflate(getLayoutInflater());
|
binding = PdfviewerBinding.inflate(getLayoutInflater());
|
||||||
setContentView(binding.getRoot());
|
setContentView(binding.getRoot());
|
||||||
setSupportActionBar(binding.toolbar);
|
setSupportActionBar(binding.toolbar);
|
||||||
|
passwordValidationViewModel = new ViewModelProvider(this, ViewModelProvider.AndroidViewModelFactory.getInstance(getApplication())).get(PasswordStatus.class);
|
||||||
|
|
||||||
WindowCompat.setDecorFitsSystemWindows(getWindow(), false);
|
WindowCompat.setDecorFitsSystemWindows(getWindow(), false);
|
||||||
|
|
||||||
|
@ -214,6 +281,12 @@ public class PdfViewer extends AppCompatActivity implements LoaderManager.Loader
|
||||||
Log.d(TAG, "path " + path);
|
Log.d(TAG, "path " + path);
|
||||||
|
|
||||||
if ("/placeholder.pdf".equals(path)) {
|
if ("/placeholder.pdf".equals(path)) {
|
||||||
|
maybeCloseInputStream();
|
||||||
|
try {
|
||||||
|
mInputStream = getContentResolver().openInputStream(mUri);
|
||||||
|
} catch (FileNotFoundException ignored) {
|
||||||
|
snackbar.setText(R.string.error_while_opening).show();
|
||||||
|
}
|
||||||
return new WebResourceResponse("application/pdf", null, mInputStream);
|
return new WebResourceResponse("application/pdf", null, mInputStream);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -247,6 +320,7 @@ public class PdfViewer extends AppCompatActivity implements LoaderManager.Loader
|
||||||
public void onPageFinished(WebView view, String url) {
|
public void onPageFinished(WebView view, String url) {
|
||||||
mDocumentState = STATE_LOADED;
|
mDocumentState = STATE_LOADED;
|
||||||
invalidateOptionsMenu();
|
invalidateOptionsMenu();
|
||||||
|
loadPdfWithPassword(mEncryptedDocumentPassword);
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
|
@ -297,7 +371,7 @@ public class PdfViewer extends AppCompatActivity implements LoaderManager.Loader
|
||||||
// loader manager impl so that the result will be delivered.
|
// loader manager impl so that the result will be delivered.
|
||||||
LoaderManager.getInstance(this);
|
LoaderManager.getInstance(this);
|
||||||
|
|
||||||
snackbar = Snackbar.make(binding.webview, "", Snackbar.LENGTH_LONG);
|
snackbar = Snackbar.make(binding.getRoot(), "", Snackbar.LENGTH_LONG);
|
||||||
|
|
||||||
final Intent intent = getIntent();
|
final Intent intent = getIntent();
|
||||||
if (Intent.ACTION_VIEW.equals(intent.getAction())) {
|
if (Intent.ACTION_VIEW.equals(intent.getAction())) {
|
||||||
|
@ -310,10 +384,15 @@ public class PdfViewer extends AppCompatActivity implements LoaderManager.Loader
|
||||||
}
|
}
|
||||||
|
|
||||||
if (savedInstanceState != null) {
|
if (savedInstanceState != null) {
|
||||||
|
if (Build.VERSION.SDK_INT < Build.VERSION_CODES.TIRAMISU) {
|
||||||
mUri = savedInstanceState.getParcelable(STATE_URI);
|
mUri = savedInstanceState.getParcelable(STATE_URI);
|
||||||
|
} else {
|
||||||
|
mUri = savedInstanceState.getParcelable(STATE_URI, Uri.class);
|
||||||
|
}
|
||||||
mPage = savedInstanceState.getInt(STATE_PAGE);
|
mPage = savedInstanceState.getInt(STATE_PAGE);
|
||||||
mZoomRatio = savedInstanceState.getFloat(STATE_ZOOM_RATIO);
|
mZoomRatio = savedInstanceState.getFloat(STATE_ZOOM_RATIO);
|
||||||
mDocumentOrientationDegrees = savedInstanceState.getInt(STATE_DOCUMENT_ORIENTATION_DEGREES);
|
mDocumentOrientationDegrees = savedInstanceState.getInt(STATE_DOCUMENT_ORIENTATION_DEGREES);
|
||||||
|
mEncryptedDocumentPassword = savedInstanceState.getString(STATE_ENCRYPTED_DOCUMENT_PASSWORD);
|
||||||
}
|
}
|
||||||
|
|
||||||
if (mUri != null) {
|
if (mUri != null) {
|
||||||
|
@ -326,6 +405,38 @@ public class PdfViewer extends AppCompatActivity implements LoaderManager.Loader
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
protected void onDestroy() {
|
||||||
|
super.onDestroy();
|
||||||
|
binding.webview.removeJavascriptInterface("channel");
|
||||||
|
binding.getRoot().removeView(binding.webview);
|
||||||
|
binding.webview.destroy();
|
||||||
|
maybeCloseInputStream();
|
||||||
|
}
|
||||||
|
|
||||||
|
void maybeCloseInputStream() {
|
||||||
|
InputStream stream = mInputStream;
|
||||||
|
if (stream == null) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
mInputStream = null;
|
||||||
|
try {
|
||||||
|
stream.close();
|
||||||
|
} catch (IOException ignored) {}
|
||||||
|
}
|
||||||
|
|
||||||
|
private PasswordPromptFragment getPasswordPromptFragment() {
|
||||||
|
if (mPasswordPromptFragment == null) {
|
||||||
|
final Fragment fragment = getSupportFragmentManager().findFragmentByTag(PasswordPromptFragment.class.getName());
|
||||||
|
if (fragment != null) {
|
||||||
|
mPasswordPromptFragment = (PasswordPromptFragment) fragment;
|
||||||
|
} else {
|
||||||
|
mPasswordPromptFragment = new PasswordPromptFragment();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return mPasswordPromptFragment;
|
||||||
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
protected void onResume() {
|
protected void onResume() {
|
||||||
super.onResume();
|
super.onResume();
|
||||||
|
@ -372,14 +483,21 @@ public class PdfViewer extends AppCompatActivity implements LoaderManager.Loader
|
||||||
}
|
}
|
||||||
mInputStream = getContentResolver().openInputStream(mUri);
|
mInputStream = getContentResolver().openInputStream(mUri);
|
||||||
} catch (IOException e) {
|
} catch (IOException e) {
|
||||||
snackbar.setText(R.string.io_error).show();
|
snackbar.setText(R.string.error_while_opening).show();
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
mDocumentState = 0;
|
||||||
showSystemUi();
|
showSystemUi();
|
||||||
|
invalidateOptionsMenu();
|
||||||
binding.webview.loadUrl("https://localhost/viewer.html");
|
binding.webview.loadUrl("https://localhost/viewer.html");
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public void loadPdfWithPassword(final String password) {
|
||||||
|
mEncryptedDocumentPassword = password;
|
||||||
|
binding.webview.evaluateJavascript("loadDocument()", null);
|
||||||
|
}
|
||||||
|
|
||||||
private void renderPage(final int zoom) {
|
private void renderPage(final int zoom) {
|
||||||
binding.webview.evaluateJavascript("onRenderPage(" + zoom + ")", null);
|
binding.webview.evaluateJavascript("onRenderPage(" + zoom + ")", null);
|
||||||
}
|
}
|
||||||
|
@ -396,7 +514,7 @@ public class PdfViewer extends AppCompatActivity implements LoaderManager.Loader
|
||||||
Intent intent = new Intent(Intent.ACTION_OPEN_DOCUMENT);
|
Intent intent = new Intent(Intent.ACTION_OPEN_DOCUMENT);
|
||||||
intent.addCategory(Intent.CATEGORY_OPENABLE);
|
intent.addCategory(Intent.CATEGORY_OPENABLE);
|
||||||
intent.setType("application/pdf");
|
intent.setType("application/pdf");
|
||||||
startActivityForResult(intent, ACTION_OPEN_DOCUMENT_REQUEST_CODE);
|
openDocumentLauncher.launch(intent);
|
||||||
}
|
}
|
||||||
|
|
||||||
private void shareDocument() {
|
private void shareDocument() {
|
||||||
|
@ -476,21 +594,7 @@ public class PdfViewer extends AppCompatActivity implements LoaderManager.Loader
|
||||||
savedInstanceState.putInt(STATE_PAGE, mPage);
|
savedInstanceState.putInt(STATE_PAGE, mPage);
|
||||||
savedInstanceState.putFloat(STATE_ZOOM_RATIO, mZoomRatio);
|
savedInstanceState.putFloat(STATE_ZOOM_RATIO, mZoomRatio);
|
||||||
savedInstanceState.putInt(STATE_DOCUMENT_ORIENTATION_DEGREES, mDocumentOrientationDegrees);
|
savedInstanceState.putInt(STATE_DOCUMENT_ORIENTATION_DEGREES, mDocumentOrientationDegrees);
|
||||||
}
|
savedInstanceState.putString(STATE_ENCRYPTED_DOCUMENT_PASSWORD, mEncryptedDocumentPassword);
|
||||||
|
|
||||||
@Override
|
|
||||||
protected void onActivityResult(int requestCode, int resultCode, Intent resultData) {
|
|
||||||
super.onActivityResult(requestCode, resultCode, resultData);
|
|
||||||
|
|
||||||
if (requestCode == ACTION_OPEN_DOCUMENT_REQUEST_CODE && resultCode == Activity.RESULT_OK) {
|
|
||||||
if (resultData != null) {
|
|
||||||
mUri = resultData.getData();
|
|
||||||
mPage = 1;
|
|
||||||
mDocumentProperties = null;
|
|
||||||
loadPdf();
|
|
||||||
invalidateOptionsMenu();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
private void showPageNumber() {
|
private void showPageNumber() {
|
||||||
|
@ -515,10 +619,10 @@ public class PdfViewer extends AppCompatActivity implements LoaderManager.Loader
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public boolean onPrepareOptionsMenu(Menu menu) {
|
public boolean onPrepareOptionsMenu(Menu menu) {
|
||||||
final int[] ids = { R.id.action_zoom_in, R.id.action_zoom_out, R.id.action_jump_to_page,
|
final int[] ids = {R.id.action_jump_to_page, R.id.action_next, R.id.action_previous,
|
||||||
R.id.action_next, R.id.action_previous, R.id.action_first, R.id.action_last,
|
R.id.action_first, R.id.action_last, R.id.action_rotate_clockwise,
|
||||||
R.id.action_rotate_clockwise, R.id.action_rotate_counterclockwise,
|
R.id.action_rotate_counterclockwise, R.id.action_view_document_properties,
|
||||||
R.id.action_view_document_properties, R.id.action_share };
|
R.id.action_share, R.id.action_save_as};
|
||||||
if (mDocumentState < STATE_LOADED) {
|
if (mDocumentState < STATE_LOADED) {
|
||||||
for (final int id : ids) {
|
for (final int id : ids) {
|
||||||
final MenuItem item = menu.findItem(id);
|
final MenuItem item = menu.findItem(id);
|
||||||
|
@ -537,11 +641,10 @@ public class PdfViewer extends AppCompatActivity implements LoaderManager.Loader
|
||||||
}
|
}
|
||||||
|
|
||||||
enableDisableMenuItem(menu.findItem(R.id.action_open), getWebViewRelease() >= MIN_WEBVIEW_RELEASE);
|
enableDisableMenuItem(menu.findItem(R.id.action_open), getWebViewRelease() >= MIN_WEBVIEW_RELEASE);
|
||||||
enableDisableMenuItem(menu.findItem(R.id.action_zoom_in), mZoomRatio != MAX_ZOOM_RATIO);
|
|
||||||
enableDisableMenuItem(menu.findItem(R.id.action_zoom_out), mZoomRatio != MIN_ZOOM_RATIO);
|
|
||||||
enableDisableMenuItem(menu.findItem(R.id.action_share), mUri != null);
|
enableDisableMenuItem(menu.findItem(R.id.action_share), mUri != null);
|
||||||
enableDisableMenuItem(menu.findItem(R.id.action_next), mPage < mNumPages);
|
enableDisableMenuItem(menu.findItem(R.id.action_next), mPage < mNumPages);
|
||||||
enableDisableMenuItem(menu.findItem(R.id.action_previous), mPage > 1);
|
enableDisableMenuItem(menu.findItem(R.id.action_previous), mPage > 1);
|
||||||
|
enableDisableMenuItem(menu.findItem(R.id.action_save_as), mUri != null);
|
||||||
|
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
@ -564,12 +667,6 @@ public class PdfViewer extends AppCompatActivity implements LoaderManager.Loader
|
||||||
} else if (itemId == R.id.action_open) {
|
} else if (itemId == R.id.action_open) {
|
||||||
openDocument();
|
openDocument();
|
||||||
return true;
|
return true;
|
||||||
} else if (itemId == R.id.action_zoom_out) {
|
|
||||||
zoomOut(0.25f, true);
|
|
||||||
return true;
|
|
||||||
} else if (itemId == R.id.action_zoom_in) {
|
|
||||||
zoomIn(0.25f, true);
|
|
||||||
return true;
|
|
||||||
} else if (itemId == R.id.action_rotate_clockwise) {
|
} else if (itemId == R.id.action_rotate_clockwise) {
|
||||||
documentOrientationChanged(90);
|
documentOrientationChanged(90);
|
||||||
return true;
|
return true;
|
||||||
|
@ -588,8 +685,42 @@ public class PdfViewer extends AppCompatActivity implements LoaderManager.Loader
|
||||||
} else if (itemId == R.id.action_share) {
|
} else if (itemId == R.id.action_share) {
|
||||||
shareDocument();
|
shareDocument();
|
||||||
return true;
|
return true;
|
||||||
|
} else if (itemId == R.id.action_save_as) {
|
||||||
|
saveDocument();
|
||||||
}
|
}
|
||||||
|
|
||||||
return super.onOptionsItemSelected(item);
|
return super.onOptionsItemSelected(item);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private void saveDocument() {
|
||||||
|
Intent intent = new Intent(Intent.ACTION_CREATE_DOCUMENT);
|
||||||
|
intent.addCategory(Intent.CATEGORY_OPENABLE);
|
||||||
|
intent.setType("application/pdf");
|
||||||
|
intent.putExtra(Intent.EXTRA_TITLE, getCurrentDocumentName());
|
||||||
|
saveAsLauncher.launch(intent);
|
||||||
|
}
|
||||||
|
|
||||||
|
private String getCurrentDocumentName() {
|
||||||
|
if (mDocumentProperties == null || mDocumentProperties.isEmpty()) return "";
|
||||||
|
String fileName = "";
|
||||||
|
String title = "";
|
||||||
|
for (CharSequence property : mDocumentProperties) {
|
||||||
|
if (property.toString().startsWith("File name:")) {
|
||||||
|
fileName = property.toString().replace("File name:", "");
|
||||||
|
}
|
||||||
|
if (property.toString().startsWith("Title:-")) {
|
||||||
|
title = property.toString().replace("Title:-", "");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return fileName.length() > 2 ? fileName : title;
|
||||||
|
}
|
||||||
|
|
||||||
|
private void saveDocumentAs(Uri uri) {
|
||||||
|
try {
|
||||||
|
KtUtilsKt.saveAs(this, mUri, uri);
|
||||||
|
} catch (IOException e) {
|
||||||
|
e.printStackTrace();
|
||||||
|
snackbar.setText(R.string.error_while_saving).show();
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -39,14 +39,13 @@ public class Utils {
|
||||||
|
|
||||||
final Calendar calendar = Calendar.getInstance();
|
final Calendar calendar = Calendar.getInstance();
|
||||||
final int currentYear = calendar.get(Calendar.YEAR);
|
final int currentYear = calendar.get(Calendar.YEAR);
|
||||||
int year;
|
|
||||||
|
|
||||||
// Year is required
|
// Year is required
|
||||||
String field = date.substring(position += 2, 6);
|
String field = date.substring(position += 2, 6);
|
||||||
if (!TextUtils.isDigitsOnly(field)) {
|
if (!TextUtils.isDigitsOnly(field)) {
|
||||||
throw new ParseException("Invalid year", position);
|
throw new ParseException("Invalid year", position);
|
||||||
}
|
}
|
||||||
year = Integer.parseInt(field);
|
int year = Integer.parseInt(field);
|
||||||
if (year > currentYear) {
|
if (year > currentYear) {
|
||||||
year = currentYear;
|
year = currentYear;
|
||||||
}
|
}
|
||||||
|
|
|
@ -0,0 +1,102 @@
|
||||||
|
package app.grapheneos.pdfviewer.fragment
|
||||||
|
|
||||||
|
import android.app.Dialog
|
||||||
|
import android.content.DialogInterface
|
||||||
|
import android.os.Bundle
|
||||||
|
import android.text.Editable
|
||||||
|
import android.text.TextUtils
|
||||||
|
import android.text.TextWatcher
|
||||||
|
import android.view.LayoutInflater
|
||||||
|
import android.view.WindowManager
|
||||||
|
import android.view.inputmethod.EditorInfo.IME_ACTION_DONE
|
||||||
|
import androidx.appcompat.app.AlertDialog
|
||||||
|
import androidx.fragment.app.DialogFragment
|
||||||
|
import app.grapheneos.pdfviewer.PdfViewer
|
||||||
|
import app.grapheneos.pdfviewer.R
|
||||||
|
import app.grapheneos.pdfviewer.databinding.PasswordDialogFragmentBinding
|
||||||
|
import app.grapheneos.pdfviewer.viewModel.PasswordStatus
|
||||||
|
import com.google.android.material.dialog.MaterialAlertDialogBuilder
|
||||||
|
import com.google.android.material.textfield.TextInputEditText
|
||||||
|
import com.google.android.material.textfield.TextInputLayout
|
||||||
|
|
||||||
|
class PasswordPromptFragment : DialogFragment() {
|
||||||
|
|
||||||
|
private lateinit var passwordLayout : TextInputLayout
|
||||||
|
private lateinit var passwordEditText : TextInputEditText
|
||||||
|
|
||||||
|
override fun onCreateDialog(savedInstanceState: Bundle?): Dialog {
|
||||||
|
val passwordPrompt = MaterialAlertDialogBuilder(requireContext())
|
||||||
|
val passwordDialogFragmentBinding =
|
||||||
|
PasswordDialogFragmentBinding.inflate(LayoutInflater.from(requireContext()))
|
||||||
|
passwordLayout = passwordDialogFragmentBinding.pdfPasswordTextInputLayout
|
||||||
|
passwordEditText = passwordDialogFragmentBinding.pdfPasswordEditText
|
||||||
|
passwordPrompt.setView(passwordDialogFragmentBinding.root)
|
||||||
|
passwordEditText.addTextChangedListener(object : TextWatcher {
|
||||||
|
override fun beforeTextChanged(charSequence: CharSequence, i: Int, i1: Int, i2: Int) {}
|
||||||
|
override fun onTextChanged(input: CharSequence, i: Int, i1: Int, i2: Int) {
|
||||||
|
updatePositiveButton()
|
||||||
|
}
|
||||||
|
override fun afterTextChanged(editable: Editable) {}
|
||||||
|
})
|
||||||
|
passwordEditText.setOnEditorActionListener { _, actionId, _ ->
|
||||||
|
if (actionId != IME_ACTION_DONE) return@setOnEditorActionListener false
|
||||||
|
sendPassword()
|
||||||
|
true
|
||||||
|
}
|
||||||
|
passwordPrompt.setPositiveButton(R.string.open, null)
|
||||||
|
passwordPrompt.setNegativeButton(R.string.cancel, null)
|
||||||
|
val dialog = passwordPrompt.create()
|
||||||
|
passwordPrompt.setCancelable(false)
|
||||||
|
isCancelable = false
|
||||||
|
dialog.setCanceledOnTouchOutside(false)
|
||||||
|
dialog.window?.setSoftInputMode(WindowManager.LayoutParams.SOFT_INPUT_STATE_VISIBLE)
|
||||||
|
(requireActivity() as PdfViewer).passwordValidationViewModel.status.observe(
|
||||||
|
this
|
||||||
|
) {
|
||||||
|
when (it) {
|
||||||
|
PasswordStatus.Status.MissingPassword -> {
|
||||||
|
passwordEditText.editableText.clear()
|
||||||
|
passwordDialogFragmentBinding.title.setText(R.string.password_prompt_description)
|
||||||
|
}
|
||||||
|
PasswordStatus.Status.InvalidPassword -> {
|
||||||
|
passwordEditText.editableText.clear()
|
||||||
|
passwordDialogFragmentBinding.pdfPasswordTextInputLayout.error =
|
||||||
|
"invalid password"
|
||||||
|
}
|
||||||
|
PasswordStatus.Status.Validated -> {
|
||||||
|
//Activity will dismiss the dialog
|
||||||
|
}
|
||||||
|
else -> {
|
||||||
|
throw NullPointerException("status shouldn't be null")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return dialog
|
||||||
|
}
|
||||||
|
|
||||||
|
private fun updatePositiveButton() {
|
||||||
|
passwordLayout.error = ""
|
||||||
|
val btn = (dialog as AlertDialog).getButton(DialogInterface.BUTTON_POSITIVE)
|
||||||
|
btn.isEnabled = passwordEditText.text?.isNotEmpty() ?: false
|
||||||
|
}
|
||||||
|
|
||||||
|
private fun sendPassword() {
|
||||||
|
val password = passwordEditText.text.toString()
|
||||||
|
if (!TextUtils.isEmpty(password)) {
|
||||||
|
(activity as PdfViewer).loadPdfWithPassword(password)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun onStart() {
|
||||||
|
super.onStart()
|
||||||
|
updatePositiveButton()
|
||||||
|
passwordEditText.requestFocus()
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun onResume() {
|
||||||
|
super.onResume()
|
||||||
|
(dialog as AlertDialog).getButton(DialogInterface.BUTTON_POSITIVE).setOnClickListener {
|
||||||
|
sendPassword()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,27 @@
|
||||||
|
package app.grapheneos.pdfviewer.viewModel
|
||||||
|
|
||||||
|
import androidx.lifecycle.MutableLiveData
|
||||||
|
import androidx.lifecycle.ViewModel
|
||||||
|
|
||||||
|
class PasswordStatus : ViewModel() {
|
||||||
|
|
||||||
|
enum class Status {
|
||||||
|
MissingPassword,
|
||||||
|
InvalidPassword,
|
||||||
|
Validated
|
||||||
|
}
|
||||||
|
|
||||||
|
val status: MutableLiveData<Status> = MutableLiveData(Status.MissingPassword)
|
||||||
|
|
||||||
|
fun passwordMissing() {
|
||||||
|
status.postValue(Status.MissingPassword)
|
||||||
|
}
|
||||||
|
|
||||||
|
fun invalid() {
|
||||||
|
status.postValue(Status.InvalidPassword)
|
||||||
|
}
|
||||||
|
|
||||||
|
fun validated() {
|
||||||
|
status.postValue(Status.Validated)
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,10 @@
|
||||||
|
<vector xmlns:android="http://schemas.android.com/apk/res/android"
|
||||||
|
android:width="24dp"
|
||||||
|
android:height="24dp"
|
||||||
|
android:viewportWidth="24"
|
||||||
|
android:viewportHeight="24"
|
||||||
|
android:tint="?attr/colorControlNormal">
|
||||||
|
<path
|
||||||
|
android:fillColor="@android:color/white"
|
||||||
|
android:pathData="M17,3L5,3c-1.11,0 -2,0.9 -2,2v14c0,1.1 0.89,2 2,2h14c1.1,0 2,-0.9 2,-2L21,7l-4,-4zM19,19L5,19L5,5h11.17L19,7.83L19,19zM12,12c-1.66,0 -3,1.34 -3,3s1.34,3 3,3 3,-1.34 3,-3 -1.34,-3 -3,-3zM6,6h9v4L6,10z"/>
|
||||||
|
</vector>
|
|
@ -1,9 +0,0 @@
|
||||||
<vector xmlns:android="http://schemas.android.com/apk/res/android"
|
|
||||||
android:width="24dp"
|
|
||||||
android:height="24dp"
|
|
||||||
android:viewportWidth="24"
|
|
||||||
android:viewportHeight="24">
|
|
||||||
<path
|
|
||||||
android:fillColor="?attr/colorControlNormal"
|
|
||||||
android:pathData="M15.5,14h-0.79l-0.28,-0.27c1.2,-1.4 1.82,-3.31 1.48,-5.34 -0.47,-2.78 -2.79,-5 -5.59,-5.34 -4.23,-0.52 -7.78,3.04 -7.27,7.27 0.34,2.8 2.56,5.12 5.34,5.59 2.03,0.34 3.94,-0.28 5.34,-1.48l0.27,0.28v0.79l4.26,4.25c0.41,0.41 1.07,0.41 1.48,0l0.01,-0.01c0.41,-0.41 0.41,-1.07 0,-1.48L15.5,14zM9.5,14C7.01,14 5,11.99 5,9.5S7.01,5 9.5,5 14,7.01 14,9.5 11.99,14 9.5,14zM9.5,7c-0.28,0 -0.5,0.22 -0.5,0.5L9,9L7.5,9c-0.28,0 -0.5,0.22 -0.5,0.5s0.22,0.5 0.5,0.5L9,10v1.5c0,0.28 0.22,0.5 0.5,0.5s0.5,-0.22 0.5,-0.5L10,10h1.5c0.28,0 0.5,-0.22 0.5,-0.5s-0.22,-0.5 -0.5,-0.5L10,9L10,7.5c0,-0.28 -0.22,-0.5 -0.5,-0.5z" />
|
|
||||||
</vector>
|
|
|
@ -1,9 +0,0 @@
|
||||||
<vector xmlns:android="http://schemas.android.com/apk/res/android"
|
|
||||||
android:width="24dp"
|
|
||||||
android:height="24dp"
|
|
||||||
android:viewportWidth="24"
|
|
||||||
android:viewportHeight="24">
|
|
||||||
<path
|
|
||||||
android:fillColor="?attr/colorControlNormal"
|
|
||||||
android:pathData="M15.5,14h-0.79l-0.28,-0.27c1.2,-1.4 1.82,-3.31 1.48,-5.34 -0.47,-2.78 -2.79,-5 -5.59,-5.34 -4.23,-0.52 -7.79,3.04 -7.27,7.27 0.34,2.8 2.56,5.12 5.34,5.59 2.03,0.34 3.94,-0.28 5.34,-1.48l0.27,0.28v0.79l4.26,4.25c0.41,0.41 1.07,0.41 1.48,0l0.01,-0.01c0.41,-0.41 0.41,-1.07 0,-1.48L15.5,14zM9.5,14C7.01,14 5,11.99 5,9.5S7.01,5 9.5,5 14,7.01 14,9.5 11.99,14 9.5,14zM7.5,9h4c0.28,0 0.5,0.22 0.5,0.5s-0.22,0.5 -0.5,0.5h-4c-0.28,0 -0.5,-0.22 -0.5,-0.5s0.22,-0.5 0.5,-0.5z" />
|
|
||||||
</vector>
|
|
|
@ -0,0 +1,40 @@
|
||||||
|
<?xml version="1.0" encoding="utf-8"?>
|
||||||
|
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
|
||||||
|
xmlns:app="http://schemas.android.com/apk/res-auto"
|
||||||
|
android:layout_width="wrap_content"
|
||||||
|
android:layout_height="wrap_content"
|
||||||
|
android:orientation="vertical">
|
||||||
|
|
||||||
|
<com.google.android.material.textview.MaterialTextView
|
||||||
|
android:id="@+id/title"
|
||||||
|
android:layout_width="match_parent"
|
||||||
|
android:layout_height="wrap_content"
|
||||||
|
android:layout_marginStart="24dp"
|
||||||
|
android:layout_marginTop="24dp"
|
||||||
|
android:layout_marginEnd="24dp"
|
||||||
|
android:layout_marginBottom="20dp"
|
||||||
|
android:text="@string/password_prompt_description"
|
||||||
|
android:textColor="?android:attr/colorAccent"
|
||||||
|
android:textSize="20sp" />
|
||||||
|
|
||||||
|
<com.google.android.material.textfield.TextInputLayout
|
||||||
|
android:id="@+id/pdf_password_text_input_layout"
|
||||||
|
style="@style/Widget.Material3.TextInputLayout.OutlinedBox"
|
||||||
|
android:layout_width="match_parent"
|
||||||
|
android:layout_height="wrap_content"
|
||||||
|
app:hintEnabled="false"
|
||||||
|
app:passwordToggleEnabled="true"
|
||||||
|
app:passwordToggleTint="?android:attr/colorAccent">
|
||||||
|
|
||||||
|
<com.google.android.material.textfield.TextInputEditText
|
||||||
|
android:id="@+id/pdf_password_edit_text"
|
||||||
|
android:layout_width="match_parent"
|
||||||
|
android:layout_height="wrap_content"
|
||||||
|
android:layout_marginStart="24dp"
|
||||||
|
android:layout_marginEnd="24dp"
|
||||||
|
android:layout_marginBottom="24dp"
|
||||||
|
android:hint="@string/password_prompt_hint"
|
||||||
|
android:inputType="textPassword" />
|
||||||
|
|
||||||
|
</com.google.android.material.textfield.TextInputLayout>
|
||||||
|
</LinearLayout>
|
|
@ -25,24 +25,6 @@
|
||||||
android:title="@string/action_open"
|
android:title="@string/action_open"
|
||||||
app:showAsAction="ifRoom" />
|
app:showAsAction="ifRoom" />
|
||||||
|
|
||||||
<item
|
|
||||||
android:id="@+id/action_zoom_out"
|
|
||||||
android:icon="@drawable/ic_zoom_out_24dp"
|
|
||||||
android:title="@string/action_zoom_out"
|
|
||||||
app:showAsAction="ifRoom" />
|
|
||||||
|
|
||||||
<item
|
|
||||||
android:id="@+id/action_zoom_in"
|
|
||||||
android:icon="@drawable/ic_zoom_in_24dp"
|
|
||||||
android:title="@string/action_zoom_in"
|
|
||||||
app:showAsAction="ifRoom" />
|
|
||||||
|
|
||||||
<item
|
|
||||||
android:id="@+id/action_share"
|
|
||||||
android:icon="@drawable/ic_share_24dp"
|
|
||||||
android:title="@string/action_share"
|
|
||||||
app:showAsAction="ifRoom" />
|
|
||||||
|
|
||||||
<item
|
<item
|
||||||
android:id="@+id/action_first"
|
android:id="@+id/action_first"
|
||||||
android:icon="@drawable/ic_first_page_24dp"
|
android:icon="@drawable/ic_first_page_24dp"
|
||||||
|
@ -73,6 +55,18 @@
|
||||||
android:title="@string/action_rotate_counterclockwise"
|
android:title="@string/action_rotate_counterclockwise"
|
||||||
app:showAsAction="ifRoom" />
|
app:showAsAction="ifRoom" />
|
||||||
|
|
||||||
|
<item
|
||||||
|
android:id="@+id/action_share"
|
||||||
|
android:icon="@drawable/ic_share_24dp"
|
||||||
|
android:title="@string/action_share"
|
||||||
|
app:showAsAction="ifRoom" />
|
||||||
|
|
||||||
|
<item
|
||||||
|
android:id="@+id/action_save_as"
|
||||||
|
android:icon="@drawable/ic_save_24dp"
|
||||||
|
android:title="@string/action_save_as"
|
||||||
|
app:showAsAction="never" />
|
||||||
|
|
||||||
<item
|
<item
|
||||||
android:id="@+id/action_view_document_properties"
|
android:id="@+id/action_view_document_properties"
|
||||||
android:title="@string/action_view_document_properties"
|
android:title="@string/action_view_document_properties"
|
||||||
|
|
|
@ -2,4 +2,5 @@
|
||||||
<adaptive-icon xmlns:android="http://schemas.android.com/apk/res/android">
|
<adaptive-icon xmlns:android="http://schemas.android.com/apk/res/android">
|
||||||
<background android:drawable="@color/ic_launcher_background"/>
|
<background android:drawable="@color/ic_launcher_background"/>
|
||||||
<foreground android:drawable="@drawable/ic_launcher_foreground"/>
|
<foreground android:drawable="@drawable/ic_launcher_foreground"/>
|
||||||
|
<monochrome android:drawable="@drawable/ic_launcher_foreground" />
|
||||||
</adaptive-icon>
|
</adaptive-icon>
|
|
@ -1,5 +0,0 @@
|
||||||
<?xml version="1.0" encoding="utf-8"?>
|
|
||||||
<adaptive-icon xmlns:android="http://schemas.android.com/apk/res/android">
|
|
||||||
<background android:drawable="@color/ic_launcher_background"/>
|
|
||||||
<foreground android:drawable="@drawable/ic_launcher_foreground"/>
|
|
||||||
</adaptive-icon>
|
|
|
@ -1,5 +1,5 @@
|
||||||
<?xml version="1.0" encoding="utf-8"?>
|
<?xml version="1.0" encoding="utf-8"?>
|
||||||
<resources>
|
<resources xmlns:tools="http://schemas.android.com/tools">
|
||||||
<string name="app_name">PDF Viewer</string>
|
<string name="app_name">PDF Viewer</string>
|
||||||
<!--<string name="action_settings">Settings</string>-->
|
<!--<string name="action_settings">Settings</string>-->
|
||||||
<string name="action_previous">Previous page</string>
|
<string name="action_previous">Previous page</string>
|
||||||
|
@ -7,11 +7,11 @@
|
||||||
<string name="action_open">Open document</string>
|
<string name="action_open">Open document</string>
|
||||||
<string name="action_first">First page</string>
|
<string name="action_first">First page</string>
|
||||||
<string name="action_last">Last page</string>
|
<string name="action_last">Last page</string>
|
||||||
<string name="action_zoom_out">Zoom out</string>
|
<string name="action_jump_to_page">Jump to page</string>
|
||||||
<string name="action_zoom_in">Zoom in</string>
|
|
||||||
<string name="action_rotate_clockwise">Rotate clockwise</string>
|
<string name="action_rotate_clockwise">Rotate clockwise</string>
|
||||||
<string name="action_rotate_counterclockwise">Rotate counterclockwise</string>
|
<string name="action_rotate_counterclockwise">Rotate counterclockwise</string>
|
||||||
<string name="action_jump_to_page">Jump to page</string>
|
<string name="action_share">Share</string>
|
||||||
|
<string name="action_save_as">Save as</string>
|
||||||
<string name="action_view_document_properties">Properties</string>
|
<string name="action_view_document_properties">Properties</string>
|
||||||
|
|
||||||
<string name="document_properties_invalid_date">Invalid date</string>
|
<string name="document_properties_invalid_date">Invalid date</string>
|
||||||
|
@ -19,9 +19,14 @@
|
||||||
|
|
||||||
<string name="invalid_mime_type">Cannot open file with invalid MIME type</string>
|
<string name="invalid_mime_type">Cannot open file with invalid MIME type</string>
|
||||||
<string name="legacy_file_uri">Cannot open legacy file paths from insecure apps</string>
|
<string name="legacy_file_uri">Cannot open legacy file paths from insecure apps</string>
|
||||||
<string name="io_error">Received I/O error trying to open content</string>
|
<string name="error_while_opening">Error encountered trying to open content</string>
|
||||||
|
<string name="error_while_saving">Error encountered while saving</string>
|
||||||
|
|
||||||
<string name="webview_out_of_date_title">WebView out-of-date</string>
|
<string name="webview_out_of_date_title">WebView out-of-date</string>
|
||||||
<string name="webview_out_of_date_message">Your current WebView version is %d. The WebView should be at least version %d for the PDF Viewer to work.</string>
|
<string name="webview_out_of_date_message" tools:ignore="PluralsCandidate">Your current WebView version is %1$d. The WebView should be at least version %2$d for the PDF Viewer to work.</string>
|
||||||
<string name="action_share">Share</string>
|
|
||||||
|
<string name="password_prompt_hint">Password</string>
|
||||||
|
<string name="password_prompt_description">Enter the password to decrypt this PDF file</string>
|
||||||
|
<string name="open">Open</string>
|
||||||
|
<string name="cancel">Cancel</string>
|
||||||
</resources>
|
</resources>
|
||||||
|
|
|
@ -1,11 +1,17 @@
|
||||||
buildscript {
|
buildscript {
|
||||||
repositories {
|
repositories {
|
||||||
google()
|
// dependabot cannot handle google()
|
||||||
mavenCentral()
|
maven {
|
||||||
|
url = uri("https://dl.google.com/dl/android/maven2")
|
||||||
|
}
|
||||||
|
// dependabot cannot handle mavenCentral()
|
||||||
|
maven {
|
||||||
|
url = uri("https://repo.maven.apache.org/maven2")
|
||||||
|
}
|
||||||
}
|
}
|
||||||
dependencies {
|
dependencies {
|
||||||
classpath("com.android.tools.build:gradle:7.1.2")
|
classpath("com.android.tools.build:gradle:7.3.0")
|
||||||
classpath("org.jetbrains.kotlin:kotlin-gradle-plugin:1.6.10")
|
classpath("org.jetbrains.kotlin:kotlin-gradle-plugin:1.7.10")
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
Binary file not shown.
|
@ -1,6 +1,6 @@
|
||||||
distributionBase=GRADLE_USER_HOME
|
distributionBase=GRADLE_USER_HOME
|
||||||
distributionPath=wrapper/dists
|
distributionPath=wrapper/dists
|
||||||
distributionSha256Sum=e5444a57cda4a95f90b0c9446a9e1b47d3d7f69057765bfb54bd4f482542d548
|
distributionSha256Sum=f6b8596b10cce501591e92f229816aa4046424f3b24d771751b06779d58c8ec4
|
||||||
distributionUrl=https\://services.gradle.org/distributions/gradle-7.4.1-bin.zip
|
distributionUrl=https\://services.gradle.org/distributions/gradle-7.5.1-bin.zip
|
||||||
zipStoreBase=GRADLE_USER_HOME
|
zipStoreBase=GRADLE_USER_HOME
|
||||||
zipStorePath=wrapper/dists
|
zipStorePath=wrapper/dists
|
||||||
|
|
|
@ -205,6 +205,12 @@ set -- \
|
||||||
org.gradle.wrapper.GradleWrapperMain \
|
org.gradle.wrapper.GradleWrapperMain \
|
||||||
"$@"
|
"$@"
|
||||||
|
|
||||||
|
# Stop when "xargs" is not available.
|
||||||
|
if ! command -v xargs >/dev/null 2>&1
|
||||||
|
then
|
||||||
|
die "xargs is not available"
|
||||||
|
fi
|
||||||
|
|
||||||
# Use "xargs" to parse quoted args.
|
# Use "xargs" to parse quoted args.
|
||||||
#
|
#
|
||||||
# With -n1 it outputs one arg per line, with the quotes and backslashes removed.
|
# With -n1 it outputs one arg per line, with the quotes and backslashes removed.
|
||||||
|
|
|
@ -14,7 +14,7 @@
|
||||||
@rem limitations under the License.
|
@rem limitations under the License.
|
||||||
@rem
|
@rem
|
||||||
|
|
||||||
@if "%DEBUG%" == "" @echo off
|
@if "%DEBUG%"=="" @echo off
|
||||||
@rem ##########################################################################
|
@rem ##########################################################################
|
||||||
@rem
|
@rem
|
||||||
@rem Gradle startup script for Windows
|
@rem Gradle startup script for Windows
|
||||||
|
@ -25,7 +25,7 @@
|
||||||
if "%OS%"=="Windows_NT" setlocal
|
if "%OS%"=="Windows_NT" setlocal
|
||||||
|
|
||||||
set DIRNAME=%~dp0
|
set DIRNAME=%~dp0
|
||||||
if "%DIRNAME%" == "" set DIRNAME=.
|
if "%DIRNAME%"=="" set DIRNAME=.
|
||||||
set APP_BASE_NAME=%~n0
|
set APP_BASE_NAME=%~n0
|
||||||
set APP_HOME=%DIRNAME%
|
set APP_HOME=%DIRNAME%
|
||||||
|
|
||||||
|
@ -40,7 +40,7 @@ if defined JAVA_HOME goto findJavaFromJavaHome
|
||||||
|
|
||||||
set JAVA_EXE=java.exe
|
set JAVA_EXE=java.exe
|
||||||
%JAVA_EXE% -version >NUL 2>&1
|
%JAVA_EXE% -version >NUL 2>&1
|
||||||
if "%ERRORLEVEL%" == "0" goto execute
|
if %ERRORLEVEL% equ 0 goto execute
|
||||||
|
|
||||||
echo.
|
echo.
|
||||||
echo ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH.
|
echo ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH.
|
||||||
|
@ -75,13 +75,15 @@ set CLASSPATH=%APP_HOME%\gradle\wrapper\gradle-wrapper.jar
|
||||||
|
|
||||||
:end
|
:end
|
||||||
@rem End local scope for the variables with windows NT shell
|
@rem End local scope for the variables with windows NT shell
|
||||||
if "%ERRORLEVEL%"=="0" goto mainEnd
|
if %ERRORLEVEL% equ 0 goto mainEnd
|
||||||
|
|
||||||
:fail
|
:fail
|
||||||
rem Set variable GRADLE_EXIT_CONSOLE if you need the _script_ return code instead of
|
rem Set variable GRADLE_EXIT_CONSOLE if you need the _script_ return code instead of
|
||||||
rem the _cmd.exe /c_ return code!
|
rem the _cmd.exe /c_ return code!
|
||||||
if not "" == "%GRADLE_EXIT_CONSOLE%" exit 1
|
set EXIT_CODE=%ERRORLEVEL%
|
||||||
exit /b 1
|
if %EXIT_CODE% equ 0 set EXIT_CODE=1
|
||||||
|
if not ""=="%GRADLE_EXIT_CONSOLE%" exit %EXIT_CODE%
|
||||||
|
exit /b %EXIT_CODE%
|
||||||
|
|
||||||
:mainEnd
|
:mainEnd
|
||||||
if "%OS%"=="Windows_NT" endlocal
|
if "%OS%"=="Windows_NT" endlocal
|
||||||
|
|
|
@ -1 +1 @@
|
||||||
Subproject commit 070a365be5a7579f4ddd5a9a2d4efcd281c2d64f
|
Subproject commit cc3d3bf299ae11f8f72ae8d64cbf19b340f9a996
|
Loading…
Reference in New Issue