Accept intents to load a single PDF
Also refactor Presentation
This commit is contained in:
parent
75be87a8fe
commit
7cf7731541
|
@ -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
|
||||||
|
|
|
@ -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>
|
||||||
|
|
||||||
|
|
|
@ -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")
|
||||||
}
|
pdfDocument.renderPagesPresentation(maxWidth, maxHeight, renderAutoCrop)
|
||||||
|
|
||||||
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())
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
val bitmaps = showPages.withIndex()
|
||||||
@Suppress("DEPRECATION")
|
.mapNotNull { (i, show) -> if (show) pdfDocument.bitmapPagesPresentation[i] else null }
|
||||||
presentationDisplay.getMetrics(presentationDisplayMetrics)
|
.toList()
|
||||||
presentationLayout = LinearLayout(presentation.context)
|
p.updateImages(bitmaps)
|
||||||
presentationScroll = HorizontalScrollView(presentation.context)
|
|
||||||
presentationScroll.addView(presentationLayout)
|
|
||||||
val layoutParams = WindowManager.LayoutParams()
|
|
||||||
presentation.addContentView(presentationScroll, layoutParams)
|
|
||||||
updatePresentationImages()
|
|
||||||
presentation.show()
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
override fun onCreate(savedInstanceState: Bundle?) {
|
private fun makePresentationView() {
|
||||||
super.onCreate(savedInstanceState)
|
mediaRouter.getSelectedRoute(MediaRouter.ROUTE_TYPE_LIVE_VIDEO)?.presentationDisplay?.let { display ->
|
||||||
val windowInsetsController = WindowCompat.getInsetsController(window, window.decorView)
|
presentation = MyPresentation(this, display)
|
||||||
windowInsetsController.hide(WindowInsetsCompat.Type.systemBars())
|
updatePresentationImages()
|
||||||
|
presentation?.show()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
binding = ActivityMainBinding.inflate(layoutInflater)
|
private fun populateThumbnails() {
|
||||||
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)
|
||||||
|
|
|
@ -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)
|
||||||
|
}
|
||||||
|
}
|
Loading…
Reference in New Issue