Compare commits

...

16 Commits

Author SHA1 Message Date
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
13 changed files with 82 additions and 43 deletions

View File

@ -41,7 +41,7 @@ android {
applicationId = "app.grapheneos.pdfviewer" applicationId = "app.grapheneos.pdfviewer"
minSdk = 26 minSdk = 26
targetSdk = 33 targetSdk = 33
versionCode = 15 versionCode = 16
versionName = versionCode.toString() versionName = versionCode.toString()
resourceConfigurations.add("en") resourceConfigurations.add("en")
} }

View File

@ -3,13 +3,15 @@
android:targetSandboxVersion="2"> android:targetSandboxVersion="2">
<original-package android:name="org.grapheneos.pdfviewer" /> <original-package android:name="org.grapheneos.pdfviewer" />
<application android:icon="@mipmap/ic_launcher" <application android:name=".App"
android:roundIcon="@mipmap/ic_launcher" android:allowBackup="true"
android:icon="@mipmap/ic_launcher"
android:label="@string/app_name" android:label="@string/app_name"
android:theme="@style/AppTheme" android:roundIcon="@mipmap/ic_launcher"
android:allowBackup="true"> android:theme="@style/AppTheme">
<activity android:name=".PdfViewer" <activity android:name=".PdfViewer"
android:exported="true"> android:documentLaunchMode="always"
android:exported="true">
<intent-filter> <intent-filter>
<action android:name="android.intent.action.MAIN" /> <action android:name="android.intent.action.MAIN" />
<category android:name="android.intent.category.LAUNCHER" /> <category android:name="android.intent.category.LAUNCHER" />

View File

