Compare commits

...

158 Commits

Author SHA1 Message Date
Matéo Duparc 2296593275
Delete pdfjs-dist submodule 2023-09-09 17:25:05 +02:00
Matéo Duparc 59973a6b42
libpdfviewer: custom README 2023-09-08 19:48:14 +02:00
Matéo Duparc 476f4aa685
libpdfviewer: update to PdfViewer 17 2023-09-08 19:47:30 +02:00
Daniel Micay c7241646fe increment version to 17 2023-07-30 13:33:19 -04:00
Daniel Micay 516d99bb13 update Android Gradle plugin to 8.0.2 2023-07-30 13:33:15 -04:00
Daniel Micay 5afd670354 update Gradle to 8.2 2023-07-03 19:28:04 -04:00
Hanouta 20d6c4ea9f decrease minimum zoom ratio to 0.2 2023-07-03 19:21:54 -04:00
Hanouta 0060e2cb73 set zoomRatio to fit document to screen size 2023-07-03 19:21:29 -04:00
Daniel Micay a820314362 update Material library to 1.9.0 2023-07-03 19:12:00 -04:00
Daniel Micay e064995a1b update kotlin to 1.8.22 2023-07-03 19:09:20 -04:00
Daniel Micay 0f520dd517 update npm dependencies for build 2023-07-03 19:07:09 -04:00
dependabot[bot] e9c5deb95d Bump pdfjs-dist from 3.7.107 to 3.8.162
Bumps [pdfjs-dist](https://github.com/mozilla/pdfjs-dist) from 3.7.107 to 3.8.162.
- [Commits](https://github.com/mozilla/pdfjs-dist/commits)

---
updated-dependencies:
- dependency-name: pdfjs-dist
  dependency-type: direct:production
  update-type: version-update:semver-minor
...

Signed-off-by: dependabot[bot] <support@github.com>
2023-07-03 19:01:17 -04:00
octocorvus 457cbc9e98 update MIN_WEBVIEW_RELEASE to 92
This is the minimum supported Chromium version for PDF.js v3.8.162.
2023-07-03 19:01:17 -04:00
dependabot[bot] efc1a27203 Bump eslint from 8.43.0 to 8.44.0
Bumps [eslint](https://github.com/eslint/eslint) from 8.43.0 to 8.44.0.
- [Release notes](https://github.com/eslint/eslint/releases)
- [Changelog](https://github.com/eslint/eslint/blob/main/CHANGELOG.md)
- [Commits](https://github.com/eslint/eslint/compare/v8.43.0...v8.44.0)

---
updated-dependencies:
- dependency-name: eslint
  dependency-type: direct:development
  update-type: version-update:semver-minor
...

Signed-off-by: dependabot[bot] <support@github.com>
2023-07-03 17:41:56 -04:00
octocorvus 123bcf30a8 make DocumentPropertiesLoader#load private 2023-06-28 00:12:28 -04:00
octocorvus bfb5e4a538 fix file size parsing issue causing crash
Opening a PDF using Storage Access Framework (SAF) causes the app to
crash in some cases, when the file is not present on the device's
internal storage (for example, on Proton Drive).
2023-06-28 00:12:28 -04:00
dependabot[bot] 145fd9896c Bump eslint from 8.42.0 to 8.43.0
Bumps [eslint](https://github.com/eslint/eslint) from 8.42.0 to 8.43.0.
- [Release notes](https://github.com/eslint/eslint/releases)
- [Changelog](https://github.com/eslint/eslint/blob/main/CHANGELOG.md)
- [Commits](https://github.com/eslint/eslint/compare/v8.42.0...v8.43.0)

---
updated-dependencies:
- dependency-name: eslint
  dependency-type: direct:development
  update-type: version-update:semver-minor
...

Signed-off-by: dependabot[bot] <support@github.com>
2023-06-16 18:23:52 -04:00
octocorvus 6f40b25b31 add eslint config and missing semicolons 2023-06-09 11:33:53 -04:00
dependabot[bot] f6dd0b2a87 Bump pdfjs-dist from 3.5.141 to 3.7.107
Bumps [pdfjs-dist](https://github.com/mozilla/pdfjs-dist) from 3.5.141 to 3.7.107.
- [Commits](https://github.com/mozilla/pdfjs-dist/commits)

---
updated-dependencies:
- dependency-name: pdfjs-dist
  dependency-type: direct:production
  update-type: version-update:semver-minor
...

Signed-off-by: dependabot[bot] <support@github.com>
2023-06-08 08:50:52 +03:00
Daniel Micay 4de602be3a drop unnecessary com.android.library plugin 2023-05-03 12:59:30 -04:00
Patryk Mis 5d6c6aa4e5 Update AGP and Kotlin
Signed-off-by: Patryk Mis <foss@patrykmis.com>
2023-05-03 12:58:19 -04:00
Patryk Mis 3119c94d71 Update Gradle to v8.1.1
Signed-off-by: Patryk Mis <foss@patrykmis.com>
2023-05-03 12:58:19 -04:00
Patryk Mis 115ddcb164 Update Gradle build scripts
Signed-off-by: Patryk Mis <foss@patrykmis.com>
2023-05-03 12:58:19 -04:00
Daniel Micay 29a004091e minor whitespace fixes 2023-04-22 09:33:16 -04:00
Pratyush 195bba7891 replace custom file size parser with AOSP implementation 2023-04-20 11:18:14 -04:00
Pratyush 17c7c84296 rewrite DocumentPropertiesLoader in kotlin 2023-04-20 11:18:14 -04:00
Pratyush 61607858ef convert remaining fragments to kotlin 2023-04-20 11:18:11 -04:00
Matéo Duparc c74b374ec4
Restore correct pdfjs version 2023-04-18 16:30:51 +02:00
octocorvus fb59568765 update Android Gradle Plugin to 8.0.0
fix: update JDK to 17 for AGP 8.0
fix: enable buildConfig build feature
refactor: remove unnecessary android.enableR8.fullMode
2023-04-14 08:37:42 -04:00
Patryk Mis 4d1807718e Update Gradle to 8.1 2023-04-12 12:57:17 -04:00
Daniel Micay a6b4144a08 drop legacy GitHub Actions submodule checkout 2023-04-10 01:33:57 -04:00
Daniel Micay 5c8c4d7d83 update GitHub Actions JDK to 20 2023-04-10 01:33:27 -04:00
dependabot[bot] e161b71d22 Bump pdfjs-dist from 3.4.120 to 3.5.141
Bumps [pdfjs-dist](https://github.com/mozilla/pdfjs-dist) from 3.4.120 to 3.5.141.
- [Release notes](https://github.com/mozilla/pdfjs-dist/releases)
- [Commits](https://github.com/mozilla/pdfjs-dist/commits)

---
updated-dependencies:
- dependency-name: pdfjs-dist
  dependency-type: direct:production
  update-type: version-update:semver-minor
...

Signed-off-by: dependabot[bot] <support@github.com>
2023-04-09 10:40:16 -04:00
Daniel Micay 759417f4da update Kotlin to 1.8.20 2023-04-09 09:34:51 -04:00
octocorvus bb14ba1a25 bugfix: make text layer position absolute and use CSS transform to al...
...ign it to canvas

Fixes a bug where there was empty space above pdf page, when page
rotation is 90 or 270 degrees.

Testing: open pdf and try all rotations (0, 90, 180 and 270 degrees)
with various zoom levels (especially, max and min zoom levels), and make
sure that text layer is aligned properly to the canvas and there aren't
any unwanted blank spaces. Finally, repeat the same testing procedure
for landscape mode.

To check if text layer is aligned properly on debug builds, toggle text
layer visibility from menu or use chrome dev tools.
2023-04-09 09:32:58 -04:00
Daniel Micay a59e72d9e0 add setup script 2023-03-24 19:46:52 -04:00
Daniel Micay f2b0162630 remove empty .gitmodules 2023-03-24 19:46:11 -04:00
octocorvus ae1c0874ce add option to toggle text layer visibility on debug builds
This is to ease debugging text layer on debug builds.
2023-03-24 19:45:49 -04:00
octocorvus d445c48f3c code style changes
- js: use double quotes consistently
- js: use strict equality operator everywhere
- js: end all statements with semi colon
- css: remove redundant unit of measures
2023-03-24 19:45:49 -04:00
octocorvus 69696ae2a9 use CSS transform to rotate text layer
Newer pdf.js versions don't automatically rotate text layer based on
viewport's rotation, instead they now set the value of the attribute
"data-main-rotation" to value of rotation in degrees. So, we now apply
CSS transformation to the text layer depending on the value of that
attribute.
2023-03-24 19:45:49 -04:00
octocorvus 2dea11799c use --scale-factor variable to set text layer font size
Newer pdf.js versions requires this variable to be set equal to the
actual scale (zoom ratio), to properly set font size in text layer div.
2023-03-24 19:45:49 -04:00
octocorvus f5a1452a2d migrate away from deprecated APIs
- renderTextLayer: use textContentSource parameter instead of textContentStream
- renderTextLayer: pass a div for the container argument
2023-03-24 19:45:49 -04:00
octocorvus f87941ea22 update pdf.js to v3.4.120 2023-03-24 19:45:49 -04:00
octocorvus 371c9509f6 update GitHub workflow to install npm dependencies 2023-03-24 19:45:49 -04:00
octocorvus d01131d4c6 use dependabot for npm dependencies 2023-03-24 19:45:49 -04:00
octocorvus 2935bd4b27 migrate to pdfjs-dist npm package 2023-03-24 19:45:49 -04:00
Daniel Micay 30b8769ed2 update Gradle to 8.0.2 2023-03-10 13:40:06 -05:00
Daniel Micay f6127ca0e0 update Android Gradle plugin to 7.4.2 2023-03-10 13:40:02 -05:00
octocorvus 0c6dcd35a7 Revert "Update pdf.js to v3.2.146"
This reverts commit e34c727ff7.

Temporarily revert pdf.js update as versions equal to or greater
than this breaks our text layer rendering.
2023-02-27 10:56:06 -05:00
Daniel Micay 1e5988346c update Gradle to 8.0.1 2023-02-24 15:37:12 -05:00
amalgame21 8d44658c80 Always shows previous and next page button in any Display size 2023-02-19 10:01:40 -05:00
Daniel Micay 6db24ef23d update build tools version to 33.0.2 2023-02-11 05:36:29 -05:00
Daniel Micay deba479a69 update AndroidX appcompat library to 1.6.1 2023-02-08 14:45:45 -05:00
Daniel Micay 7703b1a175 update Kotlin to 1.8.10 2023-02-04 07:06:08 -05:00
Daniel Micay d0c95f5478 update copyright notice 2023-02-01 23:37:39 -05:00
Daniel Micay f60cd27e65 update Kotlin Gradle plugin to 1.8.0 2023-02-01 22:00:06 -05:00
Daniel Micay a25d0a4405 update Kotlin Gradle plugin to 1.8.0 2023-02-01 21:58:53 -05:00
Daniel Micay 424010af1c use Gradle toolchain support 2023-02-01 21:43:25 -05:00
Daniel Micay 284fc306fc update Material library to 1.8.0 2023-02-01 20:47:56 -05:00
Daniel Micay d78e15b1b1 update Android Gradle plugin to 7.4.1 2023-02-01 20:32:07 -05:00
Matéo Duparc 6e8fdb56a5
libpdfviewer: update to PdfViewer 16 2023-02-01 22:52:07 +01:00
Xsims c6fb2aceda Add document name in toolbar 2023-01-27 02:08:50 -05:00
Daniel Micay 7f59118453 update AndroidX appcompat library to 1.6.0 2023-01-13 19:54:54 -05:00
Daniel Micay 5b46b4c80e update Android Gradle plugin to 7.4.0 2023-01-13 19:54:29 -05:00
r3g_5z e34c727ff7 Update pdf.js to v3.2.146
Signed-off-by: r3g_5z <june@girlboss.ceo>
2023-01-09 01:20:41 -05:00
Daniel Micay 3e60cca098 add Gradle verification metadata 2023-01-09 01:11:30 -05:00
Daniel Micay 48910db7c6 silence spurious getParcelable deprecation warning 2022-12-28 12:15:34 -05:00
Daniel Micay fd510a3c19 avoid deprecated DialogFragment onActivityCreated 2022-12-28 12:08:22 -05:00
Daniel Micay b8dcc33696 remove legacy roundIcon attribute 2022-12-25 10:38:50 -05:00
Daniel Micay 45a0952296 drop non-working dependabot workaround 2022-12-25 10:14:16 -05:00
Patryk Mis aaa93a6f88 update build tools to 33.0.1 2022-12-20 04:31:08 -05:00
Daniel Micay 414d7fe3d1 update Gradle to 7.6 2022-12-09 08:48:17 -05:00
randomhydrosol 99c8c13fec update pdfjs to v3.1.81 2022-12-05 03:31:24 -05:00
loryeam 1cf1c7ec28 horizontally and vertically center canvas and text layer
Also fixes an issue where text layer is not exactly on top of the canvas when
canvas width is less than screen width (which happens often in landscape mode or
when fully zoomed out)
2022-12-05 03:20:08 -05:00
octocorvus ac4aa0bbb9 [webview] set minimum font size to 1px
This fixes an issue where some text layer divs (those with font
size < 8px) gets misaligned due to webview setting their font size
to 8px.
2022-12-04 18:32:28 -05:00
dependabot[bot] abfb212011 Bump kotlin-gradle-plugin from 1.7.21 to 1.7.22
Bumps [kotlin-gradle-plugin](https://github.com/JetBrains/kotlin) from 1.7.21 to 1.7.22.
- [Release notes](https://github.com/JetBrains/kotlin/releases)
- [Changelog](https://github.com/JetBrains/kotlin/blob/master/ChangeLog.md)
- [Commits](https://github.com/JetBrains/kotlin/compare/v1.7.21...v1.7.22)

---
updated-dependencies:
- dependency-name: org.jetbrains.kotlin:kotlin-gradle-plugin
  dependency-type: direct:production
  update-type: version-update:semver-patch
...

Signed-off-by: dependabot[bot] <support@github.com>
2022-11-29 17:17:43 -05:00
dependabot[bot] a3ba77534c Bump kotlin-gradle-plugin from 1.7.20 to 1.7.21
Bumps [kotlin-gradle-plugin](https://github.com/JetBrains/kotlin) from 1.7.20 to 1.7.21.
- [Release notes](https://github.com/JetBrains/kotlin/releases)
- [Changelog](https://github.com/JetBrains/kotlin/blob/master/ChangeLog.md)
- [Commits](https://github.com/JetBrains/kotlin/commits)

---
updated-dependencies:
- dependency-name: org.jetbrains.kotlin:kotlin-gradle-plugin
  dependency-type: direct:production
  update-type: version-update:semver-patch
...

Signed-off-by: dependabot[bot] <support@github.com>
2022-11-08 21:58:27 -05:00
Daniel Micay ed3cf2c08f remove no-op allowBackup
This was added to silence a now removed lint check.
2022-10-29 21:11:07 -04:00
smdyv e1dd3db05a Bump material from 1.6.1 to 1.7.0 2022-10-25 15:14:35 -04:00
Daniel Micay 22bd993a75 update to JDK 19 2022-10-19 19:27:19 -04:00
dependabot[bot] a73caa5914 Bump gradle from 7.3.0 to 7.3.1
Bumps gradle from 7.3.0 to 7.3.1.

---
updated-dependencies:
- dependency-name: com.android.tools.build:gradle
  dependency-type: direct:production
  update-type: version-update:semver-patch
...

Signed-off-by: dependabot[bot] <support@github.com>
2022-10-13 17:07:42 -04:00
Matéo Duparc 2766000ebc
libpdfviewer: update to PdfViewer 15 2022-10-06 18:22:57 +02:00
Daniel Micay eead76ad86 increment version to 16 2022-10-04 12:31:21 -04:00
loryeam fbd6c45c3f fix a typo in viewer.js which can cause cache miss 2022-10-04 12:23:18 -04:00
Pratyush 8d0caf65a2 avoid `NullPointerException` 2022-10-03 00:21:53 -04:00
Pratyush 221e45cd52 handle `NumberFormatException` caused by `Integer.parseInt` 2022-10-03 00:21:53 -04:00
Pratyush ee87e43bcb handle `OutOfMemoryError | IllegalArgumentException` 2022-10-03 00:21:47 -04:00
Pratyush 5fadf7f47d replace deprecated api uses 2022-10-03 00:21:31 -04:00
Pratyush 7ff831769e add missing NonNull annotation 2022-10-03 00:21:31 -04:00
Daniel Micay 9e40a05be3 make manifest whitespace style consistent 2022-09-30 17:26:30 -04:00
Pratyush 18761b3e4a replace deprecated api uses 2022-09-30 17:25:49 -04:00
Pratyush 43f971c028 add documentLaunchMode flag 2022-09-30 17:25:49 -04:00
Pratyush 2b81d4dcc3 fix navigation bar color on three button navigation system 2022-09-30 17:25:49 -04:00
Pratyush 5a55c8045b use color transparent for status bar 2022-09-30 17:25:49 -04:00
Pratyush 718b448cc5 remove hardcoded (toolbar) background color 2022-09-30 17:25:49 -04:00
Pratyush afaf71a4a2 use DynamicColors app bar theme 2022-09-30 17:25:49 -04:00
Pratyush 8a308b69e5 apply DynamicColors when available 2022-09-30 17:25:47 -04:00
dependabot[bot] ea5c34ed52 Bump kotlin-gradle-plugin from 1.7.10 to 1.7.20
Bumps [kotlin-gradle-plugin](https://github.com/JetBrains/kotlin) from 1.7.10 to 1.7.20.
- [Release notes](https://github.com/JetBrains/kotlin/releases)
- [Changelog](https://github.com/JetBrains/kotlin/blob/v1.7.20/ChangeLog.md)
- [Commits](https://github.com/JetBrains/kotlin/compare/v1.7.10...v1.7.20)

---
updated-dependencies:
- dependency-name: org.jetbrains.kotlin:kotlin-gradle-plugin
  dependency-type: direct:production
  update-type: version-update:semver-patch
...

Signed-off-by: dependabot[bot] <support@github.com>
2022-09-29 19:23:32 -04:00
Matéo Duparc 06e54dbb03
libpdfviewer: update to PdfViewer 14 2022-09-20 16:00:48 +02:00
Daniel Micay fee1b1516f move away from deprecated package attribute 2022-09-15 19:53:32 -04:00
dependabot[bot] bc77ecbcc6 Bump gradle from 7.2.2 to 7.3.0
Bumps gradle from 7.2.2 to 7.3.0.

---
updated-dependencies:
- dependency-name: com.android.tools.build:gradle
  dependency-type: direct:production
  update-type: version-update:semver-minor
...

Signed-off-by: dependabot[bot] <support@github.com>
2022-09-15 19:44:05 -04:00
Daniel Micay 955bed3614 increment version to 15 2022-09-12 22:29:25 -04:00
Daniel Micay ee3605428e minor cleanup 2022-09-12 22:27:03 -04:00
Daniel Micay e967af2397 update pdf.js to v2.16.105 2022-09-12 22:23:42 -04:00
Daniel Micay 9ea5cb1802 remove duplicate icon resource 2022-09-12 22:17:44 -04:00
Daniel Micay a307f2892c add missing newlines 2022-09-12 22:16:21 -04:00
Pratyush 8c795d4552 add android 13 monochrome icon 2022-09-12 22:15:32 -04:00
Daniel Micay d7ca71770f remove unused string resource 2022-09-12 22:06:02 -04:00
Daniel Micay a29ec202a7 update appcompat to 1.5.1 2022-09-12 21:21:26 -04:00
Pratyush ea9a57fabe Improve password UI/UX 2022-09-12 02:15:53 -04:00
Pratyush 68fc48d3a9 add on (doc) loaded callback 2022-09-12 02:08:42 -04:00
quh4gko8 3ebdfd0a11 bump targetSdk to 33 2022-09-10 22:44:02 -04:00
quh4gko8 ff11df007f Build against android 13, resolve new deprecated APIs 2022-09-10 22:44:02 -04:00
Daniel Micay fa2db54804 update pdf.js to v2.15.349 2022-08-25 23:08:53 -04:00
Patryk Mis 7634c00f59 Bump dependencies 2022-08-11 14:39:32 -04:00
Daniel Micay f67021e975 update Gradle to 7.5.1 2022-08-06 10:57:35 -04:00
dependabot[bot] adb87cbaef Bump gradle from 7.2.1 to 7.2.2
Bumps gradle from 7.2.1 to 7.2.2.

---
updated-dependencies:
- dependency-name: com.android.tools.build:gradle
  dependency-type: direct:production
  update-type: version-update:semver-patch
...

Signed-off-by: dependabot[bot] <support@github.com>
2022-08-03 22:15:55 -04:00
Daniel Micay 09ccfb069d update Android build tools to 33.0.0 2022-07-16 18:29:19 -04:00
Daniel Micay 2a91d95eef update Gradle to 7.5 2022-07-16 17:48:38 -04:00
dependabot[bot] a48213c450 Bump kotlin-gradle-plugin from 1.7.0 to 1.7.10
Bumps [kotlin-gradle-plugin](https://github.com/JetBrains/kotlin) from 1.7.0 to 1.7.10.
- [Release notes](https://github.com/JetBrains/kotlin/releases)
- [Changelog](https://github.com/JetBrains/kotlin/blob/master/ChangeLog.md)
- [Commits](https://github.com/JetBrains/kotlin/commits)

---
updated-dependencies:
- dependency-name: org.jetbrains.kotlin:kotlin-gradle-plugin
  dependency-type: direct:production
  update-type: version-update:semver-patch
...

Signed-off-by: dependabot[bot] <support@github.com>
2022-07-07 17:39:35 -04:00
dependabot[bot] 2f4c1ebfed Bump kotlin-gradle-plugin from 1.6.21 to 1.7.0
Bumps [kotlin-gradle-plugin](https://github.com/JetBrains/kotlin) from 1.6.21 to 1.7.0.
- [Release notes](https://github.com/JetBrains/kotlin/releases)
- [Changelog](https://github.com/JetBrains/kotlin/blob/master/ChangeLog.md)
- [Commits](https://github.com/JetBrains/kotlin/commits)

---
updated-dependencies:
- dependency-name: org.jetbrains.kotlin:kotlin-gradle-plugin
  dependency-type: direct:production
  update-type: version-update:semver-minor
...

Signed-off-by: dependabot[bot] <support@github.com>
2022-06-08 17:12:29 -04:00
Daniel Micay 22b400c68f work around dependabot gradle bug 2022-06-06 06:42:49 -04:00
Daniel Micay 6a6a263b0b update AndroidX appcompat library to 1.4.2 2022-06-04 11:02:25 -04:00
Daniel Micay 8cd685c8c7 remove obsolete backup lint suppression 2022-05-26 20:45:37 -04:00
Daniel Micay be3f43e7a4 ignore vector path length lint for Material icons 2022-05-26 20:44:26 -04:00
Daniel Micay 5eb98841d6 removed unused zoom icons 2022-05-26 20:42:13 -04:00
Daniel Micay e297fdf7e4 ignore false positive lint warning 2022-05-26 20:41:47 -04:00
Daniel Micay 9355f54956 update Material library 2022-05-26 20:39:57 -04:00
dependabot[bot] 5dfdaa2c17 Bump gradle from 7.2.0 to 7.2.1
Bumps gradle from 7.2.0 to 7.2.1.

---
updated-dependencies:
- dependency-name: com.android.tools.build:gradle
  dependency-type: direct:production
  update-type: version-update:semver-patch
...

Signed-off-by: dependabot[bot] <support@github.com>
2022-05-25 16:39:38 -04:00
June 3411fb0426 Update pdf.js to v2.14.305
Signed-off-by: June <june@eridan.me>
2022-05-14 13:27:14 -04:00
dependabot[bot] af567a5c0c Bump gradle from 7.1.3 to 7.2.0
Bumps gradle from 7.1.3 to 7.2.0.

---
updated-dependencies:
- dependency-name: com.android.tools.build:gradle
  dependency-type: direct:production
  update-type: version-update:semver-minor
...

Signed-off-by: dependabot[bot] <support@github.com>
2022-05-09 18:04:52 -04:00
Daniel Micay 6a7982ce80 move snackbar to root view 2022-05-06 10:12:30 -04:00
Daniel Micay 1a3e816cfc simplify error string for error while opening PDF 2022-05-06 10:10:18 -04:00
Daniel Micay c6113df31d extend Permissions Policy
These aren't supported by Chromium yet but they're standardized and it
makes sense to explicitly forbid them.
2022-05-06 10:07:59 -04:00
Daniel Micay db98371c9d increment version to 14 2022-05-06 10:07:59 -04:00
Pratyush 87c71ddac2 added support for encrypted PDF
based on https://github.com/GrapheneOS/PdfViewer/pull/17

Signed-off-by: Pratyush <codelab@pratyush.dev>
Co-authored-by: Tommy-Geenexus <tomgapplicationsdevelopment@gmail.com>
Co-authored-by: empratyush <codelab@pratyush.dev>
2022-05-06 10:07:59 -04:00
Daniel Micay 7dab1cdc91 reorder action strings 2022-05-06 10:07:59 -04:00
Daniel Micay 61d204d188 move share action lower in the menu 2022-05-06 10:07:59 -04:00
Daniel Micay 96651d02af remove obsolete zoom in / zoom out menu actions 2022-05-06 10:07:59 -04:00
Daniel Micay 21d7b9d76e fix warning about non-positional format parameters 2022-05-06 10:07:59 -04:00
Pratyush eb6eb8046c added feature to save document 2022-05-06 10:07:50 -04:00
dependabot[bot] c1a1263bd5 Bump kotlin-gradle-plugin from 1.6.20 to 1.6.21
Bumps [kotlin-gradle-plugin](https://github.com/JetBrains/kotlin) from 1.6.20 to 1.6.21.
- [Release notes](https://github.com/JetBrains/kotlin/releases)
- [Changelog](https://github.com/JetBrains/kotlin/blob/master/ChangeLog.md)
- [Commits](https://github.com/JetBrains/kotlin/commits)

---
updated-dependencies:
- dependency-name: org.jetbrains.kotlin:kotlin-gradle-plugin
  dependency-type: direct:production
  update-type: version-update:semver-patch
...

Signed-off-by: dependabot[bot] <support@github.com>
2022-05-06 04:36:50 -04:00
smdyv d71f804b5a Replace feature graphic 2022-04-22 15:14:36 -04:00
Pratyush ba9a6c8206 replace deprecated startActivityForResult 2022-04-22 04:24:25 -04:00
Daniel Micay c0eea02bb1 update CI JDK to 18 2022-04-20 06:27:40 -04:00
Patryk Mis eb6c14740f Update Gradle Wrapper 2022-04-14 07:12:13 -04:00
dependabot[bot] 6847883605 Bump actions/setup-java from 2 to 3
Bumps [actions/setup-java](https://github.com/actions/setup-java) from 2 to 3.
- [Release notes](https://github.com/actions/setup-java/releases)
- [Commits](https://github.com/actions/setup-java/compare/v2...v3)

---
updated-dependencies:
- dependency-name: actions/setup-java
  dependency-type: direct:production
  update-type: version-update:semver-major
...

Signed-off-by: dependabot[bot] <support@github.com>
2022-04-08 22:07:49 -04:00
dependabot[bot] 975dfb966c Bump gradle from 7.1.2 to 7.1.3
Bumps gradle from 7.1.2 to 7.1.3.

---
updated-dependencies:
- dependency-name: com.android.tools.build:gradle
  dependency-type: direct:production
  update-type: version-update:semver-patch
...

Signed-off-by: dependabot[bot] <support@github.com>
2022-04-07 18:20:58 -04:00
dependabot[bot] ec965ddaaa Bump kotlin-gradle-plugin from 1.6.10 to 1.6.20
Bumps [kotlin-gradle-plugin](https://github.com/JetBrains/kotlin) from 1.6.10 to 1.6.20.
- [Release notes](https://github.com/JetBrains/kotlin/releases)
- [Changelog](https://github.com/JetBrains/kotlin/blob/master/ChangeLog.md)
- [Commits](https://github.com/JetBrains/kotlin/commits)

---
updated-dependencies:
- dependency-name: org.jetbrains.kotlin:kotlin-gradle-plugin
  dependency-type: direct:production
  update-type: version-update:semver-patch
...

Signed-off-by: dependabot[bot] <support@github.com>
2022-04-01 17:10:35 -04:00
June 087a01cf72 Update gradle wrapper to 7.4.2
Signed-off-by: June <june@eridan.me>
2022-03-31 18:32:13 -04:00
Matéo Duparc 8fe8b2f4b3
libpdfviewer: update to PdfViewer 13 2022-03-21 16:43:25 +01:00
Daniel Micay 94c947305b increment version to 13 2022-03-20 05:15:49 -04:00
emschu 0c21e13459 add basic support to share documents via an additional item in options menu, add share icon
Signed-off-by: emschu <emschu@mailbox.org>
2022-03-20 05:14:05 -04:00
June cb4a0e8684 Target SDK 12L/12.1
GrapheneOS is now on 12.1

Signed-off-by: June <june@eridan.me>
2022-03-13 06:00:59 -04:00
June ea519819c6 Update gradle to 7.4.1
Signed-off-by: June <june@eridan.me>
2022-03-12 11:49:02 -05:00
June 26e6d77574 Update pdf.js to 2.13.216
tag 070a365be5a7579f4ddd5a9a2d4efcd281c2d64f

Signed-off-by: June <june@eridan.me>
2022-03-08 17:47:33 -05:00
Matéo Duparc 0f312347dd
libpdfviewer: update to PdfViewer 12 2022-03-06 21:39:38 +01:00
Matéo Duparc 4e4b7c5da4
libpdfviewer: Genesis 2022-02-18 15:03:53 +01:00
55 changed files with 3171 additions and 765 deletions

View File

@ -1,12 +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

View File

@ -1,20 +0,0 @@
name: Build application
on: [pull_request, push]
jobs:
build:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v3
with:
submodules: true
- name: Set up JDK 17
uses: actions/setup-java@v2
with:
distribution: 'temurin'
java-version: 17
cache: gradle
- name: Build with Gradle
run: ./gradlew build --no-daemon

View File

@ -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

3
.gitmodules vendored
View File

@ -1,3 +0,0 @@
[submodule "pdfjs-dist"]
path = third_party/pdfjs-dist
url = https://github.com/mozilla/pdfjs-dist.git

View File

@ -1,4 +1,4 @@
Copyright © 2017-2022 GrapheneOS
Copyright © 2017-2023 GrapheneOS
Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal

View File

@ -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/`.

View File

@ -9,10 +9,16 @@ if (useKeystoreProperties) {
}
plugins {
id("com.android.application")
id("com.android.library")
id("kotlin-android")
}
java {
toolchain {
languageVersion.set(JavaLanguageVersion.of(17))
}
}
android {
if (useKeystoreProperties) {
signingConfigs {
@ -32,25 +38,19 @@ android {
}
}
compileSdk = 32
buildToolsVersion = "32.0.0"
compileSdk = 33
buildToolsVersion = "33.0.2"
namespace = "app.grapheneos.pdfviewer"
defaultConfig {
applicationId = "app.grapheneos.pdfviewer"
minSdk = 26
targetSdk = 31
versionCode = 12
versionName = versionCode.toString()
minSdk = 21
targetSdk = 33
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) {
@ -60,7 +60,6 @@ android {
create("play") {
initWith(getByName("release"))
applicationIdSuffix = ".play"
if (useKeystoreProperties) {
signingConfig = signingConfigs.getByName("play")
}
@ -68,20 +67,21 @@ android {
buildFeatures {
viewBinding = true
buildConfig = true
}
}
compileOptions {
sourceCompatibility(JavaVersion.VERSION_11)
targetCompatibility(JavaVersion.VERSION_11)
sourceCompatibility(JavaVersion.VERSION_17)
targetCompatibility(JavaVersion.VERSION_17)
}
kotlinOptions {
jvmTarget = JavaVersion.VERSION_11.toString()
jvmTarget = JavaVersion.VERSION_17.toString()
}
}
dependencies {
implementation("androidx.appcompat:appcompat:1.4.1")
implementation("com.google.android.material:material:1.5.0")
implementation("androidx.appcompat:appcompat:1.6.1")
implementation("com.google.android.material:material:1.9.0")
}

View File

@ -1,7 +1,8 @@
<lint>
<!-- full backups are desired -->
<issue id="AllowBackup">
<ignore path="src/main/AndroidManifest.xml"/>
<!-- overly aggressive check -->
<issue id="VectorPath">
<ignore path="src/main/res/drawable/ic_rotate_left_24dp.xml"/>
<ignore path="src/main/res/drawable/ic_rotate_right_24dp.xml"/>
</issue>
<!-- Google app indexing doesn't make any sense for this app -->

View File

@ -1,28 +1,9 @@
<?xml version="1.0" encoding="utf-8"?>
<manifest xmlns:android="http://schemas.android.com/apk/res/android"
package="app.grapheneos.pdfviewer"
android:targetSandboxVersion="2">
<original-package android:name="org.grapheneos.pdfviewer" />
<application android:icon="@mipmap/ic_launcher"
android:roundIcon="@mipmap/ic_launcher_round"
android:label="@string/app_name"
android:theme="@style/AppTheme"
android:allowBackup="true">
<activity android:name=".PdfViewer"
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"

View File

@ -1 +1 @@
../../../../third_party/pdfjs-dist/build/pdf.min.js
../../../pdfjs-dist/build/pdf.min.js

View File

@ -1 +1 @@
../../../../third_party/pdfjs-dist/build/pdf.worker.min.js
../../../pdfjs-dist/build/pdf.worker.min.js

View File

@ -1,36 +1,56 @@
:root {
--text-layer-opacity: 0.2;
--text-layer-foreground: transparent;
}
html, body {
height: 100%;
}
body, canvas {
padding: 0;
margin: 0;
}
canvas {
margin: auto;
display: block;
}
body {
background-color: #c0c0c0;
}
#container {
--scale-factor: 1;
width: 100%;
height: 100%;
display: grid;
place-items: center;
}
#container canvas, #container .textLayer {
/* overlay child elements on top of each other */
grid-row-start: 1;
grid-column-start: 1;
}
canvas {
display: inline-block;
position: relative;
}
.textLayer {
position: absolute;
text-align: initial;
left: 0;
top: 0;
right: 0;
bottom: 0;
position: absolute;
overflow: hidden;
opacity: 0.2;
opacity: var(--text-layer-opacity);
line-height: 1;
}
.textLayer span,
.textLayer br {
color: transparent;
color: var(--text-layer-foreground);
position: absolute;
white-space: pre;
cursor: text;
transform-origin: 0% 0%;
transform-origin: 0 0;
}
.textLayer .highlight {
@ -79,3 +99,13 @@ body {
.textLayer .endOfContent.active {
top: 0;
}
[data-main-rotation="90"] {
transform: rotate(90deg);
}
[data-main-rotation="180"] {
transform: rotate(180deg);
}
[data-main-rotation="270"] {
transform: rotate(270deg);
}

View File

@ -7,8 +7,10 @@
<script src="pdf.js"></script>
</head>
<body>
<canvas id="content"></canvas>
<div id="text" class="textLayer"></div>
<div id="container">
<canvas id="content"></canvas>
<div id="text" class="textLayer"></div>
</div>
<script src="viewer.js"></script>
</body>
</html>

View File

@ -6,7 +6,8 @@ let pdfDoc = null;
let pageRendering = false;
let renderPending = false;
let renderPendingZoom = 0;
const canvas = document.getElementById('content');
const canvas = document.getElementById("content");
const container = document.getElementById("container");
let orientationDegrees = 0;
let zoomRatio = 1;
let textLayerDiv = document.getElementById("text");
@ -19,6 +20,8 @@ let useRender;
const cache = [];
const maxCached = 6;
let isTextLayerVisible = false;
function maybeRenderNextPage() {
if (renderPending) {
pageRendering = false;
@ -61,6 +64,21 @@ function display(newCanvas, zoom) {
}
}
function setLayerTransform(pageWidth, pageHeight, layerDiv) {
const translate = {
X: Math.max(0, pageWidth - document.body.clientWidth) / 2,
Y: Math.max(0, pageHeight - document.body.clientHeight) / 2
};
layerDiv.style.translate = `${translate.X}px ${translate.Y}px`;
}
function getDefaultZoomRatio(page, orientationDegrees) {
const viewport = page.getViewport({scale: 1, rotation: orientationDegrees});
const widthZoomRatio = document.body.clientWidth / viewport.width;
const heightZoomRatio = document.body.clientHeight / viewport.height;
return Math.max(Math.min(widthZoomRatio, heightZoomRatio, channel.getMaxZoomRatio()), channel.getMinZoomRatio());
}
function renderPage(pageNumber, zoom, prerender, prerenderTrigger=0) {
pageRendering = true;
useRender = !prerender;
@ -73,7 +91,7 @@ function renderPage(pageNumber, zoom, prerender, prerenderTrigger=0) {
for (let i = 0; i < cache.length; i++) {
const cached = cache[i];
if (cached.pageNumber === pageNumber && cached.zoomRatio === newZoomRatio &&
cache.orientationDegrees === orientationDegrees) {
cached.orientationDegrees === orientationDegrees) {
if (useRender) {
cache.splice(i, 1);
cache.push(cached);
@ -82,6 +100,8 @@ function renderPage(pageNumber, zoom, prerender, prerenderTrigger=0) {
textLayerDiv.replaceWith(cached.textLayerDiv);
textLayerDiv = cached.textLayerDiv;
setLayerTransform(cached.pageWidth, cached.pageHeight, textLayerDiv);
container.style.setProperty("--scale-factor", newZoomRatio.toString());
}
pageRendering = false;
@ -95,7 +115,15 @@ function renderPage(pageNumber, zoom, prerender, prerenderTrigger=0) {
return;
}
const viewport = page.getViewport({scale: newZoomRatio, rotation: orientationDegrees})
const defaultZoomRatio = getDefaultZoomRatio(page, orientationDegrees);
if (cache.length === 0) {
zoomRatio = defaultZoomRatio;
newZoomRatio = defaultZoomRatio;
channel.setZoomRatio(defaultZoomRatio);
}
const viewport = page.getViewport({scale: newZoomRatio, rotation: orientationDegrees});
if (useRender) {
if (newZoomRatio !== zoomRatio) {
@ -105,7 +133,7 @@ function renderPage(pageNumber, zoom, prerender, prerenderTrigger=0) {
zoomRatio = newZoomRatio;
}
if (zoom == 2) {
if (zoom === 2) {
pageRendering = false;
return;
}
@ -137,10 +165,10 @@ function renderPage(pageNumber, zoom, prerender, prerenderTrigger=0) {
}
render();
const textLayerFrag = document.createDocumentFragment();
const newTextLayerDiv = textLayerDiv.cloneNode();
task = pdfjsLib.renderTextLayer({
textContentStream: page.streamTextContent(),
container: textLayerFrag,
textContentSource: page.streamTextContent(),
container: newTextLayerDiv,
viewport: viewport
});
task.promise.then(function() {
@ -148,24 +176,36 @@ function renderPage(pageNumber, zoom, prerender, prerenderTrigger=0) {
render();
const newTextLayerDiv = textLayerDiv.cloneNode();
newTextLayerDiv.style.height = newCanvas.style.height;
newTextLayerDiv.style.width = newCanvas.style.width;
// We use CSS transform to rotate a text layer div of zero
// degrees rotation. So, when the rotation is 90 or 270
// degrees, set width and height of the text layer div to the
// height and width of the canvas, respectively, to prevent
// text layer misalignment.
if (orientationDegrees % 180 === 0) {
newTextLayerDiv.style.height = newCanvas.style.height;
newTextLayerDiv.style.width = newCanvas.style.width;
} else {
newTextLayerDiv.style.height = newCanvas.style.width;
newTextLayerDiv.style.width = newCanvas.style.height;
}
setLayerTransform(viewport.width, viewport.height, newTextLayerDiv);
if (useRender) {
textLayerDiv.replaceWith(newTextLayerDiv);
textLayerDiv = newTextLayerDiv;
container.style.setProperty("--scale-factor", newZoomRatio.toString());
}
newTextLayerDiv.appendChild(textLayerFrag);
if (cache.length === maxCached) {
cache.shift()
cache.shift();
}
cache.push({
pageNumber: pageNumber,
zoomRatio: newZoomRatio,
orientationDegrees: orientationDegrees,
canvas: newCanvas,
textLayerDiv: newTextLayerDiv
textLayerDiv: newTextLayerDiv,
pageWidth: viewport.width,
pageHeight: viewport.height
});
pageRendering = false;
@ -198,15 +238,44 @@ function isTextSelected() {
return window.getSelection().toString() !== "";
}
pdfjsLib.getDocument("https://localhost/placeholder.pdf").promise.then(function(newDoc) {
pdfDoc = newDoc;
channel.setNumPages(pdfDoc.numPages);
pdfDoc.getMetadata().then(function(data) {
channel.setDocumentProperties(JSON.stringify(data.info));
}).catch(function(error) {
console.log("getMetadata error: " + error);
function toggleTextLayerVisibility() {
let textLayerForeground = "red";
let textLayerOpacity = 1;
if (isTextLayerVisible) {
textLayerForeground = "transparent";
textLayerOpacity = 0.2;
}
document.documentElement.style.setProperty("--text-layer-foreground", textLayerForeground);
document.documentElement.style.setProperty("--text-layer-opacity", textLayerOpacity.toString());
isTextLayerVisible = !isTextLayerVisible;
}
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;
channel.setNumPages(pdfDoc.numPages);
pdfDoc.getMetadata().then(function (data) {
channel.setDocumentProperties(JSON.stringify(data.info));
}).catch(function (error) {
console.log("getMetadata error: " + error);
});
renderPage(channel.getPage(), false, false);
}, function (reason) {
console.error(reason.name + ": " + reason.message);
});
renderPage(channel.getPage(), false, false);
}).catch(function(error) {
console.log("getDocument error: " + error);
});
}
window.onresize = () => {
setLayerTransform(canvas.clientWidth, canvas.clientHeight, textLayerDiv);
};

Binary file not shown.

Before

Width:  |  Height:  |  Size: 15 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 11 KiB

View File

@ -1,12 +1,10 @@
package app.grapheneos.pdfviewer;
import android.annotation.SuppressLint;
import android.app.Activity;
import android.content.Intent;
import android.content.pm.PackageInfo;
import android.content.res.ColorStateList;
import android.graphics.Color;
import android.net.Uri;
import android.os.Build;
import android.os.Bundle;
import android.util.Log;
import android.view.Gravity;
@ -26,11 +24,14 @@ import android.widget.TextView;
import android.widget.Toast;
import androidx.annotation.NonNull;
import androidx.annotation.RequiresApi;
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.lifecycle.ViewModelProvider;
import androidx.loader.app.LoaderManager;
import androidx.loader.content.Loader;
@ -38,23 +39,25 @@ import com.google.android.material.snackbar.Snackbar;
import app.grapheneos.pdfviewer.databinding.PdfviewerBinding;
import app.grapheneos.pdfviewer.fragment.DocumentPropertiesFragment;
import app.grapheneos.pdfviewer.fragment.PasswordPromptFragment;
import app.grapheneos.pdfviewer.fragment.JumpToPageFragment;
import app.grapheneos.pdfviewer.loader.DocumentPropertiesLoader;
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.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 KEY_PROPERTIES = "properties";
private static final int MIN_WEBVIEW_RELEASE = 89;
private static final int MIN_WEBVIEW_RELEASE = 92;
private static final String CONTENT_SECURITY_POLICY =
"default-src 'none'; " +
@ -78,6 +81,7 @@ public class PdfViewer extends AppCompatActivity implements LoaderManager.Loader
"document-domain=(), " +
"encrypted-media=(), " +
"fullscreen=(), " +
"gamepad=(), " +
"geolocation=(), " +
"gyroscope=(), " +
"hid=(), " +
@ -91,32 +95,37 @@ public class PdfViewer extends AppCompatActivity implements LoaderManager.Loader
"publickey-credentials-get=(), " +
"screen-wake-lock=(), " +
"serial=(), " +
"speaker-selection=(), " +
"sync-xhr=(), " +
"usb=(), " +
"xr-spatial-tracking=()";
private static final float MIN_ZOOM_RATIO = 0.5f;
private static final float MIN_ZOOM_RATIO = 0.2f;
private static final float MAX_ZOOM_RATIO = 1.5f;
private static final int ALPHA_LOW = 130;
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_END = 2;
private static final int PADDING = 10;
private Uri mUri;
public int mPage;
public int mNumPages;
private float mZoomRatio = 1f;
private int mDocumentOrientationDegrees;
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 class Channel {
@JavascriptInterface
@ -129,6 +138,21 @@ public class PdfViewer extends AppCompatActivity implements LoaderManager.Loader
return mZoomRatio;
}
@JavascriptInterface
public void setZoomRatio(final float ratio) {
mZoomRatio = Math.max(Math.min(ratio, MAX_ZOOM_RATIO), MIN_ZOOM_RATIO);
}
@JavascriptInterface
public float getMinZoomRatio() {
return MIN_ZOOM_RATIO;
}
@JavascriptInterface
public float getMaxZoomRatio() {
return MAX_ZOOM_RATIO;
}
@JavascriptInterface
public int getDocumentOrientationDegrees() {
return mDocumentOrientationDegrees;
@ -137,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
@ -148,19 +172,44 @@ public class PdfViewer extends AppCompatActivity implements LoaderManager.Loader
final Bundle args = new Bundle();
args.putString(KEY_PROPERTIES, properties);
runOnUiThread(() -> LoaderManager.getInstance(PdfViewer.this).restartLoader(DocumentPropertiesLoader.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(activity.getSupportFragmentManager(), PasswordPromptFragment.class.getName());
}
passwordValidationViewModel.passwordMissing();
}
@JavascriptInterface
public void invalidPassword() {
activity.runOnUiThread(() -> passwordValidationViewModel.invalid());
}
@JavascriptInterface
public void onLoaded() {
passwordValidationViewModel.validated();
if (getPasswordPromptFragment().isAdded()) {
getPasswordPromptFragment().dismiss();
}
}
@JavascriptInterface
public String getPassword() {
return mEncryptedDocumentPassword != null ? mEncryptedDocumentPassword : "";
}
}
@Override
@SuppressLint({"SetJavaScriptEnabled", "ClickableViewAccessibility"})
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
binding = PdfviewerBinding.inflate(getLayoutInflater());
setContentView(binding.getRoot());
setSupportActionBar(binding.toolbar);
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.
@ -184,6 +233,7 @@ public class PdfViewer extends AppCompatActivity implements LoaderManager.Loader
settings.setAllowFileAccess(false);
settings.setCacheMode(WebSettings.LOAD_NO_CACHE);
settings.setJavaScriptEnabled(true);
settings.setMinimumFontSize(1);
CookieManager.getInstance().setAcceptCookie(false);
@ -192,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;
@ -214,6 +264,7 @@ public class PdfViewer extends AppCompatActivity implements LoaderManager.Loader
Log.d(TAG, "path " + path);
if ("/placeholder.pdf".equals(path)) {
mInputStream.reset();
return new WebResourceResponse("application/pdf", null, mInputStream);
}
@ -246,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 ((getWindow().getDecorView().getSystemUiVisibility() &
View.SYSTEM_UI_FLAG_FULLSCREEN) == 0) {
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
@ -286,62 +334,68 @@ 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.webview, "", 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) {
mUri = savedInstanceState.getParcelable(STATE_URI);
mPage = savedInstanceState.getInt(STATE_PAGE);
mZoomRatio = savedInstanceState.getFloat(STATE_ZOOM_RATIO);
mDocumentOrientationDegrees = savedInstanceState.getInt(STATE_DOCUMENT_ORIENTATION_DEGREES);
}
if (mUri != null) {
if ("file".equals(mUri.getScheme())) {
snackbar.setText(R.string.legacy_file_uri).show();
return;
}
loadPdf();
}
}
@Override
protected void onResume() {
super.onResume();
public void onDestroy() {
binding.webview.removeJavascriptInterface("channel");
binding.getRoot().removeView(binding.webview);
binding.webview.destroy();
maybeCloseInputStream();
}
// 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);
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;
}
private void setToolbarTitleWithDocumentName() {
String documentName = getCurrentDocumentName();
if (documentName != null && !documentName.isEmpty()) {
activity.getSupportActionBar().setTitle(documentName);
} 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.getSupportActionBar().setTitle(R.string.app_name);
}
}
public void onResume() {
// The user could have left the activity to update the WebView
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;
@ -351,13 +405,14 @@ public class PdfViewer extends AppCompatActivity implements LoaderManager.Loader
@NonNull
@Override
public Loader<List<CharSequence>> onCreateLoader(int id, Bundle args) {
return new DocumentPropertiesLoader(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;
LoaderManager.getInstance(this).destroyLoader(DocumentPropertiesLoader.ID);
setToolbarTitleWithDocumentName();
LoaderManager.getInstance(activity).destroyLoader(DocumentPropertiesAsyncTaskLoader.ID);
}
@Override
@ -365,21 +420,22 @@ 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.io_error).show();
return;
}
public void loadPdf(ByteArrayInputStream inputStream, String fileName, Long fileSize) {
mPage = 1;
mDocumentProperties = null;
mInputStream = inputStream;
this.fileName = fileName;
this.fileSize = fileSize;
showSystemUi();
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) {
binding.webview.evaluateJavascript("onRenderPage(" + zoom + ")", null);
}
@ -392,18 +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");
startActivityForResult(intent, ACTION_OPEN_DOCUMENT_REQUEST_CODE);
}
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();
}
}
@ -411,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();
}
}
@ -434,51 +483,18 @@ public class PdfViewer extends AppCompatActivity implements LoaderManager.Loader
mPage = selected_page;
renderPage(0);
showPageNumber();
invalidateOptionsMenu();
activity.invalidateOptionsMenu();
}
}
private void showSystemUi() {
getWindow().getDecorView().setSystemUiVisibility(
View.SYSTEM_UI_FLAG_LAYOUT_STABLE |
View.SYSTEM_UI_FLAG_LAYOUT_HIDE_NAVIGATION |
View.SYSTEM_UI_FLAG_LAYOUT_FULLSCREEN);
getSupportActionBar().show();
ViewKt.showSystemUi(binding.getRoot(), activity.getWindow());
activity.getSupportActionBar().show();
}
private void hideSystemUi() {
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);
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);
}
@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();
}
}
ViewKt.hideSystemUi(binding.getRoot(), activity.getWindow());
activity.getSupportActionBar().hide();
}
private void showPageNumber() {
@ -486,27 +502,29 @@ 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(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);
return true;
if (BuildConfig.DEBUG) {
inflater.inflate(R.menu.pdf_viewer_debug, menu);
}
}
@Override
public boolean onPrepareOptionsMenu(Menu menu) {
final int[] ids = { R.id.action_zoom_in, R.id.action_zoom_out, R.id.action_jump_to_page,
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_view_document_properties));
if (BuildConfig.DEBUG) {
ids.add(R.id.debug_action_toggle_text_layer_visibility);
}
if (mDocumentState < STATE_LOADED) {
for (final int id : ids) {
final MenuItem item = menu.findItem(id);
@ -524,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_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_previous), mPage > 1);
return true;
}
@Override
public boolean onOptionsItemSelected(MenuItem item) {
final int itemId = item.getItemId();
if (itemId == R.id.action_previous) {
@ -548,15 +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_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) {
documentOrientationChanged(90);
return true;
@ -566,14 +571,32 @@ 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.debug_action_toggle_text_layer_visibility) {
binding.webview.evaluateJavascript("toggleTextLayerVisibility()", null);
return true;
}
return super.onOptionsItemSelected(item);
return false;
}
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;
}
}

View File

@ -9,20 +9,13 @@ import java.text.ParseException;
import java.util.Calendar;
public class Utils {
public static String parseFileSize(long fileSize) {
final double kb = fileSize / 1000d;
if (kb == 0d) {
return fileSize + " Bytes";
private static int parseIntSafely(String field) throws ParseException {
try {
return Integer.parseInt(field);
} catch (NumberFormatException e) {
throw new ParseException("Error while parsing int", -1);
}
final DecimalFormat format = new DecimalFormat("#.##");
format.setRoundingMode(RoundingMode.CEILING);
if (kb < 1000) {
return format.format(kb) + " kB (" + fileSize + " Bytes)";
}
return format.format(kb / 1000) + " MB (" + fileSize + " Bytes)";
}
// Parse date as per PDF spec (complies with PDF v1.4 to v1.7)
@ -39,14 +32,13 @@ public class Utils {
final Calendar calendar = Calendar.getInstance();
final int currentYear = calendar.get(Calendar.YEAR);
int year;
// Year is required
String field = date.substring(position += 2, 6);
if (!TextUtils.isDigitsOnly(field)) {
throw new ParseException("Invalid year", position);
}
year = Integer.parseInt(field);
int year = parseIntSafely(field);
if (year > currentYear) {
year = currentYear;
}
@ -67,7 +59,7 @@ public class Utils {
if (!TextUtils.isDigitsOnly(field)) {
throw new ParseException("Invalid month", position);
}
month = Integer.parseInt(field) - 1;
month = parseIntSafely(field) - 1;
if (month > 11) {
throw new ParseException("Invalid month", position);
}
@ -78,7 +70,7 @@ public class Utils {
if (!TextUtils.isDigitsOnly(field)) {
throw new ParseException("Invalid day", position);
}
day = Integer.parseInt(field);
day = parseIntSafely(field);
if (day > 31) {
throw new ParseException("Invalid day", position);
}
@ -89,7 +81,7 @@ public class Utils {
if (!TextUtils.isDigitsOnly(field)) {
throw new ParseException("Invalid hours", position);
}
hours = Integer.parseInt(field);
hours = parseIntSafely(field);
if (hours > 23) {
throw new ParseException("Invalid hours", position);
}
@ -100,7 +92,7 @@ public class Utils {
if (!TextUtils.isDigitsOnly(field)) {
throw new ParseException("Invalid minutes", position);
}
minutes = Integer.parseInt(field);
minutes = parseIntSafely(field);
if (minutes > 59) {
throw new ParseException("Invalid minutes", position);
}
@ -111,7 +103,7 @@ public class Utils {
if (!TextUtils.isDigitsOnly(field)) {
throw new ParseException("Invalid seconds", position);
}
seconds = Integer.parseInt(field);
seconds = parseIntSafely(field);
if (seconds > 59) {
throw new ParseException("Invalid seconds", position);
}
@ -135,7 +127,7 @@ public class Utils {
if (!TextUtils.isDigitsOnly(field)) {
throw new ParseException("Invalid UTC offset hours", position);
}
offsetHours = Integer.parseInt(field);
offsetHours = parseIntSafely(field);
final int offsetHoursMinutes = offsetHours * 100 + offsetMinutes;
// Validate UTC offset (UTC-12:00 to UTC+14:00)
@ -158,7 +150,7 @@ public class Utils {
if (!TextUtils.isDigitsOnly(field)) {
throw new ParseException("Invalid UTC offset minutes", position);
}
offsetMinutes = Integer.parseInt(field);
offsetMinutes = parseIntSafely(field);
if (offsetMinutes > 59) {
throw new ParseException("Invalid UTC offset minutes", position);
}

View File

@ -1,60 +0,0 @@
package app.grapheneos.pdfviewer.fragment;
import android.app.Activity;
import android.app.Dialog;
import android.os.Bundle;
import android.widget.ArrayAdapter;
import androidx.annotation.NonNull;
import androidx.fragment.app.DialogFragment;
import com.google.android.material.dialog.MaterialAlertDialogBuilder;
import app.grapheneos.pdfviewer.R;
import java.util.ArrayList;
import java.util.List;
public class DocumentPropertiesFragment extends DialogFragment {
public static final String TAG = "DocumentPropertiesFragment";
private static final String KEY_DOCUMENT_PROPERTIES = "document_properties";
private List<String> mDocumentProperties;
public static DocumentPropertiesFragment newInstance(final List<CharSequence> metaData) {
final DocumentPropertiesFragment fragment = new DocumentPropertiesFragment();
final Bundle args = new Bundle();
args.putCharSequenceArrayList(KEY_DOCUMENT_PROPERTIES, (ArrayList<CharSequence>) metaData);
fragment.setArguments(args);
return fragment;
}
@Override
public void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
if (getArguments() != null) {
mDocumentProperties = getArguments().getStringArrayList(KEY_DOCUMENT_PROPERTIES);
}
}
@NonNull
@Override
public Dialog onCreateDialog(Bundle savedInstanceState) {
final Activity activity = requireActivity();
final MaterialAlertDialogBuilder dialog = new MaterialAlertDialogBuilder(activity)
.setPositiveButton(android.R.string.ok, null);
if (mDocumentProperties != null) {
dialog.setTitle(getString(R.string.action_view_document_properties));
dialog.setAdapter(new ArrayAdapter<>(activity, android.R.layout.simple_list_item_1,
mDocumentProperties), null);
} else {
dialog.setTitle(R.string.document_properties_retrieval_failed);
}
return dialog.create();
}
}

View File

@ -0,0 +1,53 @@
package app.grapheneos.pdfviewer.fragment
import android.app.Dialog
import android.os.Bundle
import android.widget.ArrayAdapter
import androidx.fragment.app.DialogFragment
import app.grapheneos.pdfviewer.R
import com.google.android.material.dialog.MaterialAlertDialogBuilder
class DocumentPropertiesFragment : DialogFragment() {
// TODO replace with nav args once the `PdfViewer` activity is converted to kotlin
private val mDocumentProperties: List<String> by lazy {
requireArguments().getStringArrayList(KEY_DOCUMENT_PROPERTIES)?.toList() ?: emptyList()
}
override fun onCreateDialog(savedInstanceState: Bundle?): Dialog {
return MaterialAlertDialogBuilder(requireActivity())
.setPositiveButton(android.R.string.ok, null).apply {
if (mDocumentProperties.isNotEmpty()) {
setTitle(getString(R.string.action_view_document_properties))
setAdapter(
ArrayAdapter(
requireActivity(),
android.R.layout.simple_list_item_1,
mDocumentProperties
), null
)
} else {
setTitle(R.string.document_properties_retrieval_failed)
}
}
.create()
}
companion object {
const val TAG = "DocumentPropertiesFragment"
private const val KEY_DOCUMENT_PROPERTIES = "document_properties"
@JvmStatic
fun newInstance(metaData: List<CharSequence>): DocumentPropertiesFragment {
val fragment = DocumentPropertiesFragment()
val args = Bundle()
args.putCharSequenceArrayList(
KEY_DOCUMENT_PROPERTIES,
metaData as ArrayList<CharSequence>
)
fragment.arguments = args
return fragment
}
}
}

View File

@ -1,65 +0,0 @@
package app.grapheneos.pdfviewer.fragment;
import android.app.Dialog;
import android.os.Bundle;
import android.view.Gravity;
import android.widget.FrameLayout;
import android.widget.NumberPicker;
import androidx.annotation.NonNull;
import androidx.fragment.app.DialogFragment;
import com.google.android.material.dialog.MaterialAlertDialogBuilder;
import app.grapheneos.pdfviewer.PdfViewer;
public class JumpToPageFragment extends DialogFragment {
public static final String TAG = "JumpToPageFragment";
private final static String STATE_PICKER_CUR = "picker_cur";
private final static String STATE_PICKER_MIN = "picker_min";
private final static String STATE_PICKER_MAX = "picker_max";
private NumberPicker mPicker;
@Override
public void onActivityCreated(Bundle savedInstanceState) {
super.onActivityCreated(savedInstanceState);
if (savedInstanceState != null) {
mPicker.setMinValue(savedInstanceState.getInt(STATE_PICKER_MIN));
mPicker.setMaxValue(savedInstanceState.getInt(STATE_PICKER_MAX));
mPicker.setValue(savedInstanceState.getInt(STATE_PICKER_CUR));
}
}
@NonNull
@Override
public Dialog onCreateDialog(Bundle savedInstanceState) {
mPicker = new NumberPicker(getActivity());
mPicker.setMinValue(1);
mPicker.setMaxValue(((PdfViewer)requireActivity()).mNumPages);
mPicker.setValue(((PdfViewer)requireActivity()).mPage);
final FrameLayout layout = new FrameLayout(getActivity());
layout.addView(mPicker, new FrameLayout.LayoutParams(
FrameLayout.LayoutParams.WRAP_CONTENT,
FrameLayout.LayoutParams.WRAP_CONTENT,
Gravity.CENTER));
return new MaterialAlertDialogBuilder(requireActivity())
.setView(layout)
.setPositiveButton(android.R.string.ok, (dialogInterface, i) -> {
mPicker.clearFocus();
((PdfViewer)requireActivity()).onJumpToPageInDocument(mPicker.getValue());
})
.setNegativeButton(android.R.string.cancel, null)
.create();
}
@Override
public void onSaveInstanceState(Bundle outState) {
outState.putInt(STATE_PICKER_MIN, mPicker.getMinValue());
outState.putInt(STATE_PICKER_MAX, mPicker.getMaxValue());
outState.putInt(STATE_PICKER_CUR, mPicker.getValue());
}
}

View File

@ -0,0 +1,58 @@
package app.grapheneos.pdfviewer.fragment
import android.app.Dialog
import android.content.DialogInterface
import android.os.Bundle
import android.view.Gravity
import android.widget.FrameLayout
import android.widget.NumberPicker
import androidx.fragment.app.DialogFragment
import app.grapheneos.pdfviewer.PdfViewer
import com.google.android.material.dialog.MaterialAlertDialogBuilder
class JumpToPageFragment(private val pdfViewer: PdfViewer) : DialogFragment() {
companion object {
const val TAG = "JumpToPageFragment"
private const val STATE_PICKER_CUR = "picker_cur"
private const val STATE_PICKER_MIN = "picker_min"
private const val STATE_PICKER_MAX = "picker_max"
}
private val mPicker: NumberPicker by lazy { NumberPicker(requireActivity()) }
override fun onCreateDialog(savedInstanceState: Bundle?): Dialog {
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 = pdfViewer.mNumPages
mPicker.value = pdfViewer.mPage
}
val layout = FrameLayout(requireActivity())
layout.addView(
mPicker, FrameLayout.LayoutParams(
FrameLayout.LayoutParams.WRAP_CONTENT,
FrameLayout.LayoutParams.WRAP_CONTENT,
Gravity.CENTER
)
)
return MaterialAlertDialogBuilder(requireActivity())
.setView(layout)
.setPositiveButton(android.R.string.ok) { _: DialogInterface?, _: Int ->
mPicker.clearFocus()
pdfViewer.onJumpToPageInDocument(mPicker.value)
}
.setNegativeButton(android.R.string.cancel, null)
.create()
}
override fun onSaveInstanceState(outState: Bundle) {
outState.putInt(STATE_PICKER_MIN, mPicker.minValue)
outState.putInt(STATE_PICKER_MAX, mPicker.maxValue)
outState.putInt(STATE_PICKER_CUR, mPicker.value)
}
}

View File

@ -0,0 +1,103 @@
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
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(private val pdfViewer: PdfViewer) : 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)
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)) {
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()
}
}
}

View File

@ -0,0 +1,20 @@
package app.grapheneos.pdfviewer.ktx
import android.view.View
import android.view.Window
import androidx.core.view.WindowCompat
import androidx.core.view.WindowInsetsCompat
import androidx.core.view.WindowInsetsControllerCompat
private val systemBars = WindowInsetsCompat.Type.statusBars()
fun View.hideSystemUi(window: Window) {
val controller = WindowCompat.getInsetsController(window, this)
controller.systemBarsBehavior =
WindowInsetsControllerCompat.BEHAVIOR_SHOW_TRANSIENT_BARS_BY_SWIPE
controller.hide(systemBars)
}
fun View.showSystemUi(window: Window) {
WindowCompat.getInsetsController(window, this).show(systemBars)
}

View File

@ -0,0 +1,51 @@
package app.grapheneos.pdfviewer.loader;
import android.content.Context;
import androidx.annotation.Nullable;
import androidx.loader.content.AsyncTaskLoader;
import java.util.List;
public class DocumentPropertiesAsyncTaskLoader extends AsyncTaskLoader<List<CharSequence>> {
public static final String TAG = "DocumentPropertiesLoader";
public static final int ID = 1;
private final String mProperties;
private final int mNumPages;
String fileName;
Long fileSize;
public DocumentPropertiesAsyncTaskLoader(Context context, String properties, int numPages, String fileName, Long fileSize) {
super(context);
mProperties = properties;
mNumPages = numPages;
this.fileName = fileName;
this.fileSize = fileSize;
}
@Override
protected void onStartLoading() {
forceLoad();
}
@Nullable
@Override
public List<CharSequence> loadInBackground() {
DocumentPropertiesLoader loader = new DocumentPropertiesLoader(
getContext(),
mProperties,
mNumPages,
fileName,
fileSize
);
return loader.loadAsList();
}
}

View File

@ -1,149 +0,0 @@
package app.grapheneos.pdfviewer.loader;
import android.content.Context;
import android.database.Cursor;
import android.graphics.Typeface;
import android.net.Uri;
import android.provider.OpenableColumns;
import android.text.SpannableStringBuilder;
import android.text.Spanned;
import android.text.style.StyleSpan;
import android.util.Log;
import androidx.loader.content.AsyncTaskLoader;
import org.json.JSONException;
import org.json.JSONObject;
import java.text.ParseException;
import java.util.ArrayList;
import java.util.List;
import app.grapheneos.pdfviewer.R;
import app.grapheneos.pdfviewer.Utils;
public class DocumentPropertiesLoader extends AsyncTaskLoader<List<CharSequence>> {
public static final String TAG = "DocumentPropertiesLoader";
public static final int ID = 1;
private final String mProperties;
private final int mNumPages;
private final Uri mUri;
private Cursor mCursor;
public DocumentPropertiesLoader(Context context, String properties, int numPages, Uri uri) {
super(context);
mProperties = properties;
mNumPages = numPages;
mUri = uri;
}
@Override
public List<CharSequence> loadInBackground() {
final Context context = getContext();
final String[] names = context.getResources().getStringArray(R.array.property_names);
final List<CharSequence> properties = new ArrayList<>(names.length);
mCursor = context.getContentResolver().query(mUri, null, null, null, null);
if (mCursor != null) {
mCursor.moveToFirst();
final int indexName = mCursor.getColumnIndex(OpenableColumns.DISPLAY_NAME);
if (indexName >= 0) {
properties.add(getProperty(null, names[0], mCursor.getString(indexName)));
}
final int indexSize = mCursor.getColumnIndex(OpenableColumns.SIZE);
if (indexSize >= 0) {
final long fileSize = Long.parseLong(mCursor.getString(indexSize));
properties.add(getProperty(null, names[1], Utils.parseFileSize(fileSize)));
}
mCursor.close();
}
try {
final JSONObject json = new JSONObject(mProperties);
properties.add(getProperty(json, names[2], "Title"));
properties.add(getProperty(json, names[3], "Author"));
properties.add(getProperty(json, names[4], "Subject"));
properties.add(getProperty(json, names[5], "Keywords"));
properties.add(getProperty(json, names[6], "CreationDate"));
properties.add(getProperty(json, names[7], "ModDate"));
properties.add(getProperty(json, names[8], "Producer"));
properties.add(getProperty(json, names[9], "Creator"));
properties.add(getProperty(json, names[10], "PDFFormatVersion"));
properties.add(getProperty(null, names[11], String.valueOf(mNumPages)));
return properties;
} catch (JSONException e) {
e.printStackTrace();
}
return null;
}
@Override
public void deliverResult(List<CharSequence> properties) {
if (isReset()) {
onReleaseResources();
} else if (isStarted()) {
super.deliverResult(properties);
}
}
@Override
protected void onStartLoading() {
forceLoad();
}
@Override
protected void onStopLoading() {
cancelLoad();
}
@Override
public void onCanceled(List<CharSequence> properties) {
super.onCanceled(properties);
onReleaseResources();
}
@Override
protected void onReset() {
super.onReset();
onStopLoading();
onReleaseResources();
}
private void onReleaseResources() {
if (mCursor != null) {
mCursor.close();
mCursor = null;
}
}
private CharSequence getProperty(final JSONObject json, String name, String specName) {
final SpannableStringBuilder property = new SpannableStringBuilder(name).append(":\n");
final String value = json != null ? json.optString(specName, "-") : specName;
if (specName.endsWith("Date")) {
final Context context = getContext();
try {
property.append(value.equals("-") ? value : Utils.parseDate(value));
} catch (ParseException e) {
Log.w(TAG, e.getMessage() + " for " + value + " at offset: " + e.getErrorOffset());
property.append(context.getString(R.string.document_properties_invalid_date));
}
} else {
property.append(value);
}
property.setSpan(new StyleSpan(Typeface.BOLD), 0, name.length(), Spanned.SPAN_EXCLUSIVE_EXCLUSIVE);
return property;
}
}

View File

@ -0,0 +1,88 @@
package app.grapheneos.pdfviewer.loader
import android.content.Context
import android.graphics.Typeface
import android.text.SpannableStringBuilder
import android.text.Spanned
import android.text.format.Formatter
import android.text.style.StyleSpan
import android.util.Log
import app.grapheneos.pdfviewer.R
import org.json.JSONException
class DocumentPropertiesLoader(
private val context: Context,
private val properties: String,
private val numPages: Int,
private val fileName: String,
private val fileSize: Long,
) {
fun loadAsList(): List<CharSequence> {
return load().map { item ->
val name = context.getString(item.key.nameResource)
val value = item.value
SpannableStringBuilder()
.append(name)
.append(":\n")
.append(value)
.apply {
setSpan(
StyleSpan(Typeface.BOLD),
0,
name.length,
Spanned.SPAN_EXCLUSIVE_EXCLUSIVE
)
}
}
}
private fun load(): Map<DocumentProperty, String> {
val result = mutableMapOf<DocumentProperty, String>()
result.addFileProperties()
result.addPageSizeProperty()
result.addPDFJsProperties()
return result
}
private fun MutableMap<DocumentProperty, String>.addPageSizeProperty() {
this[DocumentProperty.Pages] = java.lang.String.valueOf(numPages)
}
private fun MutableMap<DocumentProperty, String>.addFileProperties() {
putAll(getFileProperties())
}
private fun MutableMap<DocumentProperty, String>.addPDFJsProperties() {
putAll(getPDFJsProperties())
}
private fun getPDFJsProperties(): Map<DocumentProperty, String> {
return try {
PDFJsPropertiesToDocumentPropertyConverter(
properties,
context.getString(R.string.document_properties_invalid_date),
parseExceptionListener = { parseException, value ->
Log.w(
DocumentPropertiesAsyncTaskLoader.TAG,
"${parseException.message} for $value at offset: ${parseException.errorOffset}"
)
}
).convert()
} catch (e: JSONException) {
Log.w(
DocumentPropertiesAsyncTaskLoader.TAG,
"invalid properties"
)
emptyMap()
}
}
private fun getFileProperties(): Map<DocumentProperty, String> {
val collections = mutableMapOf<DocumentProperty, String>()
collections[DocumentProperty.FileName] = fileName
collections[DocumentProperty.FileSize] = Formatter.formatFileSize(context, fileSize)
return collections
}
}

View File

@ -0,0 +1,35 @@
package app.grapheneos.pdfviewer.loader
import androidx.annotation.StringRes
import app.grapheneos.pdfviewer.R
private const val TITLE_KEY = "Title"
private const val AUTHOR_KEY = "Author"
private const val SUBJECT_KEY = "Subject"
private const val KEYWORDS_KEY = "Keywords"
private const val CREATION_DATE_KEY = "CreationDate"
private const val MODIFY_DATE_KEY = "ModDate"
private const val PRODUCER_KEY = "Producer"
private const val CREATOR_KEY = "Creator"
private const val PDF_VERSION_KEY = "PDFFormatVersion"
const val DEFAULT_VALUE = "-"
enum class DocumentProperty(
val key: String = "",
@StringRes val nameResource: Int,
val isDate: Boolean = false
) {
FileName(key = "", nameResource = R.string.file_name),
FileSize(key = "", nameResource = R.string.file_size),
Pages(key = "", nameResource = R.string.pages),
Title(key = TITLE_KEY, nameResource = R.string.title),
Author(key = AUTHOR_KEY, nameResource = R.string.author),
Subject(key = SUBJECT_KEY, nameResource = R.string.subject),
Keywords(key = KEYWORDS_KEY, nameResource = R.string.keywords),
CreationDate(key = CREATION_DATE_KEY, nameResource = R.string.creation_date, isDate = true),
ModifyDate(key = MODIFY_DATE_KEY, nameResource = R.string.modify_date, isDate = true),
Producer(key = PRODUCER_KEY, nameResource = R.string.producer),
Creator(key = CREATOR_KEY, nameResource = R.string.creator),
PDFVersion(key = PDF_VERSION_KEY, nameResource = R.string.pdf_version);
}

View File

@ -0,0 +1,47 @@
package app.grapheneos.pdfviewer.loader
import app.grapheneos.pdfviewer.Utils
import org.json.JSONException
import org.json.JSONObject
import java.text.ParseException
import kotlin.jvm.Throws
class PDFJsPropertiesToDocumentPropertyConverter(
private val properties: String,
private val propertyInvalidDate: String,
private val parseExceptionListener: (e: ParseException, value: String) -> Unit
) {
@Throws(JSONException::class)
fun convert(): Map<DocumentProperty, String> {
val result = mutableMapOf<DocumentProperty, String>()
val json = JSONObject(properties)
addJsonProperties(json, result)
return result
}
private fun addJsonProperties(
json: JSONObject,
collections: MutableMap<DocumentProperty, String>
) {
for (documentProperty in DocumentProperty.values()) {
val key = documentProperty.key
if (key.isEmpty()) continue
val value = json.optString(key, DEFAULT_VALUE)
collections[documentProperty] = prettify(documentProperty, value)
}
}
private fun prettify(property: DocumentProperty, value: String): String {
if (value != DEFAULT_VALUE && property.isDate) {
return try {
Utils.parseDate(value)
} catch (parseException: ParseException) {
parseExceptionListener.invoke(parseException, value)
propertyInvalidDate
}
}
return value
}
}

View File

@ -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)
}
}

View File

@ -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>

View File

@ -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>

View File

@ -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>

View File

@ -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>

View File

@ -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>

View File

@ -14,7 +14,6 @@
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>

View File

@ -11,31 +11,13 @@
android:id="@+id/action_previous"
android:icon="@drawable/ic_navigate_before_24dp"
android:title="@string/action_previous"
app:showAsAction="ifRoom" />
app:showAsAction="always" />
<item
android:id="@+id/action_next"
android:icon="@drawable/ic_navigate_next_24dp"
android:title="@string/action_next"
app:showAsAction="ifRoom" />
<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_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" />
app:showAsAction="always" />
<item
android:id="@+id/action_first"

View File

@ -0,0 +1,10 @@
<?xml version="1.0" encoding="utf-8"?>
<menu xmlns:app="http://schemas.android.com/apk/res-auto"
xmlns:android="http://schemas.android.com/apk/res/android">
<item
android:id="@+id/debug_action_toggle_text_layer_visibility"
android:title="@string/debug_action_toggle_text_layer_visibility"
app:showAsAction="never" />
</menu>

View File

@ -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>

View File

@ -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>

View File

@ -1,3 +0,0 @@
<?xml version="1.0" encoding="utf-8"?>
<resources xmlns:tools="http://schemas.android.com/tools"
tools:shrinkMode="strict" />

View File

@ -1,13 +1,14 @@
<resources>
<resources xmlns:tools="http://schemas.android.com/tools">
<!-- 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:statusBarColor">@android:color/transparent</item>
<item name="android:navigationBarColor">@android:color/transparent</item>
<item name="android:windowLightNavigationBar" tools:ignore="NewApi">false</item>
</style>
<style name="AppTheme.PopupOverlay" parent="ThemeOverlay.Material3.Dark" />
<style name="AppTheme.PopupOverlay" parent="ThemeOverlay.Material3.DynamicColors.Dark" />
</resources>

View File

@ -1,17 +0,0 @@
<?xml version="1.0" encoding="utf-8"?>
<resources>
<string-array name="property_names">
<item>File name</item>
<item>File size</item>
<item>Title</item>
<item>Author</item>
<item>Subject</item>
<item>Keywords</item>
<item>Creation date</item>
<item>Modify date</item>
<item>Producer</item>
<item>Creator</item>
<item>PDF version</item>
<item>Pages</item>
</string-array>
</resources>

View File

@ -1,4 +0,0 @@
<?xml version="1.0" encoding="utf-8"?>
<resources>
<color name="ic_launcher_background">#FFFFFF</color>
</resources>

View File

@ -1,26 +1,39 @@
<?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="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_zoom_out">Zoom out</string>
<string name="action_zoom_in">Zoom in</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_jump_to_page">Jump to page</string>
<string name="action_view_document_properties">Properties</string>
<string name="debug_action_toggle_text_layer_visibility">Toggle text layer visibility</string>
<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="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_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="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>
<string name="file_name">File name</string>
<string name="file_size">File size</string>
<string name="title">Title</string>
<string name="author">Author</string>
<string name="subject">Subject</string>
<string name="keywords">Keywords</string>
<string name="creation_date">Creation date</string>
<string name="modify_date">Modify date</string>
<string name="producer">Producer</string>
<string name="creator">Creator</string>
<string name="pdf_version">PDF version</string>
<string name="pages">Pages</string>
</resources>

View File

@ -1,15 +1,16 @@
<resources>
<resources xmlns:tools="http://schemas.android.com/tools">
<!-- 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:statusBarColor">@android:color/transparent</item>
<item name="android:navigationBarColor">@android:color/transparent</item>
<item name="android:windowLightNavigationBar" tools:ignore="NewApi">true</item>
</style>
<style name="AppTheme.AppBarOverlay" parent="ThemeOverlay.Material3.Dark" />
<style name="AppTheme.AppBarOverlay" parent="ThemeOverlay.Material3.DynamicColors.Dark" />
<style name="AppTheme.PopupOverlay" parent="ThemeOverlay.Material3.Light" />
<style name="AppTheme.PopupOverlay" parent="ThemeOverlay.Material3.DynamicColors.Light" />
</resources>

View File

@ -1,12 +1,6 @@
buildscript {
repositories {
google()
mavenCentral()
}
dependencies {
classpath("com.android.tools.build:gradle:7.1.2")
classpath("org.jetbrains.kotlin:kotlin-gradle-plugin:1.6.10")
}
plugins {
id("com.android.application") apply false
id("org.jetbrains.kotlin.android") apply false
}
allprojects {

View File

@ -1,4 +1,3 @@
org.gradle.jvmargs=-Xmx2048m -Dfile.encoding=UTF-8
android.useAndroidX=true
android.enableR8.fullMode=true
kotlin.code.style=official

File diff suppressed because it is too large Load Diff

Binary file not shown.

View File

@ -1,6 +1,8 @@
distributionBase=GRADLE_USER_HOME
distributionPath=wrapper/dists
distributionSha256Sum=8cc27038d5dbd815759851ba53e70cf62e481b87494cc97cfd97982ada5ba634
distributionUrl=https\://services.gradle.org/distributions/gradle-7.4-bin.zip
distributionSha256Sum=38f66cd6eef217b4c35855bb11ea4e9fbc53594ccccb5fb82dfd317ef8c2c5a3
distributionUrl=https\://services.gradle.org/distributions/gradle-8.2-bin.zip
networkTimeout=10000
validateDistributionUrl=true
zipStoreBase=GRADLE_USER_HOME
zipStorePath=wrapper/dists

30
gradlew vendored
View File

@ -55,7 +55,7 @@
# Darwin, MinGW, and NonStop.
#
# (3) This script is generated from the Groovy template
# https://github.com/gradle/gradle/blob/master/subprojects/plugins/src/main/resources/org/gradle/api/internal/plugins/unixStartScript.txt
# https://github.com/gradle/gradle/blob/HEAD/subprojects/plugins/src/main/resources/org/gradle/api/internal/plugins/unixStartScript.txt
# within the Gradle project.
#
# You can find Gradle at https://github.com/gradle/gradle/.
@ -80,13 +80,10 @@ do
esac
done
APP_HOME=$( cd "${APP_HOME:-./}" && pwd -P ) || exit
APP_NAME="Gradle"
# This is normally unused
# shellcheck disable=SC2034
APP_BASE_NAME=${0##*/}
# Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script.
DEFAULT_JVM_OPTS='"-Xmx64m" "-Xms64m"'
APP_HOME=$( cd "${APP_HOME:-./}" && pwd -P ) || exit
# Use the maximum available, or set MAX_FD != -1 to use that value.
MAX_FD=maximum
@ -133,22 +130,29 @@ location of your Java installation."
fi
else
JAVACMD=java
which java >/dev/null 2>&1 || die "ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH.
if ! command -v java >/dev/null 2>&1
then
die "ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH.
Please set the JAVA_HOME variable in your environment to match the
location of your Java installation."
fi
fi
# Increase the maximum file descriptors if we can.
if ! "$cygwin" && ! "$darwin" && ! "$nonstop" ; then
case $MAX_FD in #(
max*)
# In POSIX sh, ulimit -H is undefined. That's why the result is checked to see if it worked.
# shellcheck disable=SC3045
MAX_FD=$( ulimit -H -n ) ||
warn "Could not query maximum file descriptor limit"
esac
case $MAX_FD in #(
'' | soft) :;; #(
*)
# In POSIX sh, ulimit -n is undefined. That's why the result is checked to see if it worked.
# shellcheck disable=SC3045
ulimit -n "$MAX_FD" ||
warn "Could not set maximum file descriptor limit to $MAX_FD"
esac
@ -193,6 +197,10 @@ if "$cygwin" || "$msys" ; then
done
fi
# Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script.
DEFAULT_JVM_OPTS='"-Xmx64m" "-Xms64m"'
# Collect all arguments for the java command;
# * $DEFAULT_JVM_OPTS, $JAVA_OPTS, and $GRADLE_OPTS can contain fragments of
# shell script including quotes and variable substitutions, so put them in
@ -205,6 +213,12 @@ set -- \
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.
#
# With -n1 it outputs one arg per line, with the quotes and backslashes removed.

15
gradlew.bat vendored
View File

@ -14,7 +14,7 @@
@rem limitations under the License.
@rem
@if "%DEBUG%" == "" @echo off
@if "%DEBUG%"=="" @echo off
@rem ##########################################################################
@rem
@rem Gradle startup script for Windows
@ -25,7 +25,8 @@
if "%OS%"=="Windows_NT" setlocal
set DIRNAME=%~dp0
if "%DIRNAME%" == "" set DIRNAME=.
if "%DIRNAME%"=="" set DIRNAME=.
@rem This is normally unused
set APP_BASE_NAME=%~n0
set APP_HOME=%DIRNAME%
@ -40,7 +41,7 @@ if defined JAVA_HOME goto findJavaFromJavaHome
set JAVA_EXE=java.exe
%JAVA_EXE% -version >NUL 2>&1
if "%ERRORLEVEL%" == "0" goto execute
if %ERRORLEVEL% equ 0 goto execute
echo.
echo ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH.
@ -75,13 +76,15 @@ set CLASSPATH=%APP_HOME%\gradle\wrapper\gradle-wrapper.jar
:end
@rem End local scope for the variables with windows NT shell
if "%ERRORLEVEL%"=="0" goto mainEnd
if %ERRORLEVEL% equ 0 goto mainEnd
:fail
rem Set variable GRADLE_EXIT_CONSOLE if you need the _script_ return code instead of
rem the _cmd.exe /c_ return code!
if not "" == "%GRADLE_EXIT_CONSOLE%" exit 1
exit /b 1
set EXIT_CODE=%ERRORLEVEL%
if %EXIT_CODE% equ 0 set EXIT_CODE=1
if not ""=="%GRADLE_EXIT_CONSOLE%" exit %EXIT_CODE%
exit /b %EXIT_CODE%
:mainEnd
if "%OS%"=="Windows_NT" endlocal

View File

@ -1,3 +1,9 @@
pluginManagement {
repositories {
google()
mavenCentral()
}
}
dependencyResolutionManagement {
repositoriesMode.set(RepositoriesMode.FAIL_ON_PROJECT_REPOS)
repositories {

@ -1 +0,0 @@
Subproject commit a7fc0d33a11d032741f44d0b623780253747da10