Compare commits
10 Commits
main
...
libpdfview
Author | SHA1 | Date |
---|---|---|
Matéo Duparc | 2296593275 | |
Matéo Duparc | 59973a6b42 | |
Matéo Duparc | 476f4aa685 | |
Matéo Duparc | c74b374ec4 | |
Matéo Duparc | 6e8fdb56a5 | |
Matéo Duparc | 2766000ebc | |
Matéo Duparc | 06e54dbb03 | |
Matéo Duparc | 8fe8b2f4b3 | |
Matéo Duparc | 0f312347dd | |
Matéo Duparc | 4e4b7c5da4 |
|
@ -1,40 +0,0 @@
|
|||
{
|
||||
"env": {
|
||||
"browser": true,
|
||||
"es2022": true
|
||||
},
|
||||
"extends": "eslint:recommended",
|
||||
"parserOptions": {
|
||||
"ecmaVersion": 2022,
|
||||
"sourceType": "module"
|
||||
},
|
||||
"rules": {
|
||||
"indent": [
|
||||
"error",
|
||||
4
|
||||
],
|
||||
"linebreak-style": [
|
||||
"error",
|
||||
"unix"
|
||||
],
|
||||
"quotes": [
|
||||
"error",
|
||||
"double"
|
||||
],
|
||||
"semi": [
|
||||
"error",
|
||||
"always"
|
||||
],
|
||||
"no-var": [
|
||||
"error"
|
||||
]
|
||||
},
|
||||
"globals": {
|
||||
"pdfjsLib": "readonly",
|
||||
"channel": "readonly"
|
||||
},
|
||||
"ignorePatterns": [
|
||||
"pdf.js",
|
||||
"pdf.worker.js"
|
||||
]
|
||||
}
|
|
@ -1,17 +0,0 @@
|
|||
version: 2
|
||||
updates:
|
||||
- package-ecosystem: github-actions
|
||||
directory: "/"
|
||||
schedule:
|
||||
interval: daily
|
||||
target-branch: main
|
||||
- package-ecosystem: gradle
|
||||
directory: "/"
|
||||
schedule:
|
||||
interval: daily
|
||||
target-branch: main
|
||||
- package-ecosystem: npm
|
||||
directory: "/"
|
||||
schedule:
|
||||
interval: daily
|
||||
target-branch: main
|
|
@ -1,23 +0,0 @@
|
|||
name: Build application
|
||||
|
||||
on: [pull_request, push]
|
||||
|
||||
jobs:
|
||||
build:
|
||||
runs-on: ubuntu-latest
|
||||
|
||||
steps:
|
||||
- uses: actions/checkout@v3
|
||||
- uses: actions/setup-node@v3
|
||||
with:
|
||||
cache: npm
|
||||
- name: Set up JDK 20
|
||||
uses: actions/setup-java@v3
|
||||
with:
|
||||
distribution: 'temurin'
|
||||
java-version: 20
|
||||
cache: gradle
|
||||
|
||||
- run: npm ci --ignore-scripts
|
||||
- name: Build with Gradle
|
||||
run: ./gradlew build --no-daemon
|
|
@ -1,11 +0,0 @@
|
|||
name: Validate Gradle Wrapper
|
||||
|
||||
on: [pull_request, push]
|
||||
|
||||
jobs:
|
||||
validation:
|
||||
name: Validation
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- uses: actions/checkout@v3
|
||||
- uses: gradle/wrapper-validation-action@v1
|
|
@ -6,4 +6,3 @@ keystore.properties
|
|||
*.keystore
|
||||
/.idea
|
||||
/releases
|
||||
/node_modules
|
||||
|
|
17
README.md
17
README.md
|
@ -1,11 +1,6 @@
|
|||
Simple Android PDF viewer based on pdf.js and content providers. The app
|
||||
doesn't require any permissions. The PDF stream is fed into the sandboxed
|
||||
WebView without giving it access to content or files. Content-Security-Policy
|
||||
is used to enforce that the JavaScript and styling properties within the
|
||||
WebView are entirely static content from the apk assets. It reuses the hardened
|
||||
Chromium rendering stack while only exposing a tiny subset of the attack
|
||||
surface compared to actual web content. The PDF rendering code itself is memory
|
||||
safe with dynamic code evaluation disabled, and even if an attacker did gain
|
||||
code execution by exploiting the underlying web rendering engine, they're
|
||||
within the Chromium renderer sandbox with no access to the network (unlike a
|
||||
browser), files, or other content.
|
||||
Fork of GrapheneOS' [PdfViewer](https://github.com/GrapheneOS/PdfViewer) to work as a library.
|
||||
|
||||
## Warning !
|
||||
The only goal of this library is to be integrated in [DroidFS](https://forge.chapril.org/hardcoresushi/DroidFS). Use it at your own risk !
|
||||
|
||||
The npm dependency has been removed. Instead, `pdf.min.js` and `pdf.worker.min.js` must be manually placed in `app/pdfjs-dist/build/`.
|
||||
|
|
|
@ -9,7 +9,7 @@ if (useKeystoreProperties) {
|
|||
}
|
||||
|
||||
plugins {
|
||||
id("com.android.application")
|
||||
id("com.android.library")
|
||||
id("kotlin-android")
|
||||
}
|
||||
|
||||
|
@ -44,21 +44,13 @@ android {
|
|||
namespace = "app.grapheneos.pdfviewer"
|
||||
|
||||
defaultConfig {
|
||||
applicationId = "app.grapheneos.pdfviewer"
|
||||
minSdk = 26
|
||||
minSdk = 21
|
||||
targetSdk = 33
|
||||
versionCode = 17
|
||||
versionName = versionCode.toString()
|
||||
resourceConfigurations.add("en")
|
||||
}
|
||||
|
||||
buildTypes {
|
||||
getByName("debug") {
|
||||
applicationIdSuffix = ".debug"
|
||||
}
|
||||
|
||||
getByName("release") {
|
||||
isShrinkResources = true
|
||||
isMinifyEnabled = true
|
||||
proguardFiles(getDefaultProguardFile("proguard-android-optimize.txt"), "proguard-rules.pro")
|
||||
if (useKeystoreProperties) {
|
||||
|
@ -68,7 +60,6 @@ android {
|
|||
|
||||
create("play") {
|
||||
initWith(getByName("release"))
|
||||
applicationIdSuffix = ".play"
|
||||
if (useKeystoreProperties) {
|
||||
signingConfig = signingConfigs.getByName("play")
|
||||
}
|
||||
|
|
|
@ -3,25 +3,7 @@
|
|||
android:targetSandboxVersion="2">
|
||||
<original-package android:name="org.grapheneos.pdfviewer" />
|
||||
|
||||
<application android:name=".App"
|
||||
android:icon="@mipmap/ic_launcher"
|
||||
android:label="@string/app_name"
|
||||
android:theme="@style/AppTheme">
|
||||
<activity android:name=".PdfViewer"
|
||||
android:documentLaunchMode="always"
|
||||
android:exported="true">
|
||||
<intent-filter>
|
||||
<action android:name="android.intent.action.MAIN" />
|
||||
<category android:name="android.intent.category.LAUNCHER" />
|
||||
</intent-filter>
|
||||
<intent-filter>
|
||||
<action android:name="android.intent.action.VIEW" />
|
||||
<category android:name="android.intent.category.DEFAULT" />
|
||||
<category android:name="android.intent.category.BROWSABLE" />
|
||||
<data android:mimeType="application/pdf" />
|
||||
</intent-filter>
|
||||
</activity>
|
||||
|
||||
<application>
|
||||
<meta-data android:name="android.webkit.WebView.MetricsOptOut"
|
||||
android:value="true" />
|
||||
<meta-data android:name="android.webkit.WebView.EnableSafeBrowsing"
|
||||
|
|
|
@ -1 +1 @@
|
|||
../../../../node_modules/pdfjs-dist/build/pdf.min.js
|
||||
../../../pdfjs-dist/build/pdf.min.js
|
|
@ -1 +1 @@
|
|||
../../../../node_modules/pdfjs-dist/build/pdf.worker.min.js
|
||||
../../../pdfjs-dist/build/pdf.worker.min.js
|
Binary file not shown.
Before Width: | Height: | Size: 6.7 KiB |
Binary file not shown.
Before Width: | Height: | Size: 11 KiB |
|
@ -1,13 +0,0 @@
|
|||
package app.grapheneos.pdfviewer
|
||||
|
||||
import android.app.Application
|
||||
import com.google.android.material.color.DynamicColors
|
||||
|
||||
class App : Application() {
|
||||
|
||||
override fun onCreate() {
|
||||
super.onCreate()
|
||||
DynamicColors.applyToActivitiesIfAvailable(this)
|
||||
}
|
||||
|
||||
}
|
|
@ -1,30 +0,0 @@
|
|||
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,
|
||||
IllegalArgumentException::class,
|
||||
OutOfMemoryError::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,7 +1,5 @@
|
|||
package app.grapheneos.pdfviewer;
|
||||
|
||||
import android.annotation.SuppressLint;
|
||||
import android.content.Intent;
|
||||
import android.content.pm.PackageInfo;
|
||||
import android.content.res.ColorStateList;
|
||||
import android.graphics.Color;
|
||||
|
@ -25,9 +23,8 @@ import android.webkit.WebViewClient;
|
|||
import android.widget.TextView;
|
||||
import android.widget.Toast;
|
||||
|
||||
import androidx.activity.result.ActivityResultLauncher;
|
||||
import androidx.activity.result.contract.ActivityResultContracts;
|
||||
import androidx.annotation.NonNull;
|
||||
import androidx.annotation.RequiresApi;
|
||||
import androidx.appcompat.app.AppCompatActivity;
|
||||
import androidx.core.graphics.Insets;
|
||||
import androidx.core.view.ViewCompat;
|
||||
|
@ -48,22 +45,17 @@ import app.grapheneos.pdfviewer.ktx.ViewKt;
|
|||
import app.grapheneos.pdfviewer.loader.DocumentPropertiesAsyncTaskLoader;
|
||||
import app.grapheneos.pdfviewer.viewModel.PasswordStatus;
|
||||
|
||||
import java.io.ByteArrayInputStream;
|
||||
import java.io.IOException;
|
||||
import java.io.InputStream;
|
||||
import java.io.FileNotFoundException;
|
||||
import java.util.ArrayList;
|
||||
import java.util.Arrays;
|
||||
import java.util.HashMap;
|
||||
import java.util.List;
|
||||
|
||||
public class PdfViewer extends AppCompatActivity implements LoaderManager.LoaderCallbacks<List<CharSequence>> {
|
||||
public class PdfViewer implements LoaderManager.LoaderCallbacks<List<CharSequence>> {
|
||||
public static final String TAG = "PdfViewer";
|
||||
|
||||
private static final String STATE_URI = "uri";
|
||||
private static final String STATE_PAGE = "page";
|
||||
private static final String STATE_ZOOM_RATIO = "zoomRatio";
|
||||
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 int MIN_WEBVIEW_RELEASE = 92;
|
||||
|
||||
|
@ -116,7 +108,6 @@ public class PdfViewer extends AppCompatActivity implements LoaderManager.Loader
|
|||
private static final int STATE_END = 2;
|
||||
private static final int PADDING = 10;
|
||||
|
||||
private Uri mUri;
|
||||
public int mPage;
|
||||
public int mNumPages;
|
||||
private float mZoomRatio = 1f;
|
||||
|
@ -124,43 +115,18 @@ public class PdfViewer extends AppCompatActivity implements LoaderManager.Loader
|
|||
private int mDocumentState;
|
||||
private String mEncryptedDocumentPassword;
|
||||
private List<CharSequence> mDocumentProperties;
|
||||
private InputStream mInputStream;
|
||||
private ByteArrayInputStream mInputStream;
|
||||
|
||||
private PdfviewerBinding binding;
|
||||
private TextView mTextView;
|
||||
private Toast mToast;
|
||||
private Snackbar snackbar;
|
||||
|
||||
AppCompatActivity activity;
|
||||
String fileName;
|
||||
Long fileSize;
|
||||
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 {
|
||||
@JavascriptInterface
|
||||
public int getPage() {
|
||||
|
@ -195,7 +161,7 @@ public class PdfViewer extends AppCompatActivity implements LoaderManager.Loader
|
|||
@JavascriptInterface
|
||||
public void setNumPages(int numPages) {
|
||||
mNumPages = numPages;
|
||||
runOnUiThread(PdfViewer.this::invalidateOptionsMenu);
|
||||
activity.runOnUiThread(activity::invalidateOptionsMenu);
|
||||
}
|
||||
|
||||
@JavascriptInterface
|
||||
|
@ -206,20 +172,20 @@ public class PdfViewer extends AppCompatActivity implements LoaderManager.Loader
|
|||
|
||||
final Bundle args = new Bundle();
|
||||
args.putString(KEY_PROPERTIES, properties);
|
||||
runOnUiThread(() -> LoaderManager.getInstance(PdfViewer.this).restartLoader(DocumentPropertiesAsyncTaskLoader.ID, args, PdfViewer.this));
|
||||
activity.runOnUiThread(() -> LoaderManager.getInstance(PdfViewer.this.activity).restartLoader(DocumentPropertiesAsyncTaskLoader.ID, args, PdfViewer.this));
|
||||
}
|
||||
|
||||
@JavascriptInterface
|
||||
public void showPasswordPrompt() {
|
||||
if (!getPasswordPromptFragment().isAdded()){
|
||||
getPasswordPromptFragment().show(getSupportFragmentManager(), PasswordPromptFragment.class.getName());
|
||||
if (!getPasswordPromptFragment().isAdded()) {
|
||||
getPasswordPromptFragment().show(activity.getSupportFragmentManager(), PasswordPromptFragment.class.getName());
|
||||
}
|
||||
passwordValidationViewModel.passwordMissing();
|
||||
}
|
||||
|
||||
@JavascriptInterface
|
||||
public void invalidPassword() {
|
||||
runOnUiThread(() -> passwordValidationViewModel.invalid());
|
||||
activity.runOnUiThread(() -> passwordValidationViewModel.invalid());
|
||||
}
|
||||
|
||||
@JavascriptInterface
|
||||
|
@ -236,16 +202,14 @@ public class PdfViewer extends AppCompatActivity implements LoaderManager.Loader
|
|||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
@SuppressLint({"SetJavaScriptEnabled", "ClickableViewAccessibility"})
|
||||
protected void onCreate(Bundle savedInstanceState) {
|
||||
super.onCreate(savedInstanceState);
|
||||
binding = PdfviewerBinding.inflate(getLayoutInflater());
|
||||
setContentView(binding.getRoot());
|
||||
setSupportActionBar(binding.toolbar);
|
||||
passwordValidationViewModel = new ViewModelProvider(this, ViewModelProvider.AndroidViewModelFactory.getInstance(getApplication())).get(PasswordStatus.class);
|
||||
public PdfViewer(@NonNull AppCompatActivity activity) {
|
||||
this.activity = activity;
|
||||
binding = PdfviewerBinding.inflate(activity.getLayoutInflater());
|
||||
activity.setContentView(binding.getRoot());
|
||||
activity.setSupportActionBar(binding.toolbar);
|
||||
passwordValidationViewModel = new ViewModelProvider(activity, ViewModelProvider.AndroidViewModelFactory.getInstance(activity.getApplication())).get(PasswordStatus.class);
|
||||
|
||||
WindowCompat.setDecorFitsSystemWindows(getWindow(), false);
|
||||
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.
|
||||
|
@ -278,7 +242,7 @@ public class PdfViewer extends AppCompatActivity implements LoaderManager.Loader
|
|||
binding.webview.setWebViewClient(new WebViewClient() {
|
||||
private WebResourceResponse fromAsset(final String mime, final String path) {
|
||||
try {
|
||||
InputStream inputStream = getAssets().open(path.substring(1));
|
||||
InputStream inputStream = activity.getAssets().open(path.substring(1));
|
||||
return new WebResourceResponse(mime, null, inputStream);
|
||||
} catch (IOException e) {
|
||||
return null;
|
||||
|
@ -300,12 +264,7 @@ public class PdfViewer extends AppCompatActivity implements LoaderManager.Loader
|
|||
Log.d(TAG, "path " + path);
|
||||
|
||||
if ("/placeholder.pdf".equals(path)) {
|
||||
maybeCloseInputStream();
|
||||
try {
|
||||
mInputStream = getContentResolver().openInputStream(mUri);
|
||||
} catch (FileNotFoundException ignored) {
|
||||
snackbar.setText(R.string.error_while_opening).show();
|
||||
}
|
||||
mInputStream.reset();
|
||||
return new WebResourceResponse("application/pdf", null, mInputStream);
|
||||
}
|
||||
|
||||
|
@ -338,28 +297,25 @@ public class PdfViewer extends AppCompatActivity implements LoaderManager.Loader
|
|||
@Override
|
||||
public void onPageFinished(WebView view, String url) {
|
||||
mDocumentState = STATE_LOADED;
|
||||
invalidateOptionsMenu();
|
||||
activity.invalidateOptionsMenu();
|
||||
loadPdfWithPassword(mEncryptedDocumentPassword);
|
||||
}
|
||||
});
|
||||
|
||||
GestureHelper.attach(PdfViewer.this, binding.webview,
|
||||
GestureHelper.attach(activity, binding.webview,
|
||||
new GestureHelper.GestureListener() {
|
||||
@Override
|
||||
public boolean onTapUp() {
|
||||
if (mUri != null) {
|
||||
binding.webview.evaluateJavascript("isTextSelected()", selection -> {
|
||||
if (!Boolean.parseBoolean(selection)) {
|
||||
if (getSupportActionBar().isShowing()) {
|
||||
hideSystemUi();
|
||||
} else {
|
||||
showSystemUi();
|
||||
}
|
||||
binding.webview.evaluateJavascript("isTextSelected()", selection -> {
|
||||
if (!Boolean.parseBoolean(selection)) {
|
||||
if (activity.getSupportActionBar().isShowing()) {
|
||||
hideSystemUi();
|
||||
} else {
|
||||
showSystemUi();
|
||||
}
|
||||
});
|
||||
return true;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
});
|
||||
return true;
|
||||
}
|
||||
|
||||
@Override
|
||||
|
@ -378,56 +334,14 @@ public class PdfViewer extends AppCompatActivity implements LoaderManager.Loader
|
|||
}
|
||||
});
|
||||
|
||||
mTextView = new TextView(this);
|
||||
mTextView = new TextView(activity);
|
||||
mTextView.setBackgroundColor(Color.DKGRAY);
|
||||
mTextView.setTextColor(ColorStateList.valueOf(Color.WHITE));
|
||||
mTextView.setTextSize(18);
|
||||
mTextView.setPadding(PADDING, 0, PADDING, 0);
|
||||
|
||||
// If loaders are not being initialized in onCreate(), the result will not be delivered
|
||||
// after orientation change (See FragmentHostCallback), thus initialize the
|
||||
// loader manager impl so that the result will be delivered.
|
||||
LoaderManager.getInstance(this);
|
||||
|
||||
snackbar = Snackbar.make(binding.getRoot(), "", Snackbar.LENGTH_LONG);
|
||||
|
||||
final Intent intent = getIntent();
|
||||
if (Intent.ACTION_VIEW.equals(intent.getAction())) {
|
||||
if (!"application/pdf".equals(intent.getType())) {
|
||||
snackbar.setText(R.string.invalid_mime_type).show();
|
||||
return;
|
||||
}
|
||||
mUri = intent.getData();
|
||||
mPage = 1;
|
||||
}
|
||||
|
||||
if (savedInstanceState != null) {
|
||||
if (Build.VERSION.SDK_INT < Build.VERSION_CODES.TIRAMISU) {
|
||||
@SuppressWarnings("deprecation")
|
||||
final Uri uri = savedInstanceState.getParcelable(STATE_URI);
|
||||
mUri = uri;
|
||||
} else {
|
||||
mUri = savedInstanceState.getParcelable(STATE_URI, Uri.class);
|
||||
}
|
||||
mPage = savedInstanceState.getInt(STATE_PAGE);
|
||||
mZoomRatio = savedInstanceState.getFloat(STATE_ZOOM_RATIO);
|
||||
mDocumentOrientationDegrees = savedInstanceState.getInt(STATE_DOCUMENT_ORIENTATION_DEGREES);
|
||||
mEncryptedDocumentPassword = savedInstanceState.getString(STATE_ENCRYPTED_DOCUMENT_PASSWORD);
|
||||
}
|
||||
|
||||
if (mUri != null) {
|
||||
if ("file".equals(mUri.getScheme())) {
|
||||
snackbar.setText(R.string.legacy_file_uri).show();
|
||||
return;
|
||||
}
|
||||
|
||||
loadPdf();
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void onDestroy() {
|
||||
super.onDestroy();
|
||||
public void onDestroy() {
|
||||
binding.webview.removeJavascriptInterface("channel");
|
||||
binding.getRoot().removeView(binding.webview);
|
||||
binding.webview.destroy();
|
||||
|
@ -447,11 +361,11 @@ public class PdfViewer extends AppCompatActivity implements LoaderManager.Loader
|
|||
|
||||
private PasswordPromptFragment getPasswordPromptFragment() {
|
||||
if (mPasswordPromptFragment == null) {
|
||||
final Fragment fragment = getSupportFragmentManager().findFragmentByTag(PasswordPromptFragment.class.getName());
|
||||
final Fragment fragment = activity.getSupportFragmentManager().findFragmentByTag(PasswordPromptFragment.class.getName());
|
||||
if (fragment != null) {
|
||||
mPasswordPromptFragment = (PasswordPromptFragment) fragment;
|
||||
} else {
|
||||
mPasswordPromptFragment = new PasswordPromptFragment();
|
||||
mPasswordPromptFragment = new PasswordPromptFragment(this);
|
||||
}
|
||||
}
|
||||
return mPasswordPromptFragment;
|
||||
|
@ -460,28 +374,28 @@ public class PdfViewer extends AppCompatActivity implements LoaderManager.Loader
|
|||
private void setToolbarTitleWithDocumentName() {
|
||||
String documentName = getCurrentDocumentName();
|
||||
if (documentName != null && !documentName.isEmpty()) {
|
||||
getSupportActionBar().setTitle(documentName);
|
||||
activity.getSupportActionBar().setTitle(documentName);
|
||||
} else {
|
||||
getSupportActionBar().setTitle(R.string.app_name);
|
||||
activity.getSupportActionBar().setTitle(R.string.app_name);
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void onResume() {
|
||||
super.onResume();
|
||||
|
||||
public void onResume() {
|
||||
// The user could have left the activity to update the WebView
|
||||
invalidateOptionsMenu();
|
||||
if (getWebViewRelease() >= MIN_WEBVIEW_RELEASE) {
|
||||
binding.webviewOutOfDateLayout.setVisibility(View.GONE);
|
||||
binding.webview.setVisibility(View.VISIBLE);
|
||||
} else {
|
||||
binding.webview.setVisibility(View.GONE);
|
||||
binding.webviewOutOfDateMessage.setText(getString(R.string.webview_out_of_date_message, getWebViewRelease(), MIN_WEBVIEW_RELEASE));
|
||||
binding.webviewOutOfDateLayout.setVisibility(View.VISIBLE);
|
||||
activity.invalidateOptionsMenu();
|
||||
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) {
|
||||
if (getWebViewRelease() >= MIN_WEBVIEW_RELEASE) {
|
||||
binding.webviewOutOfDateLayout.setVisibility(View.GONE);
|
||||
binding.webview.setVisibility(View.VISIBLE);
|
||||
} else {
|
||||
binding.webview.setVisibility(View.GONE);
|
||||
binding.webviewOutOfDateMessage.setText(activity.getString(R.string.webview_out_of_date_message, getWebViewRelease(), MIN_WEBVIEW_RELEASE));
|
||||
binding.webviewOutOfDateLayout.setVisibility(View.VISIBLE);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@RequiresApi(api = Build.VERSION_CODES.O)
|
||||
private int getWebViewRelease() {
|
||||
PackageInfo webViewPackage = WebView.getCurrentWebViewPackage();
|
||||
String webViewVersionName = webViewPackage.versionName;
|
||||
|
@ -491,14 +405,14 @@ public class PdfViewer extends AppCompatActivity implements LoaderManager.Loader
|
|||
@NonNull
|
||||
@Override
|
||||
public Loader<List<CharSequence>> onCreateLoader(int id, Bundle args) {
|
||||
return new DocumentPropertiesAsyncTaskLoader(this, args.getString(KEY_PROPERTIES), mNumPages, mUri);
|
||||
return new DocumentPropertiesAsyncTaskLoader(activity, args.getString(KEY_PROPERTIES), mNumPages, fileName, fileSize);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onLoadFinished(@NonNull Loader<List<CharSequence>> loader, List<CharSequence> data) {
|
||||
mDocumentProperties = data;
|
||||
setToolbarTitleWithDocumentName();
|
||||
LoaderManager.getInstance(this).destroyLoader(DocumentPropertiesAsyncTaskLoader.ID);
|
||||
LoaderManager.getInstance(activity).destroyLoader(DocumentPropertiesAsyncTaskLoader.ID);
|
||||
}
|
||||
|
||||
@Override
|
||||
|
@ -506,20 +420,14 @@ public class PdfViewer extends AppCompatActivity implements LoaderManager.Loader
|
|||
mDocumentProperties = null;
|
||||
}
|
||||
|
||||
private void loadPdf() {
|
||||
try {
|
||||
if (mInputStream != null) {
|
||||
mInputStream.close();
|
||||
}
|
||||
mInputStream = getContentResolver().openInputStream(mUri);
|
||||
} catch (IOException e) {
|
||||
snackbar.setText(R.string.error_while_opening).show();
|
||||
return;
|
||||
}
|
||||
|
||||
mDocumentState = 0;
|
||||
public void loadPdf(ByteArrayInputStream inputStream, String fileName, Long fileSize) {
|
||||
mPage = 1;
|
||||
mDocumentProperties = null;
|
||||
mInputStream = inputStream;
|
||||
this.fileName = fileName;
|
||||
this.fileSize = fileSize;
|
||||
showSystemUi();
|
||||
invalidateOptionsMenu();
|
||||
activity.invalidateOptionsMenu();
|
||||
binding.webview.loadUrl("https://localhost/viewer.html");
|
||||
}
|
||||
|
||||
|
@ -540,30 +448,11 @@ public class PdfViewer extends AppCompatActivity implements LoaderManager.Loader
|
|||
renderPage(0);
|
||||
}
|
||||
|
||||
private void openDocument() {
|
||||
Intent intent = new Intent(Intent.ACTION_OPEN_DOCUMENT);
|
||||
intent.addCategory(Intent.CATEGORY_OPENABLE);
|
||||
intent.setType("application/pdf");
|
||||
openDocumentLauncher.launch(intent);
|
||||
}
|
||||
|
||||
private void shareDocument() {
|
||||
if (mUri != null) {
|
||||
Intent shareIntent = new Intent(Intent.ACTION_SEND);
|
||||
shareIntent.setDataAndTypeAndNormalize(mUri, "application/pdf");
|
||||
shareIntent.putExtra(Intent.EXTRA_STREAM, mUri);
|
||||
shareIntent.addFlags(Intent.FLAG_GRANT_READ_URI_PERMISSION);
|
||||
startActivity(Intent.createChooser(shareIntent, getString(R.string.action_share)));
|
||||
} else {
|
||||
Log.w(TAG, "Cannot share unexpected null URI");
|
||||
}
|
||||
}
|
||||
|
||||
private void zoomIn(float value, boolean end) {
|
||||
if (mZoomRatio < MAX_ZOOM_RATIO) {
|
||||
mZoomRatio = Math.min(mZoomRatio + value, MAX_ZOOM_RATIO);
|
||||
renderPage(end ? 1 : 2);
|
||||
invalidateOptionsMenu();
|
||||
activity.invalidateOptionsMenu();
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -571,7 +460,7 @@ public class PdfViewer extends AppCompatActivity implements LoaderManager.Loader
|
|||
if (mZoomRatio > MIN_ZOOM_RATIO) {
|
||||
mZoomRatio = Math.max(mZoomRatio - value, MIN_ZOOM_RATIO);
|
||||
renderPage(end ? 1 : 2);
|
||||
invalidateOptionsMenu();
|
||||
activity.invalidateOptionsMenu();
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -594,28 +483,18 @@ public class PdfViewer extends AppCompatActivity implements LoaderManager.Loader
|
|||
mPage = selected_page;
|
||||
renderPage(0);
|
||||
showPageNumber();
|
||||
invalidateOptionsMenu();
|
||||
activity.invalidateOptionsMenu();
|
||||
}
|
||||
}
|
||||
|
||||
private void showSystemUi() {
|
||||
ViewKt.showSystemUi(binding.getRoot(), getWindow());
|
||||
getSupportActionBar().show();
|
||||
ViewKt.showSystemUi(binding.getRoot(), activity.getWindow());
|
||||
activity.getSupportActionBar().show();
|
||||
}
|
||||
|
||||
private void hideSystemUi() {
|
||||
ViewKt.hideSystemUi(binding.getRoot(), getWindow());
|
||||
getSupportActionBar().hide();
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onSaveInstanceState(@NonNull Bundle savedInstanceState) {
|
||||
super.onSaveInstanceState(savedInstanceState);
|
||||
savedInstanceState.putParcelable(STATE_URI, mUri);
|
||||
savedInstanceState.putInt(STATE_PAGE, mPage);
|
||||
savedInstanceState.putFloat(STATE_ZOOM_RATIO, mZoomRatio);
|
||||
savedInstanceState.putInt(STATE_DOCUMENT_ORIENTATION_DEGREES, mDocumentOrientationDegrees);
|
||||
savedInstanceState.putString(STATE_ENCRYPTED_DOCUMENT_PASSWORD, mEncryptedDocumentPassword);
|
||||
ViewKt.hideSystemUi(binding.getRoot(), activity.getWindow());
|
||||
activity.getSupportActionBar().hide();
|
||||
}
|
||||
|
||||
private void showPageNumber() {
|
||||
|
@ -623,30 +502,26 @@ public class PdfViewer extends AppCompatActivity implements LoaderManager.Loader
|
|||
mToast.cancel();
|
||||
}
|
||||
mTextView.setText(String.format("%s/%s", mPage, mNumPages));
|
||||
mToast = new Toast(getApplicationContext());
|
||||
mToast = new Toast(activity);
|
||||
mToast.setGravity(Gravity.BOTTOM | Gravity.END, PADDING, PADDING);
|
||||
mToast.setDuration(Toast.LENGTH_SHORT);
|
||||
mToast.setView(mTextView);
|
||||
mToast.show();
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean onCreateOptionsMenu(@NonNull Menu menu) {
|
||||
super.onCreateOptionsMenu(menu);
|
||||
MenuInflater inflater = getMenuInflater();
|
||||
public void onCreateOptionMenu(@NonNull Menu menu) {
|
||||
MenuInflater inflater = activity.getMenuInflater();
|
||||
inflater.inflate(R.menu.pdf_viewer, menu);
|
||||
if (BuildConfig.DEBUG) {
|
||||
inflater.inflate(R.menu.pdf_viewer_debug, menu);
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean onPrepareOptionsMenu(@NonNull Menu menu) {
|
||||
final ArrayList<Integer> ids = new ArrayList<>(Arrays.asList(R.id.action_jump_to_page,
|
||||
R.id.action_next, R.id.action_previous, R.id.action_first, R.id.action_last,
|
||||
R.id.action_rotate_clockwise, R.id.action_rotate_counterclockwise,
|
||||
R.id.action_view_document_properties, R.id.action_share, R.id.action_save_as));
|
||||
R.id.action_view_document_properties));
|
||||
if (BuildConfig.DEBUG) {
|
||||
ids.add(R.id.debug_action_toggle_text_layer_visibility);
|
||||
}
|
||||
|
@ -667,16 +542,12 @@ public class PdfViewer extends AppCompatActivity implements LoaderManager.Loader
|
|||
mDocumentState = STATE_END;
|
||||
}
|
||||
|
||||
enableDisableMenuItem(menu.findItem(R.id.action_open), getWebViewRelease() >= MIN_WEBVIEW_RELEASE);
|
||||
enableDisableMenuItem(menu.findItem(R.id.action_share), mUri != null);
|
||||
enableDisableMenuItem(menu.findItem(R.id.action_next), mPage < mNumPages);
|
||||
enableDisableMenuItem(menu.findItem(R.id.action_previous), mPage > 1);
|
||||
enableDisableMenuItem(menu.findItem(R.id.action_save_as), mUri != null);
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean onOptionsItemSelected(MenuItem item) {
|
||||
final int itemId = item.getItemId();
|
||||
if (itemId == R.id.action_previous) {
|
||||
|
@ -691,9 +562,6 @@ public class PdfViewer extends AppCompatActivity implements LoaderManager.Loader
|
|||
} else if (itemId == R.id.action_last) {
|
||||
onJumpToPageInDocument(mNumPages);
|
||||
return true;
|
||||
} else if (itemId == R.id.action_open) {
|
||||
openDocument();
|
||||
return true;
|
||||
} else if (itemId == R.id.action_rotate_clockwise) {
|
||||
documentOrientationChanged(90);
|
||||
return true;
|
||||
|
@ -703,31 +571,18 @@ public class PdfViewer extends AppCompatActivity implements LoaderManager.Loader
|
|||
} else if (itemId == R.id.action_view_document_properties) {
|
||||
DocumentPropertiesFragment
|
||||
.newInstance(mDocumentProperties)
|
||||
.show(getSupportFragmentManager(), DocumentPropertiesFragment.TAG);
|
||||
.show(activity.getSupportFragmentManager(), DocumentPropertiesFragment.TAG);
|
||||
return true;
|
||||
} else if (itemId == R.id.action_jump_to_page) {
|
||||
new JumpToPageFragment()
|
||||
.show(getSupportFragmentManager(), JumpToPageFragment.TAG);
|
||||
new JumpToPageFragment(this)
|
||||
.show(activity.getSupportFragmentManager(), JumpToPageFragment.TAG);
|
||||
return true;
|
||||
} else if (itemId == R.id.action_share) {
|
||||
shareDocument();
|
||||
return true;
|
||||
} else if (itemId == R.id.action_save_as) {
|
||||
saveDocument();
|
||||
} else if (itemId == R.id.debug_action_toggle_text_layer_visibility) {
|
||||
binding.webview.evaluateJavascript("toggleTextLayerVisibility()", null);
|
||||
return true;
|
||||
}
|
||||
|
||||
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);
|
||||
return false;
|
||||
}
|
||||
|
||||
private String getCurrentDocumentName() {
|
||||
|
@ -744,12 +599,4 @@ public class PdfViewer extends AppCompatActivity implements LoaderManager.Loader
|
|||
}
|
||||
return fileName.length() > 2 ? fileName : title;
|
||||
}
|
||||
|
||||
private void saveDocumentAs(Uri uri) {
|
||||
try {
|
||||
KtUtilsKt.saveAs(this, mUri, uri);
|
||||
} catch (IOException | OutOfMemoryError | IllegalArgumentException e) {
|
||||
snackbar.setText(R.string.error_while_saving).show();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -10,7 +10,7 @@ import androidx.fragment.app.DialogFragment
|
|||
import app.grapheneos.pdfviewer.PdfViewer
|
||||
import com.google.android.material.dialog.MaterialAlertDialogBuilder
|
||||
|
||||
class JumpToPageFragment : DialogFragment() {
|
||||
class JumpToPageFragment(private val pdfViewer: PdfViewer) : DialogFragment() {
|
||||
|
||||
companion object {
|
||||
const val TAG = "JumpToPageFragment"
|
||||
|
@ -23,16 +23,14 @@ class JumpToPageFragment : DialogFragment() {
|
|||
|
||||
override fun onCreateDialog(savedInstanceState: Bundle?): Dialog {
|
||||
|
||||
val viewerActivity: PdfViewer = (requireActivity() as PdfViewer)
|
||||
|
||||
if (savedInstanceState != null) {
|
||||
mPicker.minValue = savedInstanceState.getInt(STATE_PICKER_MIN)
|
||||
mPicker.maxValue = savedInstanceState.getInt(STATE_PICKER_MAX)
|
||||
mPicker.value = savedInstanceState.getInt(STATE_PICKER_CUR)
|
||||
} else {
|
||||
mPicker.minValue = 1
|
||||
mPicker.maxValue = viewerActivity.mNumPages
|
||||
mPicker.value = viewerActivity.mPage
|
||||
mPicker.maxValue = pdfViewer.mNumPages
|
||||
mPicker.value = pdfViewer.mPage
|
||||
}
|
||||
val layout = FrameLayout(requireActivity())
|
||||
layout.addView(
|
||||
|
@ -46,7 +44,7 @@ class JumpToPageFragment : DialogFragment() {
|
|||
.setView(layout)
|
||||
.setPositiveButton(android.R.string.ok) { _: DialogInterface?, _: Int ->
|
||||
mPicker.clearFocus()
|
||||
viewerActivity.onJumpToPageInDocument(mPicker.value)
|
||||
pdfViewer.onJumpToPageInDocument(mPicker.value)
|
||||
}
|
||||
.setNegativeButton(android.R.string.cancel, null)
|
||||
.create()
|
||||
|
|
|
@ -9,6 +9,7 @@ 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
|
||||
|
@ -19,7 +20,7 @@ import com.google.android.material.dialog.MaterialAlertDialogBuilder
|
|||
import com.google.android.material.textfield.TextInputEditText
|
||||
import com.google.android.material.textfield.TextInputLayout
|
||||
|
||||
class PasswordPromptFragment : DialogFragment() {
|
||||
class PasswordPromptFragment(private val pdfViewer: PdfViewer) : DialogFragment() {
|
||||
|
||||
private lateinit var passwordLayout : TextInputLayout
|
||||
private lateinit var passwordEditText : TextInputEditText
|
||||
|
@ -50,7 +51,7 @@ class PasswordPromptFragment : DialogFragment() {
|
|||
isCancelable = false
|
||||
dialog.setCanceledOnTouchOutside(false)
|
||||
dialog.window?.setSoftInputMode(WindowManager.LayoutParams.SOFT_INPUT_STATE_VISIBLE)
|
||||
(requireActivity() as PdfViewer).passwordValidationViewModel.status.observe(
|
||||
pdfViewer.passwordValidationViewModel.status.observe(
|
||||
this
|
||||
) {
|
||||
when (it) {
|
||||
|
@ -83,7 +84,7 @@ class PasswordPromptFragment : DialogFragment() {
|
|||
private fun sendPassword() {
|
||||
val password = passwordEditText.text.toString()
|
||||
if (!TextUtils.isEmpty(password)) {
|
||||
(activity as PdfViewer).loadPdfWithPassword(password)
|
||||
pdfViewer.loadPdfWithPassword(password)
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
@ -1,7 +1,6 @@
|
|||
package app.grapheneos.pdfviewer.loader;
|
||||
|
||||
import android.content.Context;
|
||||
import android.net.Uri;
|
||||
|
||||
import androidx.annotation.Nullable;
|
||||
import androidx.loader.content.AsyncTaskLoader;
|
||||
|
@ -16,14 +15,17 @@ public class DocumentPropertiesAsyncTaskLoader extends AsyncTaskLoader<List<Char
|
|||
|
||||
private final String mProperties;
|
||||
private final int mNumPages;
|
||||
private final Uri mUri;
|
||||
|
||||
public DocumentPropertiesAsyncTaskLoader(Context context, String properties, int numPages, Uri uri) {
|
||||
String fileName;
|
||||
Long fileSize;
|
||||
|
||||
public DocumentPropertiesAsyncTaskLoader(Context context, String properties, int numPages, String fileName, Long fileSize) {
|
||||
super(context);
|
||||
|
||||
mProperties = properties;
|
||||
mNumPages = numPages;
|
||||
mUri = uri;
|
||||
this.fileName = fileName;
|
||||
this.fileSize = fileSize;
|
||||
}
|
||||
|
||||
|
||||
|
@ -40,7 +42,8 @@ public class DocumentPropertiesAsyncTaskLoader extends AsyncTaskLoader<List<Char
|
|||
getContext(),
|
||||
mProperties,
|
||||
mNumPages,
|
||||
mUri
|
||||
fileName,
|
||||
fileSize
|
||||
);
|
||||
|
||||
return loader.loadAsList();
|
||||
|
|
|
@ -2,14 +2,11 @@ package app.grapheneos.pdfviewer.loader
|
|||
|
||||
import android.content.Context
|
||||
import android.graphics.Typeface
|
||||
import android.net.Uri
|
||||
import android.provider.OpenableColumns
|
||||
import android.text.SpannableStringBuilder
|
||||
import android.text.Spanned
|
||||
import android.text.format.Formatter
|
||||
import android.text.style.StyleSpan
|
||||
import android.util.Log
|
||||
import androidx.core.database.getLongOrNull
|
||||
import app.grapheneos.pdfviewer.R
|
||||
import org.json.JSONException
|
||||
|
||||
|
@ -17,7 +14,8 @@ class DocumentPropertiesLoader(
|
|||
private val context: Context,
|
||||
private val properties: String,
|
||||
private val numPages: Int,
|
||||
private val mUri: Uri
|
||||
private val fileName: String,
|
||||
private val fileSize: Long,
|
||||
) {
|
||||
|
||||
fun loadAsList(): List<CharSequence> {
|
||||
|
@ -83,31 +81,8 @@ class DocumentPropertiesLoader(
|
|||
|
||||
private fun getFileProperties(): Map<DocumentProperty, String> {
|
||||
val collections = mutableMapOf<DocumentProperty, String>()
|
||||
val proj = arrayOf(
|
||||
OpenableColumns.DISPLAY_NAME,
|
||||
OpenableColumns.SIZE
|
||||
)
|
||||
|
||||
context.contentResolver.query(
|
||||
mUri,
|
||||
proj,
|
||||
null,
|
||||
null
|
||||
)?.use { cursor ->
|
||||
cursor.moveToFirst()
|
||||
val indexName: Int = cursor.getColumnIndex(OpenableColumns.DISPLAY_NAME)
|
||||
|
||||
if (indexName >= 0) {
|
||||
collections[DocumentProperty.FileName] = cursor.getString(indexName)
|
||||
}
|
||||
|
||||
val indexSize: Int = cursor.getColumnIndex(OpenableColumns.SIZE)
|
||||
if (indexSize >= 0) {
|
||||
val fileSize = cursor.getLongOrNull(indexSize)
|
||||
collections[DocumentProperty.FileSize] =
|
||||
fileSize?.let { Formatter.formatFileSize(context, it) } ?: context.getString(R.string.unknown_file_size)
|
||||
}
|
||||
}
|
||||
collections[DocumentProperty.FileName] = fileName
|
||||
collections[DocumentProperty.FileSize] = Formatter.formatFileSize(context, fileSize)
|
||||
return collections
|
||||
}
|
||||
}
|
||||
|
|
|
@ -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,30 +0,0 @@
|
|||
<!--
|
||||
Copyright (C) 2021 Google Inc.
|
||||
|
||||
Licensed under the Apache License, Version 2.0 (the "License");
|
||||
you may not use this file except in compliance with the License.
|
||||
You may obtain a copy of the License at
|
||||
|
||||
http://www.apache.org/licenses/LICENSE-2.0
|
||||
|
||||
Unless required by applicable law or agreed to in writing, software
|
||||
distributed under the License is distributed on an "AS IS" BASIS,
|
||||
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
See the License for the specific language governing permissions and
|
||||
limitations under the License.
|
||||
-->
|
||||
<vector xmlns:android="http://schemas.android.com/apk/res/android"
|
||||
android:width="108dp"
|
||||
android:height="108dp"
|
||||
android:viewportWidth="108"
|
||||
android:viewportHeight="108">
|
||||
<group
|
||||
android:scaleX="2.43375"
|
||||
android:scaleY="2.43375"
|
||||
android:translateX="24.795"
|
||||
android:translateY="24.795">
|
||||
<path
|
||||
android:fillColor="#000000"
|
||||
android:pathData="M18,4L6,4C4.9,4 4,4.9 4,6v12c0,1.1 0.9,2 2,2h12c1.1,0 2,-0.9 2,-2L20,6C20,4.9 19.1,4 18,4ZM9.5,11.5C9.5,12.33 8.83,13 8,13L7,13v1.25C7,14.66 6.66,15 6.25,15 5.84,15 5.5,14.66 5.5,14.25L5.5,10c0,-0.55 0.45,-1 1,-1L8,9c0.83,0 1.5,0.67 1.5,1.5zM14.5,13.5c0,0.83 -0.67,1.5 -1.5,1.5h-2c-0.28,0 -0.5,-0.22 -0.5,-0.5v-5C10.5,9.22 10.72,9 11,9h2c0.83,0 1.5,0.67 1.5,1.5zM18.5,9.75c0,0.41 -0.34,0.75 -0.75,0.75L17,10.5v1h0.75c0.41,0 0.75,0.34 0.75,0.75 0,0.41 -0.34,0.75 -0.75,0.75L17,13v1.25C17,14.66 16.66,15 16.25,15 15.84,15 15.5,14.66 15.5,14.25L15.5,10c0,-0.55 0.45,-1 1,-1h1.25c0.41,0 0.75,0.34 0.75,0.75zM7,11.5h1v-1L7,10.5ZM12,13.5h1v-3h-1z" />
|
||||
</group>
|
||||
</vector>
|
|
@ -1,10 +0,0 @@
|
|||
<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,10 +0,0 @@
|
|||
<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="M18,16.08c-0.76,0 -1.44,0.3 -1.96,0.77L8.91,12.7c0.05,-0.23 0.09,-0.46 0.09,-0.7s-0.04,-0.47 -0.09,-0.7l7.05,-4.11c0.54,0.5 1.25,0.81 2.04,0.81 1.66,0 3,-1.34 3,-3s-1.34,-3 -3,-3 -3,1.34 -3,3c0,0.24 0.04,0.47 0.09,0.7L8.04,9.81C7.5,9.31 6.79,9 6,9c-1.66,0 -3,1.34 -3,3s1.34,3 3,3c0.79,0 1.5,-0.31 2.04,-0.81l7.12,4.16c-0.05,0.21 -0.08,0.43 -0.08,0.65 0,1.61 1.31,2.92 2.92,2.92 1.61,0 2.92,-1.31 2.92,-2.92s-1.31,-2.92 -2.92,-2.92z"/>
|
||||
</vector>
|
|
@ -19,12 +19,6 @@
|
|||
android:title="@string/action_next"
|
||||
app:showAsAction="always" />
|
||||
|
||||
<item
|
||||
android:id="@+id/action_open"
|
||||
android:icon="@drawable/ic_insert_drive_file_24dp"
|
||||
android:title="@string/action_open"
|
||||
app:showAsAction="ifRoom" />
|
||||
|
||||
<item
|
||||
android:id="@+id/action_first"
|
||||
android:icon="@drawable/ic_first_page_24dp"
|
||||
|
@ -55,18 +49,6 @@
|
|||
android:title="@string/action_rotate_counterclockwise"
|
||||
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
|
||||
android:id="@+id/action_view_document_properties"
|
||||
android:title="@string/action_view_document_properties"
|
||||
|
|
|
@ -1,6 +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"/>
|
||||
<monochrome android:drawable="@drawable/ic_launcher_foreground" />
|
||||
</adaptive-icon>
|
|
@ -1,3 +0,0 @@
|
|||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<resources xmlns:tools="http://schemas.android.com/tools"
|
||||
tools:shrinkMode="strict" />
|
|
@ -1,4 +0,0 @@
|
|||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<resources>
|
||||
<color name="ic_launcher_background">#FFFFFF</color>
|
||||
</resources>
|
|
@ -4,14 +4,11 @@
|
|||
<!--<string name="action_settings">Settings</string>-->
|
||||
<string name="action_previous">Previous page</string>
|
||||
<string name="action_next">Next page</string>
|
||||
<string name="action_open">Open document</string>
|
||||
<string name="action_first">First page</string>
|
||||
<string name="action_last">Last page</string>
|
||||
<string name="action_jump_to_page">Jump to page</string>
|
||||
<string name="action_rotate_clockwise">Rotate clockwise</string>
|
||||
<string name="action_rotate_counterclockwise">Rotate counterclockwise</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="debug_action_toggle_text_layer_visibility">Toggle text layer visibility</string>
|
||||
|
@ -19,11 +16,6 @@
|
|||
<string name="document_properties_invalid_date">Invalid date</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="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_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>
|
||||
|
||||
|
@ -44,6 +36,4 @@
|
|||
<string name="creator">Creator</string>
|
||||
<string name="pdf_version">PDF version</string>
|
||||
<string name="pages">Pages</string>
|
||||
|
||||
<string name="unknown_file_size">Unknown</string>
|
||||
</resources>
|
||||
|
|
|
@ -1,6 +1,6 @@
|
|||
plugins {
|
||||
id("com.android.application") version "8.0.2" apply false
|
||||
id("org.jetbrains.kotlin.android") version "1.8.22" apply false
|
||||
id("com.android.application") apply false
|
||||
id("org.jetbrains.kotlin.android") apply false
|
||||
}
|
||||
|
||||
allprojects {
|
||||
|
|
File diff suppressed because it is too large
Load Diff
|
@ -1,8 +0,0 @@
|
|||
{
|
||||
"dependencies": {
|
||||
"pdfjs-dist": "3.8.162"
|
||||
},
|
||||
"devDependencies": {
|
||||
"eslint": "^8.44.0"
|
||||
}
|
||||
}
|
Loading…
Reference in New Issue