Accept intents to load a single PDF

Also refactor Presentation
This commit is contained in:
Luke Hubmayer-Werner 2024-08-18 00:05:02 +09:30
parent 75be87a8fe
commit 7cf7731541
4 changed files with 126 additions and 71 deletions

View File

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

View File

@ -16,12 +16,17 @@
android:name=".MainActivity"
android:exported="true"
android:label="@string/app_name"
android:launchMode="singleInstance"
android:theme="@style/Theme.PDFcast.NoActionBar">
<intent-filter>
<action android:name="android.intent.action.MAIN" />
<category android:name="android.intent.category.LAUNCHER" />
</intent-filter>
<intent-filter android:label="Read PDF">
<action android:name="android.intent.action.VIEW" />
<category android:name="android.intent.category.DEFAULT" />
<data android:mimeType="application/pdf" android:scheme="content" />
</intent-filter>
</activity>
</application>

View File

@ -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<Boolean>()
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)

View File

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