libpdfviewer: update to PdfViewer 14
This commit is contained in:
commit
06e54dbb03
6
.github/workflows/build.yml
vendored
6
.github/workflows/build.yml
vendored
@ -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
|
||||||
|
@ -198,7 +198,18 @@ 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) {
|
||||||
pdfDoc = newDoc;
|
pdfDoc = newDoc;
|
||||||
channel.setNumPages(pdfDoc.numPages);
|
channel.setNumPages(pdfDoc.numPages);
|
||||||
pdfDoc.getMetadata().then(function (data) {
|
pdfDoc.getMetadata().then(function (data) {
|
||||||
@ -207,6 +218,7 @@ pdfjsLib.getDocument("https://localhost/placeholder.pdf").promise.then(function(
|
|||||||
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);
|
||||||
});
|
});
|
||||||
|
}
|
||||||
|
@ -2,6 +2,8 @@ package app.grapheneos.pdfviewer;
|
|||||||
|
|
||||||
import android.annotation.SuppressLint;
|
import android.annotation.SuppressLint;
|
||||||
import android.content.Context;
|
import android.content.Context;
|
||||||
|
import android.view.GestureDetector;
|
||||||
|
import android.view.MotionEvent;
|
||||||
import android.view.ScaleGestureDetector;
|
import android.view.ScaleGestureDetector;
|
||||||
import android.view.View;
|
import android.view.View;
|
||||||
|
|
||||||
@ -11,6 +13,7 @@ import android.view.View;
|
|||||||
|
|
||||||
class GestureHelper {
|
class GestureHelper {
|
||||||
public interface GestureListener {
|
public interface GestureListener {
|
||||||
|
boolean onTapUp();
|
||||||
// Can be replaced with ratio when supported
|
// Can be replaced with ratio when supported
|
||||||
void onZoomIn(float value);
|
void onZoomIn(float value);
|
||||||
void onZoomOut(float value);
|
void onZoomOut(float value);
|
||||||
@ -19,6 +22,15 @@ class GestureHelper {
|
|||||||
|
|
||||||
@SuppressLint("ClickableViewAccessibility")
|
@SuppressLint("ClickableViewAccessibility")
|
||||||
static void attach(Context context, View gestureView, GestureListener listener) {
|
static void attach(Context context, View gestureView, GestureListener listener) {
|
||||||
|
|
||||||
|
final GestureDetector detector = new GestureDetector(context,
|
||||||
|
new GestureDetector.SimpleOnGestureListener() {
|
||||||
|
@Override
|
||||||
|
public boolean onSingleTapUp(MotionEvent motionEvent) {
|
||||||
|
return listener.onTapUp();
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
final ScaleGestureDetector scaleDetector = new ScaleGestureDetector(context,
|
final ScaleGestureDetector scaleDetector = new ScaleGestureDetector(context,
|
||||||
new ScaleGestureDetector.SimpleOnScaleGestureListener() {
|
new ScaleGestureDetector.SimpleOnScaleGestureListener() {
|
||||||
final float SPAN_RATIO = 600;
|
final float SPAN_RATIO = 600;
|
||||||
@ -55,6 +67,7 @@ class GestureHelper {
|
|||||||
});
|
});
|
||||||
|
|
||||||
gestureView.setOnTouchListener((view, motionEvent) -> {
|
gestureView.setOnTouchListener((view, motionEvent) -> {
|
||||||
|
detector.onTouchEvent(motionEvent);
|
||||||
scaleDetector.onTouchEvent(motionEvent);
|
scaleDetector.onTouchEvent(motionEvent);
|
||||||
return false;
|
return false;
|
||||||
});
|
});
|
||||||
|
@ -8,11 +8,11 @@ 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;
|
||||||
import android.view.LayoutInflater;
|
|
||||||
import android.view.Menu;
|
import android.view.Menu;
|
||||||
import android.view.MenuInflater;
|
import android.view.MenuInflater;
|
||||||
import android.view.MenuItem;
|
import android.view.MenuItem;
|
||||||
import android.view.View;
|
import android.view.View;
|
||||||
|
import android.view.ViewGroup;
|
||||||
import android.webkit.CookieManager;
|
import android.webkit.CookieManager;
|
||||||
import android.webkit.JavascriptInterface;
|
import android.webkit.JavascriptInterface;
|
||||||
import android.webkit.WebResourceRequest;
|
import android.webkit.WebResourceRequest;
|
||||||
@ -26,14 +26,23 @@ import android.widget.Toast;
|
|||||||
import androidx.annotation.NonNull;
|
import androidx.annotation.NonNull;
|
||||||
import androidx.annotation.RequiresApi;
|
import androidx.annotation.RequiresApi;
|
||||||
import androidx.appcompat.app.AppCompatActivity;
|
import androidx.appcompat.app.AppCompatActivity;
|
||||||
|
import androidx.core.graphics.Insets;
|
||||||
|
import androidx.core.view.ViewCompat;
|
||||||
|
import androidx.core.view.WindowCompat;
|
||||||
|
import androidx.core.view.WindowInsetsCompat;
|
||||||
|
import androidx.fragment.app.Fragment;
|
||||||
import androidx.loader.app.LoaderManager;
|
import androidx.loader.app.LoaderManager;
|
||||||
import androidx.loader.content.Loader;
|
import androidx.loader.content.Loader;
|
||||||
|
|
||||||
|
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 java.io.ByteArrayInputStream;
|
||||||
import java.io.IOException;
|
import java.io.IOException;
|
||||||
import java.io.InputStream;
|
import java.io.InputStream;
|
||||||
import java.util.HashMap;
|
import java.util.HashMap;
|
||||||
@ -67,6 +76,7 @@ public class PdfViewer implements LoaderManager.LoaderCallbacks<List<CharSequenc
|
|||||||
"document-domain=(), " +
|
"document-domain=(), " +
|
||||||
"encrypted-media=(), " +
|
"encrypted-media=(), " +
|
||||||
"fullscreen=(), " +
|
"fullscreen=(), " +
|
||||||
|
"gamepad=(), " +
|
||||||
"geolocation=(), " +
|
"geolocation=(), " +
|
||||||
"gyroscope=(), " +
|
"gyroscope=(), " +
|
||||||
"hid=(), " +
|
"hid=(), " +
|
||||||
@ -80,6 +90,7 @@ public class PdfViewer implements LoaderManager.LoaderCallbacks<List<CharSequenc
|
|||||||
"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=()";
|
||||||
@ -97,8 +108,9 @@ public class PdfViewer implements LoaderManager.LoaderCallbacks<List<CharSequenc
|
|||||||
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 ByteArrayInputStream mInputStream;
|
||||||
|
|
||||||
private PdfviewerBinding binding;
|
private PdfviewerBinding binding;
|
||||||
private TextView mTextView;
|
private TextView mTextView;
|
||||||
@ -107,6 +119,8 @@ public class PdfViewer implements LoaderManager.LoaderCallbacks<List<CharSequenc
|
|||||||
AppCompatActivity activity;
|
AppCompatActivity activity;
|
||||||
String fileName;
|
String fileName;
|
||||||
Long fileSize;
|
Long fileSize;
|
||||||
|
private Snackbar snackbar;
|
||||||
|
private PasswordPromptFragment mPasswordPromptFragment;
|
||||||
|
|
||||||
private class Channel {
|
private class Channel {
|
||||||
@JavascriptInterface
|
@JavascriptInterface
|
||||||
@ -140,13 +154,49 @@ public class PdfViewer implements LoaderManager.LoaderCallbacks<List<CharSequenc
|
|||||||
args.putString(KEY_PROPERTIES, properties);
|
args.putString(KEY_PROPERTIES, properties);
|
||||||
activity.runOnUiThread(() -> LoaderManager.getInstance(PdfViewer.this.activity).restartLoader(DocumentPropertiesLoader.ID, args, PdfViewer.this));
|
activity.runOnUiThread(() -> LoaderManager.getInstance(PdfViewer.this.activity).restartLoader(DocumentPropertiesLoader.ID, args, PdfViewer.this));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@JavascriptInterface
|
||||||
|
public void showPasswordPrompt() {
|
||||||
|
if (getPasswordPromptFragment().isAdded()) {
|
||||||
|
getPasswordPromptFragment().dismiss();
|
||||||
|
}
|
||||||
|
getPasswordPromptFragment().show(activity.getSupportFragmentManager(), PasswordPromptFragment.class.getName());
|
||||||
|
}
|
||||||
|
|
||||||
|
@JavascriptInterface
|
||||||
|
public void invalidPassword() {
|
||||||
|
activity.runOnUiThread(PdfViewer.this::notifyInvalidPassword);
|
||||||
|
showPasswordPrompt();
|
||||||
|
}
|
||||||
|
|
||||||
|
@JavascriptInterface
|
||||||
|
public String getPassword() {
|
||||||
|
return mEncryptedDocumentPassword != null ? mEncryptedDocumentPassword : "";
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private void notifyInvalidPassword() {
|
||||||
|
snackbar.setText(R.string.password_prompt_invalid_password).show();
|
||||||
}
|
}
|
||||||
|
|
||||||
public PdfViewer(@NonNull AppCompatActivity activity) {
|
public PdfViewer(@NonNull AppCompatActivity activity) {
|
||||||
this.activity = activity;
|
this.activity = activity;
|
||||||
LayoutInflater inflater = activity.getLayoutInflater();
|
binding = PdfviewerBinding.inflate(activity.getLayoutInflater());
|
||||||
binding = PdfviewerBinding.inflate(inflater);
|
|
||||||
activity.setContentView(binding.getRoot());
|
activity.setContentView(binding.getRoot());
|
||||||
|
activity.setSupportActionBar(binding.toolbar);
|
||||||
|
|
||||||
|
WindowCompat.setDecorFitsSystemWindows(activity.getWindow(), false);
|
||||||
|
|
||||||
|
// Margins for the toolbar are needed, so that content of the toolbar
|
||||||
|
// is not covered by a system button navigation bar when in landscape.
|
||||||
|
ViewCompat.setOnApplyWindowInsetsListener(binding.toolbar, (v, windowInsets) -> {
|
||||||
|
Insets insets = windowInsets.getInsets(WindowInsetsCompat.Type.systemBars());
|
||||||
|
ViewGroup.MarginLayoutParams mlp = (ViewGroup.MarginLayoutParams) v.getLayoutParams();
|
||||||
|
mlp.leftMargin = insets.left;
|
||||||
|
mlp.rightMargin = insets.right;
|
||||||
|
v.setLayoutParams(mlp);
|
||||||
|
return windowInsets;
|
||||||
|
});
|
||||||
|
|
||||||
binding.webview.setBackgroundColor(Color.TRANSPARENT);
|
binding.webview.setBackgroundColor(Color.TRANSPARENT);
|
||||||
|
|
||||||
@ -189,6 +239,7 @@ public class PdfViewer implements LoaderManager.LoaderCallbacks<List<CharSequenc
|
|||||||
Log.d(TAG, "path " + path);
|
Log.d(TAG, "path " + path);
|
||||||
|
|
||||||
if ("/placeholder.pdf".equals(path)) {
|
if ("/placeholder.pdf".equals(path)) {
|
||||||
|
mInputStream.reset();
|
||||||
return new WebResourceResponse("application/pdf", null, mInputStream);
|
return new WebResourceResponse("application/pdf", null, mInputStream);
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -222,11 +273,27 @@ public class PdfViewer implements LoaderManager.LoaderCallbacks<List<CharSequenc
|
|||||||
public void onPageFinished(WebView view, String url) {
|
public void onPageFinished(WebView view, String url) {
|
||||||
mDocumentState = STATE_LOADED;
|
mDocumentState = STATE_LOADED;
|
||||||
activity.invalidateOptionsMenu();
|
activity.invalidateOptionsMenu();
|
||||||
|
loadPdfWithPassword(mEncryptedDocumentPassword);
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
GestureHelper.attach(activity, binding.webview,
|
GestureHelper.attach(activity, binding.webview,
|
||||||
new GestureHelper.GestureListener() {
|
new GestureHelper.GestureListener() {
|
||||||
|
@Override
|
||||||
|
public boolean onTapUp() {
|
||||||
|
binding.webview.evaluateJavascript("isTextSelected()", selection -> {
|
||||||
|
if (!Boolean.parseBoolean(selection)) {
|
||||||
|
if ((activity.getWindow().getDecorView().getSystemUiVisibility() &
|
||||||
|
View.SYSTEM_UI_FLAG_FULLSCREEN) == 0) {
|
||||||
|
hideSystemUi();
|
||||||
|
} else {
|
||||||
|
showSystemUi();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
});
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public void onZoomIn(float value) {
|
public void onZoomIn(float value) {
|
||||||
zoomIn(value, false);
|
zoomIn(value, false);
|
||||||
@ -248,17 +315,44 @@ public class PdfViewer implements LoaderManager.LoaderCallbacks<List<CharSequenc
|
|||||||
mTextView.setTextColor(ColorStateList.valueOf(Color.WHITE));
|
mTextView.setTextColor(ColorStateList.valueOf(Color.WHITE));
|
||||||
mTextView.setTextSize(18);
|
mTextView.setTextSize(18);
|
||||||
mTextView.setPadding(PADDING, 0, PADDING, 0);
|
mTextView.setPadding(PADDING, 0, PADDING, 0);
|
||||||
|
|
||||||
|
snackbar = Snackbar.make(binding.getRoot(), "", Snackbar.LENGTH_LONG);
|
||||||
}
|
}
|
||||||
|
|
||||||
public void onCreateOptionMenu(Menu menu) {
|
public void onDestroy() {
|
||||||
MenuInflater inflater = activity.getMenuInflater();
|
binding.webview.removeJavascriptInterface("channel");
|
||||||
inflater.inflate(R.menu.pdf_viewer, menu);
|
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 = activity.getSupportFragmentManager().findFragmentByTag(PasswordPromptFragment.class.getName());
|
||||||
|
if (fragment != null) {
|
||||||
|
mPasswordPromptFragment = (PasswordPromptFragment) fragment;
|
||||||
|
} else {
|
||||||
|
mPasswordPromptFragment = new PasswordPromptFragment(this);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return mPasswordPromptFragment;
|
||||||
}
|
}
|
||||||
|
|
||||||
public void onResume() {
|
public void onResume() {
|
||||||
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) {
|
|
||||||
// The user could have left the activity to update the WebView
|
// The user could have left the activity to update the WebView
|
||||||
activity.invalidateOptionsMenu();
|
activity.invalidateOptionsMenu();
|
||||||
|
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) {
|
||||||
if (getWebViewRelease() >= MIN_WEBVIEW_RELEASE) {
|
if (getWebViewRelease() >= MIN_WEBVIEW_RELEASE) {
|
||||||
binding.webviewOutOfDateLayout.setVisibility(View.GONE);
|
binding.webviewOutOfDateLayout.setVisibility(View.GONE);
|
||||||
binding.webview.setVisibility(View.VISIBLE);
|
binding.webview.setVisibility(View.VISIBLE);
|
||||||
@ -294,14 +388,20 @@ public class PdfViewer implements LoaderManager.LoaderCallbacks<List<CharSequenc
|
|||||||
mDocumentProperties = null;
|
mDocumentProperties = null;
|
||||||
}
|
}
|
||||||
|
|
||||||
public void loadPdf(InputStream inputStream, String fileName, Long fileSize) {
|
public void loadPdf(ByteArrayInputStream inputStream, String fileName, Long fileSize) {
|
||||||
mPage = 1;
|
mPage = 1;
|
||||||
mDocumentProperties = null;
|
mDocumentProperties = null;
|
||||||
mInputStream = inputStream;
|
mInputStream = inputStream;
|
||||||
this.fileName = fileName;
|
this.fileName = fileName;
|
||||||
this.fileSize = fileSize;
|
this.fileSize = fileSize;
|
||||||
binding.webview.loadUrl("https://localhost/viewer.html");
|
showSystemUi();
|
||||||
activity.invalidateOptionsMenu();
|
activity.invalidateOptionsMenu();
|
||||||
|
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) {
|
||||||
@ -355,6 +455,25 @@ public class PdfViewer implements LoaderManager.LoaderCallbacks<List<CharSequenc
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private void showSystemUi() {
|
||||||
|
activity.getWindow().getDecorView().setSystemUiVisibility(
|
||||||
|
View.SYSTEM_UI_FLAG_LAYOUT_STABLE |
|
||||||
|
View.SYSTEM_UI_FLAG_LAYOUT_HIDE_NAVIGATION |
|
||||||
|
View.SYSTEM_UI_FLAG_LAYOUT_FULLSCREEN);
|
||||||
|
activity.getSupportActionBar().show();
|
||||||
|
}
|
||||||
|
|
||||||
|
private void hideSystemUi() {
|
||||||
|
activity.getWindow().getDecorView().setSystemUiVisibility(
|
||||||
|
View.SYSTEM_UI_FLAG_LAYOUT_STABLE |
|
||||||
|
View.SYSTEM_UI_FLAG_LAYOUT_HIDE_NAVIGATION |
|
||||||
|
View.SYSTEM_UI_FLAG_LAYOUT_FULLSCREEN |
|
||||||
|
View.SYSTEM_UI_FLAG_HIDE_NAVIGATION |
|
||||||
|
View.SYSTEM_UI_FLAG_FULLSCREEN |
|
||||||
|
View.SYSTEM_UI_FLAG_IMMERSIVE);
|
||||||
|
activity.getSupportActionBar().hide();
|
||||||
|
}
|
||||||
|
|
||||||
private void showPageNumber() {
|
private void showPageNumber() {
|
||||||
if (mToast != null) {
|
if (mToast != null) {
|
||||||
mToast.cancel();
|
mToast.cancel();
|
||||||
@ -367,11 +486,15 @@ public class PdfViewer implements LoaderManager.LoaderCallbacks<List<CharSequenc
|
|||||||
mToast.show();
|
mToast.show();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public void onCreateOptionMenu(Menu menu) {
|
||||||
|
MenuInflater inflater = activity.getMenuInflater();
|
||||||
|
inflater.inflate(R.menu.pdf_viewer, menu);
|
||||||
|
}
|
||||||
|
|
||||||
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 };
|
|
||||||
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);
|
||||||
@ -389,8 +512,6 @@ public class PdfViewer implements LoaderManager.LoaderCallbacks<List<CharSequenc
|
|||||||
mDocumentState = STATE_END;
|
mDocumentState = STATE_END;
|
||||||
}
|
}
|
||||||
|
|
||||||
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_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);
|
||||||
|
|
||||||
@ -411,12 +532,6 @@ public class PdfViewer implements LoaderManager.LoaderCallbacks<List<CharSequenc
|
|||||||
} else if (itemId == R.id.action_last) {
|
} else if (itemId == R.id.action_last) {
|
||||||
onJumpToPageInDocument(mNumPages);
|
onJumpToPageInDocument(mNumPages);
|
||||||
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;
|
||||||
|
@ -0,0 +1,67 @@
|
|||||||
|
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 android.widget.EditText
|
||||||
|
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
|
||||||
|
|
||||||
|
class PasswordPromptFragment(private val pdfViewer: PdfViewer) : DialogFragment() {
|
||||||
|
|
||||||
|
private lateinit var passwordEditText : EditText
|
||||||
|
|
||||||
|
override fun onCreateDialog(savedInstanceState: Bundle?): Dialog {
|
||||||
|
val passwordPrompt = AlertDialog.Builder(requireContext())
|
||||||
|
val passwordDialogFragmentBinding =
|
||||||
|
PasswordDialogFragmentBinding.inflate(LayoutInflater.from(requireContext()))
|
||||||
|
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) { _, _ -> sendPassword() }
|
||||||
|
passwordPrompt.setNegativeButton(R.string.cancel, null)
|
||||||
|
val dialog = passwordPrompt.create()
|
||||||
|
dialog.setCanceledOnTouchOutside(false)
|
||||||
|
dialog.window?.setSoftInputMode(WindowManager.LayoutParams.SOFT_INPUT_STATE_VISIBLE)
|
||||||
|
return dialog
|
||||||
|
}
|
||||||
|
|
||||||
|
private fun updatePositiveButton() {
|
||||||
|
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)) {
|
||||||
|
pdfViewer.loadPdfWithPassword(password)
|
||||||
|
dialog?.dismiss()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun onStart() {
|
||||||
|
super.onStart()
|
||||||
|
updatePositiveButton()
|
||||||
|
passwordEditText.requestFocus()
|
||||||
|
}
|
||||||
|
}
|
@ -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="M6,2c-1.1,0 -1.99,0.9 -1.99,2L4,20c0,1.1 0.89,2 1.99,2L18,22c1.1,0 2,-0.9 2,-2L20,8.83c0,-0.53 -0.21,-1.04 -0.59,-1.41l-4.83,-4.83c-0.37,-0.38 -0.88,-0.59 -1.41,-0.59L6,2zM13,8L13,3.5L18.5,9L14,9c-0.55,0 -1,-0.45 -1,-1z" />
|
|
||||||
</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>
|
|
38
app/src/main/res/layout/password_dialog_fragment.xml
Normal file
38
app/src/main/res/layout/password_dialog_fragment.xml
Normal file
@ -0,0 +1,38 @@
|
|||||||
|
<?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">
|
||||||
|
|
||||||
|
<TextView
|
||||||
|
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
|
||||||
|
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>
|
@ -4,6 +4,21 @@
|
|||||||
android:layout_width="match_parent"
|
android:layout_width="match_parent"
|
||||||
android:layout_height="match_parent">
|
android:layout_height="match_parent">
|
||||||
|
|
||||||
|
<com.google.android.material.appbar.AppBarLayout
|
||||||
|
android:layout_width="match_parent"
|
||||||
|
android:layout_height="wrap_content"
|
||||||
|
android:fitsSystemWindows="true"
|
||||||
|
android:theme="@style/AppTheme.AppBarOverlay">
|
||||||
|
|
||||||
|
<com.google.android.material.appbar.MaterialToolbar
|
||||||
|
android:id="@+id/toolbar"
|
||||||
|
android:layout_width="match_parent"
|
||||||
|
android:layout_height="?attr/actionBarSize"
|
||||||
|
android:background="#212121"
|
||||||
|
app:popupTheme="@style/AppTheme.PopupOverlay" />
|
||||||
|
|
||||||
|
</com.google.android.material.appbar.AppBarLayout>
|
||||||
|
|
||||||
<WebView
|
<WebView
|
||||||
android:id="@+id/webview"
|
android:id="@+id/webview"
|
||||||
android:layout_width="match_parent"
|
android:layout_width="match_parent"
|
||||||
|
@ -19,18 +19,6 @@
|
|||||||
android:title="@string/action_next"
|
android:title="@string/action_next"
|
||||||
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
|
<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"
|
||||||
|
13
app/src/main/res/values-night/styles.xml
Normal file
13
app/src/main/res/values-night/styles.xml
Normal file
@ -0,0 +1,13 @@
|
|||||||
|
<resources>
|
||||||
|
|
||||||
|
<!-- Base application theme. -->
|
||||||
|
<style name="AppTheme" parent="Theme.Material3.DayNight.NoActionBar">
|
||||||
|
<!-- Customize your theme here. -->
|
||||||
|
<item name="colorPrimary">#DEFFFFFF</item>
|
||||||
|
<item name="android:statusBarColor">#212121</item>
|
||||||
|
<item name="android:navigationBarColor">@android:color/transparent</item>
|
||||||
|
</style>
|
||||||
|
|
||||||
|
<style name="AppTheme.PopupOverlay" parent="ThemeOverlay.Material3.Dark" />
|
||||||
|
|
||||||
|
</resources>
|
@ -6,20 +6,20 @@
|
|||||||
<string name="action_next">Next page</string>
|
<string name="action_next">Next page</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_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>
|
||||||
<string name="document_properties_retrieval_failed">Failed to obtain document metadata</string>
|
<string name="document_properties_retrieval_failed">Failed to obtain document metadata</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="io_error">Received I/O error trying to open content</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">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="password_prompt_hint">Password</string>
|
||||||
|
<string name="password_prompt_description">Enter the password to decrypt this PDF file</string>
|
||||||
|
<string name="password_prompt_invalid_password">Invalid password</string>
|
||||||
|
<string name="open">Open</string>
|
||||||
|
<string name="cancel">Cancel</string>
|
||||||
</resources>
|
</resources>
|
||||||
|
15
app/src/main/res/values/styles.xml
Normal file
15
app/src/main/res/values/styles.xml
Normal file
@ -0,0 +1,15 @@
|
|||||||
|
<resources>
|
||||||
|
|
||||||
|
<!-- Base application theme. -->
|
||||||
|
<style name="AppTheme" parent="Theme.Material3.DayNight.NoActionBar">
|
||||||
|
<!-- Customize your theme here. -->
|
||||||
|
<item name="colorPrimary">#000000</item>
|
||||||
|
<item name="android:statusBarColor">#212121</item>
|
||||||
|
<item name="android:navigationBarColor">@android:color/transparent</item>
|
||||||
|
</style>
|
||||||
|
|
||||||
|
<style name="AppTheme.AppBarOverlay" parent="ThemeOverlay.Material3.Dark" />
|
||||||
|
|
||||||
|
<style name="AppTheme.PopupOverlay" parent="ThemeOverlay.Material3.Light" />
|
||||||
|
|
||||||
|
</resources>
|
@ -4,8 +4,8 @@ buildscript {
|
|||||||
mavenCentral()
|
mavenCentral()
|
||||||
}
|
}
|
||||||
dependencies {
|
dependencies {
|
||||||
classpath("com.android.tools.build:gradle:7.1.2")
|
classpath("com.android.tools.build:gradle:7.1.3")
|
||||||
classpath("org.jetbrains.kotlin:kotlin-gradle-plugin:1.6.10")
|
classpath("org.jetbrains.kotlin:kotlin-gradle-plugin:1.6.21")
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
BIN
gradle/wrapper/gradle-wrapper.jar
vendored
BIN
gradle/wrapper/gradle-wrapper.jar
vendored
Binary file not shown.
4
gradle/wrapper/gradle-wrapper.properties
vendored
4
gradle/wrapper/gradle-wrapper.properties
vendored
@ -1,6 +1,6 @@
|
|||||||
distributionBase=GRADLE_USER_HOME
|
distributionBase=GRADLE_USER_HOME
|
||||||
distributionPath=wrapper/dists
|
distributionPath=wrapper/dists
|
||||||
distributionSha256Sum=e5444a57cda4a95f90b0c9446a9e1b47d3d7f69057765bfb54bd4f482542d548
|
distributionSha256Sum=29e49b10984e585d8118b7d0bc452f944e386458df27371b49b4ac1dec4b7fda
|
||||||
distributionUrl=https\://services.gradle.org/distributions/gradle-7.4.1-bin.zip
|
distributionUrl=https\://services.gradle.org/distributions/gradle-7.4.2-bin.zip
|
||||||
zipStoreBase=GRADLE_USER_HOME
|
zipStoreBase=GRADLE_USER_HOME
|
||||||
zipStorePath=wrapper/dists
|
zipStorePath=wrapper/dists
|
||||||
|
Loading…
Reference in New Issue
Block a user