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] 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] Immersive View - no system status bar etc.
- [x] Autocrop margins - [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 - [ ] 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 - - [x] Tap thumbnails to hide/show pages
- - [ ] Drag thumbnails to rearrange pages - - [ ] Drag thumbnails to rearrange pages

View File

@ -16,12 +16,17 @@
android:name=".MainActivity" android:name=".MainActivity"
android:exported="true" android:exported="true"
android:label="@string/app_name" android:label="@string/app_name"
android:launchMode="singleInstance"
android:theme="@style/Theme.PDFcast.NoActionBar"> android:theme="@style/Theme.PDFcast.NoActionBar">
<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" />
</intent-filter> </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> </activity>
</application> </application>

View File

@ -1,17 +1,12 @@
package com.lhw.pdf package com.lhw.pdf
import android.app.Presentation
import android.content.Context import android.content.Context
import android.content.Intent
import android.graphics.PorterDuff import android.graphics.PorterDuff
import android.media.MediaRouter import android.media.MediaRouter
import android.os.Bundle import android.os.Bundle
import android.util.DisplayMetrics
import android.view.Display
import android.view.Menu import android.view.Menu
import android.view.WindowManager
import android.widget.HorizontalScrollView
import android.widget.ImageView import android.widget.ImageView
import android.widget.LinearLayout
import com.google.android.material.snackbar.Snackbar import com.google.android.material.snackbar.Snackbar
import com.google.android.material.navigation.NavigationView import com.google.android.material.navigation.NavigationView
import androidx.navigation.findNavController import androidx.navigation.findNavController
@ -33,15 +28,15 @@ import kotlin.math.min
class MainActivity : AppCompatActivity() { class MainActivity : AppCompatActivity() {
private lateinit var appBarConfiguration: AppBarConfiguration private lateinit var appBarConfiguration: AppBarConfiguration
private lateinit var binding: ActivityMainBinding private lateinit var binding: ActivityMainBinding
private lateinit var mediaRouter: MediaRouter
private val thumbnailWidth = 500 private val thumbnailWidth = 500
private val thumbnailHeight = 700 private val thumbnailHeight = 700
private var renderAutoCrop = true private var renderAutoCrop = true
private var pagesPerLandscape = 3F private var pagesPerLandscape = 3F
private val presentationDisplayMetrics = DisplayMetrics() private var presentation: MyPresentation? = null
private lateinit var pdfDocument: PdfDocument private lateinit var pdfDocument: PdfDocument
private lateinit var presentationScroll: HorizontalScrollView
private lateinit var presentationLayout: LinearLayout
private val showPages = mutableListOf<Boolean>() private val showPages = mutableListOf<Boolean>()
private val defaultCachedFileName = "cached.pdf"
private fun inputStreamToCache(outputFilename: String, inputStream: InputStream): File { private fun inputStreamToCache(outputFilename: String, inputStream: InputStream): File {
val fileCached = File(cacheDir, outputFilename) val fileCached = File(cacheDir, outputFilename)
@ -54,66 +49,31 @@ class MainActivity : AppCompatActivity() {
return fileCached return fileCached
} }
private fun initializePdfDocument() { private fun updatePresentationImages(reRender: Boolean = true) {
val inputStream = resources.openRawResource(R.raw.testpdf) presentation?.let { p ->
pdfDocument = PdfDocument(inputStreamToCache("cached.pdf", inputStream), true) if (reRender) {
} println(p.displayMetrics)
val maxHeight: Int = p.displayMetrics.heightPixels
class MyPresentation(outerContext: Context, display: Display) : Presentation(outerContext, display) { val maxWidth: Int = min(maxHeight * 5 / 7, (p.displayMetrics.widthPixels / pagesPerLandscape).toInt())
println("Rendering pages with maxHeight=$maxHeight maxWidth=$maxWidth")
}
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) pdfDocument.renderPagesPresentation(maxWidth, maxHeight, renderAutoCrop)
} }
presentationLayout.removeAllViewsInLayout() val bitmaps = showPages.withIndex()
showPages.withIndex().filter { (i, show) -> (show) }.forEach { (i, show) -> .mapNotNull { (i, show) -> if (show) pdfDocument.bitmapPagesPresentation[i] else null }
val bitmap = pdfDocument.bitmapPagesPresentation[i] .toList()
val img = ImageView(this) p.updateImages(bitmaps)
img.setImageBitmap(bitmap)
presentationLayout.addView(img)
} }
} }
private fun presentationView() { private fun makePresentationView() {
val mediaRouter = getSystemService(Context.MEDIA_ROUTER_SERVICE) as MediaRouter mediaRouter.getSelectedRoute(MediaRouter.ROUTE_TYPE_LIVE_VIDEO)?.presentationDisplay?.let { display ->
val presentationDisplay = mediaRouter.getSelectedRoute(MediaRouter.ROUTE_TYPE_LIVE_VIDEO)?.presentationDisplay presentation = MyPresentation(this, display)
if (presentationDisplay != null) {
val presentation = MyPresentation(this, presentationDisplay)
when (val pw = presentation.window) {
null -> {}
else -> {
WindowCompat.getInsetsController(pw, pw.decorView).hide(WindowInsetsCompat.Type.systemBars())
}
}
@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() updatePresentationImages()
presentation.show() presentation?.show()
} }
} }
override fun onCreate(savedInstanceState: Bundle?) { private fun populateThumbnails() {
super.onCreate(savedInstanceState)
val windowInsetsController = WindowCompat.getInsetsController(window, window.decorView)
windowInsetsController.hide(WindowInsetsCompat.Type.systemBars())
binding = ActivityMainBinding.inflate(layoutInflater)
setContentView(binding.root)
setSupportActionBar(binding.appBarMain.toolbar)
initializePdfDocument()
pdfDocument.renderThumbnails(thumbnailWidth, thumbnailHeight)
val container = binding.appBarMain.contentMain.thumbnailsLayout val container = binding.appBarMain.contentMain.thumbnailsLayout
for ((index, bitmap) in pdfDocument.bitmapThumbnails) { for ((index, bitmap) in pdfDocument.bitmapThumbnails) {
val img = ImageView(this) val img = ImageView(this)
@ -126,14 +86,36 @@ class MainActivity : AppCompatActivity() {
container.addView(img) container.addView(img)
showPages.add(true) 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 val containerScroll = binding.appBarMain.contentMain.thumbnailsScroll
containerScroll.viewTreeObserver.addOnScrollChangedListener { containerScroll.viewTreeObserver.addOnScrollChangedListener {
presentationScroll.scrollTo( presentation?.setScrollProgress(containerScroll.scrollX.toFloat() / (container.width - containerScroll.width))
containerScroll.scrollX * (presentationLayout.width - presentationScroll.width) / (container.width - containerScroll.width),
0
)
} }
binding.appBarMain.crop.setOnClickListener { view -> binding.appBarMain.crop.setOnClickListener { view ->
@ -172,6 +154,22 @@ class MainActivity : AppCompatActivity() {
navView.setupWithNavController(navController) 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 { override fun onCreateOptionsMenu(menu: Menu): Boolean {
// Inflate the menu; this adds items to the action bar if it is present. // Inflate the menu; this adds items to the action bar if it is present.
menuInflater.inflate(R.menu.main, menu) 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)
}
}