@ -73,7 +73,7 @@ function renderPage(pageNumber, zoom, prerender, prerenderTrigger=0) {
for (let i = 0; i < cache.length; i++) { for (let i = 0; i < cache.length; i++) {
const cached = cache[i]; const cached = cache[i];
if (cached.pageNumber === pageNumber && cached.zoomRatio === newZoomRatio && if (cached.pageNumber === pageNumber && cached.zoomRatio === newZoomRatio &&
cache.orientationDegrees === orientationDegrees) { cached.orientationDegrees === orientationDegrees) {
if (useRender) { if (useRender) {
cache.splice(i, 1); cache.splice(i, 1);
cache.push(cached); cache.push(cached);

View File

@ -0,0 +1,13 @@
package app.grapheneos.pdfviewer
import android.app.Application
import com.google.android.material.color.DynamicColors
class App : Application() {
override fun onCreate() {
super.onCreate()
DynamicColors.applyToActivitiesIfAvailable(this)
}
}

View File

@ -7,7 +7,12 @@ import java.io.IOException
import java.io.InputStream import java.io.InputStream
import java.io.OutputStream import java.io.OutputStream
@Throws(FileNotFoundException::class, IOException::class) @Throws(
FileNotFoundException::class,
IOException::class,
IllegalArgumentException::class,
OutOfMemoryError::class
)
fun saveAs(context: Context, existingUri: Uri, saveAs: Uri) { fun saveAs(context: Context, existingUri: Uri, saveAs: Uri) {
context.asInputStream(existingUri)?.use { inputStream -> context.asInputStream(existingUri)?.use { inputStream ->

View File

@ -44,6 +44,7 @@ import app.grapheneos.pdfviewer.databinding.PdfviewerBinding;
import app.grapheneos.pdfviewer.fragment.DocumentPropertiesFragment; import app.grapheneos.pdfviewer.fragment.DocumentPropertiesFragment;
import app.grapheneos.pdfviewer.fragment.PasswordPromptFragment; import app.grapheneos.pdfviewer.fragment.PasswordPromptFragment;
import app.grapheneos.pdfviewer.fragment.JumpToPageFragment; import app.grapheneos.pdfviewer.fragment.JumpToPageFragment;
import app.grapheneos.pdfviewer.ktx.ViewKt;
import app.grapheneos.pdfviewer.loader.DocumentPropertiesLoader; import app.grapheneos.pdfviewer.loader.DocumentPropertiesLoader;
import app.grapheneos.pdfviewer.viewModel.PasswordStatus; import app.grapheneos.pdfviewer.viewModel.PasswordStatus;
@ -331,8 +332,7 @@ public class PdfViewer extends AppCompatActivity implements LoaderManager.Loader
if (mUri != null) { if (mUri != null) {
binding.webview.evaluateJavascript("isTextSelected()", selection -> { binding.webview.evaluateJavascript("isTextSelected()", selection -> {
if (!Boolean.parseBoolean(selection)) { if (!Boolean.parseBoolean(selection)) {
if ((getWindow().getDecorView().getSystemUiVisibility() & if (getSupportActionBar().isShowing()) {
View.SYSTEM_UI_FLAG_FULLSCREEN) == 0) {
hideSystemUi(); hideSystemUi();
} else { } else {
showSystemUi(); showSystemUi();
@ -569,21 +569,12 @@ public class PdfViewer extends AppCompatActivity implements LoaderManager.Loader
} }
private void showSystemUi() { private void showSystemUi() {
getWindow().getDecorView().setSystemUiVisibility( ViewKt.showSystemUi(binding.getRoot(), getWindow());
View.SYSTEM_UI_FLAG_LAYOUT_STABLE |
View.SYSTEM_UI_FLAG_LAYOUT_HIDE_NAVIGATION |
View.SYSTEM_UI_FLAG_LAYOUT_FULLSCREEN);
getSupportActionBar().show(); getSupportActionBar().show();
} }
private void hideSystemUi() { private void hideSystemUi() {
getWindow().getDecorView().setSystemUiVisibility( ViewKt.hideSystemUi(binding.getRoot(), getWindow());
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(); getSupportActionBar().hide();
} }
@ -610,7 +601,7 @@ public class PdfViewer extends AppCompatActivity implements LoaderManager.Loader
} }
@Override @Override
public boolean onCreateOptionsMenu(Menu menu) { public boolean onCreateOptionsMenu(@NonNull Menu menu) {
super.onCreateOptionsMenu(menu); super.onCreateOptionsMenu(menu);
MenuInflater inflater = getMenuInflater(); MenuInflater inflater = getMenuInflater();
inflater.inflate(R.menu.pdf_viewer, menu); inflater.inflate(R.menu.pdf_viewer, menu);
@ -618,7 +609,7 @@ public class PdfViewer extends AppCompatActivity implements LoaderManager.Loader
} }
@Override @Override
public boolean onPrepareOptionsMenu(Menu menu) { public boolean onPrepareOptionsMenu(@NonNull Menu menu) {
final int[] ids = {R.id.action_jump_to_page, R.id.action_next, R.id.action_previous, final int[] ids = {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_first, R.id.action_last, R.id.action_rotate_clockwise,
R.id.action_rotate_counterclockwise, R.id.action_view_document_properties, R.id.action_rotate_counterclockwise, R.id.action_view_document_properties,
@ -718,8 +709,7 @@ public class PdfViewer extends AppCompatActivity implements LoaderManager.Loader
private void saveDocumentAs(Uri uri) { private void saveDocumentAs(Uri uri) {
try { try {
KtUtilsKt.saveAs(this, mUri, uri); KtUtilsKt.saveAs(this, mUri, uri);
} catch (IOException e) { } catch (IOException | OutOfMemoryError | IllegalArgumentException e) {
e.printStackTrace();
snackbar.setText(R.string.error_while_saving).show(); snackbar.setText(R.string.error_while_saving).show();
} }
} }

View File

@ -25,6 +25,14 @@ public class Utils {
return format.format(kb / 1000) + " MB (" + fileSize + " Bytes)"; return format.format(kb / 1000) + " MB (" + 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);
}
}
// Parse date as per PDF spec (complies with PDF v1.4 to v1.7) // Parse date as per PDF spec (complies with PDF v1.4 to v1.7)
public static String parseDate(String date) throws ParseException { public static String parseDate(String date) throws ParseException {
int position = 0; int position = 0;
@ -45,7 +53,7 @@ public class Utils {
if (!TextUtils.isDigitsOnly(field)) { if (!TextUtils.isDigitsOnly(field)) {
throw new ParseException("Invalid year", position); throw new ParseException("Invalid year", position);
} }
int year = Integer.parseInt(field); int year = parseIntSafely(field);
if (year > currentYear) { if (year > currentYear) {
year = currentYear; year = currentYear;
} }
@ -66,7 +74,7 @@ public class Utils {
if (!TextUtils.isDigitsOnly(field)) { if (!TextUtils.isDigitsOnly(field)) {
throw new ParseException("Invalid month", position); throw new ParseException("Invalid month", position);
} }
month = Integer.parseInt(field) - 1; month = parseIntSafely(field) - 1;
if (month > 11) { if (month > 11) {
throw new ParseException("Invalid month", position); throw new ParseException("Invalid month", position);
} }
@ -77,7 +85,7 @@ public class Utils {
if (!TextUtils.isDigitsOnly(field)) { if (!TextUtils.isDigitsOnly(field)) {
throw new ParseException("Invalid day", position); throw new ParseException("Invalid day", position);
} }
day = Integer.parseInt(field); day = parseIntSafely(field);
if (day > 31) { if (day > 31) {
throw new ParseException("Invalid day", position); throw new ParseException("Invalid day", position);
} }
@ -88,7 +96,7 @@ public class Utils {
if (!TextUtils.isDigitsOnly(field)) { if (!TextUtils.isDigitsOnly(field)) {
throw new ParseException("Invalid hours", position); throw new ParseException("Invalid hours", position);
} }
hours = Integer.parseInt(field); hours = parseIntSafely(field);
if (hours > 23) { if (hours > 23) {
throw new ParseException("Invalid hours", position); throw new ParseException("Invalid hours", position);
} }
@ -99,7 +107,7 @@ public class Utils {
if (!TextUtils.isDigitsOnly(field)) { if (!TextUtils.isDigitsOnly(field)) {
throw new ParseException("Invalid minutes", position); throw new ParseException("Invalid minutes", position);
} }
minutes = Integer.parseInt(field); minutes = parseIntSafely(field);
if (minutes > 59) { if (minutes > 59) {
throw new ParseException("Invalid minutes", position); throw new ParseException("Invalid minutes", position);
} }
@ -110,7 +118,7 @@ public class Utils {
if (!TextUtils.isDigitsOnly(field)) { if (!TextUtils.isDigitsOnly(field)) {
throw new ParseException("Invalid seconds", position); throw new ParseException("Invalid seconds", position);
} }
seconds = Integer.parseInt(field); seconds = parseIntSafely(field);
if (seconds > 59) { if (seconds > 59) {
throw new ParseException("Invalid seconds", position); throw new ParseException("Invalid seconds", position);
} }
@ -134,7 +142,7 @@ public class Utils {
if (!TextUtils.isDigitsOnly(field)) { if (!TextUtils.isDigitsOnly(field)) {
throw new ParseException("Invalid UTC offset hours", position); throw new ParseException("Invalid UTC offset hours", position);
} }
offsetHours = Integer.parseInt(field); offsetHours = parseIntSafely(field);
final int offsetHoursMinutes = offsetHours * 100 + offsetMinutes; final int offsetHoursMinutes = offsetHours * 100 + offsetMinutes;
// Validate UTC offset (UTC-12:00 to UTC+14:00) // Validate UTC offset (UTC-12:00 to UTC+14:00)
@ -157,7 +165,7 @@ public class Utils {
if (!TextUtils.isDigitsOnly(field)) { if (!TextUtils.isDigitsOnly(field)) {
throw new ParseException("Invalid UTC offset minutes", position); throw new ParseException("Invalid UTC offset minutes", position);
} }
offsetMinutes = Integer.parseInt(field); offsetMinutes = parseIntSafely(field);
if (offsetMinutes > 59) { if (offsetMinutes > 59) {
throw new ParseException("Invalid UTC offset minutes", position); throw new ParseException("Invalid UTC offset minutes", position);
} }

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

@ -132,7 +132,7 @@ public class DocumentPropertiesLoader extends AsyncTaskLoader<List<CharSequence>
final SpannableStringBuilder property = new SpannableStringBuilder(name).append(":\n"); final SpannableStringBuilder property = new SpannableStringBuilder(name).append(":\n");
final String value = json != null ? json.optString(specName, "-") : specName; final String value = json != null ? json.optString(specName, "-") : specName;
if (specName.endsWith("Date")) { if (specName != null && specName.endsWith("Date")) {
final Context context = getContext(); final Context context = getContext();
try { try {
property.append(value.equals("-") ? value : Utils.parseDate(value)); property.append(value.equals("-") ? value : Utils.parseDate(value));

View File

@ -14,7 +14,6 @@
android:id="@+id/toolbar" android:id="@+id/toolbar"
android:layout_width="match_parent" android:layout_width="match_parent"
android:layout_height="?attr/actionBarSize" android:layout_height="?attr/actionBarSize"
android:background="#212121"
app:popupTheme="@style/AppTheme.PopupOverlay" /> app:popupTheme="@style/AppTheme.PopupOverlay" />
</com.google.android.material.appbar.AppBarLayout> </com.google.android.material.appbar.AppBarLayout>

View File

@ -1,13 +1,14 @@
<resources> <resources xmlns:tools="http://schemas.android.com/tools">
<!-- Base application theme. --> <!-- Base application theme. -->
<style name="AppTheme" parent="Theme.Material3.DayNight.NoActionBar"> <style name="AppTheme" parent="Theme.Material3.DayNight.NoActionBar">
<!-- Customize your theme here. --> <!-- Customize your theme here. -->
<item name="colorPrimary">#DEFFFFFF</item> <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:navigationBarColor">@android:color/transparent</item>
<item name="android:windowLightNavigationBar" tools:ignore="NewApi">false</item>
</style> </style>
<style name="AppTheme.PopupOverlay" parent="ThemeOverlay.Material3.Dark" /> <style name="AppTheme.PopupOverlay" parent="ThemeOverlay.Material3.DynamicColors.Dark" />
</resources> </resources>

View File

@ -1,15 +1,16 @@
<resources> <resources xmlns:tools="http://schemas.android.com/tools">
<!-- Base application theme. --> <!-- Base application theme. -->
<style name="AppTheme" parent="Theme.Material3.DayNight.NoActionBar"> <style name="AppTheme" parent="Theme.Material3.DayNight.NoActionBar">
<!-- Customize your theme here. --> <!-- Customize your theme here. -->
<item name="colorPrimary">#000000</item> <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:navigationBarColor">@android:color/transparent</item>
<item name="android:windowLightNavigationBar" tools:ignore="NewApi">true</item>
</style> </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> </resources>

View File

@ -11,7 +11,7 @@ buildscript {
} }
dependencies { dependencies {
classpath("com.android.tools.build:gradle:7.3.0") classpath("com.android.tools.build:gradle:7.3.0")
classpath("org.jetbrains.kotlin:kotlin-gradle-plugin:1.7.10") classpath("org.jetbrains.kotlin:kotlin-gradle-plugin:1.7.20")
} }
} }