From 7cf773154118ca7ec4e0bc3b4b46749c6c303335 Mon Sep 17 00:00:00 2001 From: Luke Hubmayer-Werner Date: Sun, 18 Aug 2024 00:05:02 +0930 Subject: [PATCH] Accept intents to load a single PDF Also refactor Presentation --- README.md | 4 +- app/src/main/AndroidManifest.xml | 7 +- app/src/main/java/com/lhw/pdf/MainActivity.kt | 136 +++++++++--------- .../main/java/com/lhw/pdf/MyPresentation.kt | 50 +++++++ 4 files changed, 126 insertions(+), 71 deletions(-) create mode 100644 app/src/main/java/com/lhw/pdf/MyPresentation.kt diff --git a/README.md b/README.md index 74b97ca..5a94343 100644 --- a/README.md +++ b/README.md @@ -4,7 +4,9 @@ Minimum Viable Goals: - [x] Presentation View - i.e. show on an external monitor - haven't seen another app do this yet! - [x] Immersive View - no system status bar etc. - [x] Autocrop margins -- [ ] Open files the Android way - currently just a bundled test file +- [ ] Open files the Android way +- - [x] Accept intents to load a single PDF +- - [ ] Built-in file browser with requisite permissions - [ ] Delete+Reorder Pages - doesn't have to save the source file, but does need to save a change journal - - [x] Tap thumbnails to hide/show pages - - [ ] Drag thumbnails to rearrange pages diff --git a/app/src/main/AndroidManifest.xml b/app/src/main/AndroidManifest.xml index edb436f..b8a1c14 100644 --- a/app/src/main/AndroidManifest.xml +++ b/app/src/main/AndroidManifest.xml @@ -16,12 +16,17 @@ android:name=".MainActivity" android:exported="true" android:label="@string/app_name" + android:launchMode="singleInstance" android:theme="@style/Theme.PDFcast.NoActionBar"> - + + + + + diff --git a/app/src/main/java/com/lhw/pdf/MainActivity.kt b/app/src/main/java/com/lhw/pdf/MainActivity.kt index 4b6e2d7..5ebca8c 100644 --- a/app/src/main/java/com/lhw/pdf/MainActivity.kt +++ b/app/src/main/java/com/lhw/pdf/MainActivity.kt @@ -1,17 +1,12 @@ package com.lhw.pdf -import android.app.Presentation import android.content.Context +import android.content.Intent import android.graphics.PorterDuff import android.media.MediaRouter import android.os.Bundle -import android.util.DisplayMetrics -import android.view.Display import android.view.Menu -import android.view.WindowManager -import android.widget.HorizontalScrollView import android.widget.ImageView -import android.widget.LinearLayout import com.google.android.material.snackbar.Snackbar import com.google.android.material.navigation.NavigationView import androidx.navigation.findNavController @@ -33,15 +28,15 @@ import kotlin.math.min class MainActivity : AppCompatActivity() { private lateinit var appBarConfiguration: AppBarConfiguration private lateinit var binding: ActivityMainBinding + private lateinit var mediaRouter: MediaRouter private val thumbnailWidth = 500 private val thumbnailHeight = 700 private var renderAutoCrop = true private var pagesPerLandscape = 3F - private val presentationDisplayMetrics = DisplayMetrics() + private var presentation: MyPresentation? = null private lateinit var pdfDocument: PdfDocument - private lateinit var presentationScroll: HorizontalScrollView - private lateinit var presentationLayout: LinearLayout private val showPages = mutableListOf() + private val defaultCachedFileName = "cached.pdf" private fun inputStreamToCache(outputFilename: String, inputStream: InputStream): File { val fileCached = File(cacheDir, outputFilename) @@ -54,66 +49,31 @@ class MainActivity : AppCompatActivity() { return fileCached } - private fun initializePdfDocument() { - val inputStream = resources.openRawResource(R.raw.testpdf) - pdfDocument = PdfDocument(inputStreamToCache("cached.pdf", inputStream), true) - } - - class MyPresentation(outerContext: Context, display: Display) : Presentation(outerContext, display) { - - } - - private fun updatePresentationImages(rerender: Boolean = true) { - if (rerender) { - val maxHeight: Int = presentationDisplayMetrics.heightPixels - val maxWidth: Int = min(maxHeight*5/7, (presentationDisplayMetrics.widthPixels/pagesPerLandscape).toInt()) - pdfDocument.renderPagesPresentation(maxWidth, maxHeight, renderAutoCrop) - } - presentationLayout.removeAllViewsInLayout() - showPages.withIndex().filter { (i, show) -> (show) }.forEach { (i, show) -> - val bitmap = pdfDocument.bitmapPagesPresentation[i] - val img = ImageView(this) - img.setImageBitmap(bitmap) - presentationLayout.addView(img) - } - } - - private fun presentationView() { - val mediaRouter = getSystemService(Context.MEDIA_ROUTER_SERVICE) as MediaRouter - val presentationDisplay = mediaRouter.getSelectedRoute(MediaRouter.ROUTE_TYPE_LIVE_VIDEO)?.presentationDisplay - if (presentationDisplay != null) { - val presentation = MyPresentation(this, presentationDisplay) - when (val pw = presentation.window) { - null -> {} - else -> { - WindowCompat.getInsetsController(pw, pw.decorView).hide(WindowInsetsCompat.Type.systemBars()) - } + private fun updatePresentationImages(reRender: Boolean = true) { + presentation?.let { p -> + if (reRender) { + println(p.displayMetrics) + val maxHeight: Int = p.displayMetrics.heightPixels + val maxWidth: Int = min(maxHeight * 5 / 7, (p.displayMetrics.widthPixels / pagesPerLandscape).toInt()) + println("Rendering pages with maxHeight=$maxHeight maxWidth=$maxWidth") + pdfDocument.renderPagesPresentation(maxWidth, maxHeight, renderAutoCrop) } - - @Suppress("DEPRECATION") - presentationDisplay.getMetrics(presentationDisplayMetrics) - presentationLayout = LinearLayout(presentation.context) - presentationScroll = HorizontalScrollView(presentation.context) - presentationScroll.addView(presentationLayout) - val layoutParams = WindowManager.LayoutParams() - presentation.addContentView(presentationScroll, layoutParams) - updatePresentationImages() - presentation.show() + val bitmaps = showPages.withIndex() + .mapNotNull { (i, show) -> if (show) pdfDocument.bitmapPagesPresentation[i] else null } + .toList() + p.updateImages(bitmaps) } } - override fun onCreate(savedInstanceState: Bundle?) { - super.onCreate(savedInstanceState) - val windowInsetsController = WindowCompat.getInsetsController(window, window.decorView) - windowInsetsController.hide(WindowInsetsCompat.Type.systemBars()) + private fun makePresentationView() { + mediaRouter.getSelectedRoute(MediaRouter.ROUTE_TYPE_LIVE_VIDEO)?.presentationDisplay?.let { display -> + presentation = MyPresentation(this, display) + updatePresentationImages() + presentation?.show() + } + } - binding = ActivityMainBinding.inflate(layoutInflater) - setContentView(binding.root) - - setSupportActionBar(binding.appBarMain.toolbar) - - initializePdfDocument() - pdfDocument.renderThumbnails(thumbnailWidth, thumbnailHeight) + private fun populateThumbnails() { val container = binding.appBarMain.contentMain.thumbnailsLayout for ((index, bitmap) in pdfDocument.bitmapThumbnails) { val img = ImageView(this) @@ -126,14 +86,36 @@ class MainActivity : AppCompatActivity() { container.addView(img) showPages.add(true) } - presentationView() + } + override fun onCreate(savedInstanceState: Bundle?) { + super.onCreate(savedInstanceState) + mediaRouter = getSystemService(Context.MEDIA_ROUTER_SERVICE) as MediaRouter + //val windowInsetsController = WindowCompat.getInsetsController(window, window.decorView) + //windowInsetsController.hide(WindowInsetsCompat.Type.systemBars()) //Doesn't seem to actually fill the vacant space by default + + binding = ActivityMainBinding.inflate(layoutInflater) + setContentView(binding.root) + + setSupportActionBar(binding.appBarMain.toolbar) + + // Load previous pdf, or included test pdf first + var file = File(cacheDir, defaultCachedFileName) + if (!file.exists()) { + file = inputStreamToCache(defaultCachedFileName, resources.openRawResource(R.raw.testpdf)) + } + pdfDocument = PdfDocument(file, true) + // Then overwrite it with any pdf sent via intent + handleIntent(intent) + + pdfDocument.renderThumbnails(thumbnailWidth, thumbnailHeight) + populateThumbnails() + makePresentationView() + + val container = binding.appBarMain.contentMain.thumbnailsLayout val containerScroll = binding.appBarMain.contentMain.thumbnailsScroll containerScroll.viewTreeObserver.addOnScrollChangedListener { - presentationScroll.scrollTo( - containerScroll.scrollX * (presentationLayout.width - presentationScroll.width) / (container.width - containerScroll.width), - 0 - ) + presentation?.setScrollProgress(containerScroll.scrollX.toFloat() / (container.width - containerScroll.width)) } binding.appBarMain.crop.setOnClickListener { view -> @@ -172,6 +154,22 @@ class MainActivity : AppCompatActivity() { navView.setupWithNavController(navController) } + private fun handleIntent(intent: Intent?) { + val uri = intent?.data + if (uri == null) { + println("null intent uri passed") + return + } + println("intent uri passed: $uri") + // Do something with the URI to load PDF file + pdfDocument = contentResolver.openInputStream(uri)?.let { PdfDocument(inputStreamToCache(defaultCachedFileName, it), true) } ?: pdfDocument + } + + override fun onNewIntent(intent: Intent?) { + super.onNewIntent(intent) + handleIntent(intent) + } + override fun onCreateOptionsMenu(menu: Menu): Boolean { // Inflate the menu; this adds items to the action bar if it is present. menuInflater.inflate(R.menu.main, menu) diff --git a/app/src/main/java/com/lhw/pdf/MyPresentation.kt b/app/src/main/java/com/lhw/pdf/MyPresentation.kt new file mode 100644 index 0000000..6499a85 --- /dev/null +++ b/app/src/main/java/com/lhw/pdf/MyPresentation.kt @@ -0,0 +1,50 @@ +package com.lhw.pdf + +import android.app.Presentation +import android.content.Context +import android.graphics.Bitmap +import android.util.DisplayMetrics +import android.view.Display +import android.view.WindowManager +import android.widget.HorizontalScrollView +import android.widget.ImageView +import android.widget.LinearLayout +import androidx.core.view.WindowCompat +import androidx.core.view.WindowInsetsCompat + +class MyPresentation(outerContext: Context, display: Display) : Presentation(outerContext, display) { + val displayMetrics = DisplayMetrics() + private val layout = LinearLayout(this.context) + private val scroll = HorizontalScrollView(this.context) + private val layoutParams = WindowManager.LayoutParams() + init { + @Suppress("DEPRECATION") + display.getMetrics(displayMetrics) + window?.let { + WindowCompat.getInsetsController(it, it.decorView) + .hide(WindowInsetsCompat.Type.systemBars()) + } + scroll.addView(layout) + println(layoutParams) + addContentView(scroll, layoutParams) + } + + fun updateImages(bitmaps: Iterable) { + println("Updating presentation images") + println("Layout has scale ${layout.scaleX} ${layout.scaleY}") + layout.removeAllViewsInLayout() + bitmaps.forEach { + val img = ImageView(this.context) + img.setImageBitmap(it) + layout.addView(img) + img.minimumWidth = it.width // No idea what broke when I refactored this class + img.minimumHeight = it.height + println("${it.width}x${it.height}") + } + println("Presentation heights ${scroll.height} ${layout.height}") + } + + fun setScrollProgress(progress: Float) { + scroll.scrollTo((progress * (layout.width - scroll.width)).toInt(), 0) + } +}