Merge pull request #1266 from omnivore-app/feature/pspdfkit-android-integration

PSPDFKit Android
This commit is contained in:
Satindar Dhillon
2022-10-06 17:54:40 -07:00
committed by GitHub
16 changed files with 252 additions and 12 deletions

View File

@ -156,6 +156,7 @@ dependencies {
implementation 'io.coil-kt:coil-compose:2.2.0'
implementation 'com.google.code.gson:gson:2.8.6'
implementation 'com.pspdfkit:pspdfkit:8.4.1'
}
apollo {

View File

@ -15,6 +15,7 @@
android:roundIcon="@mipmap/ic_launcher_round"
android:supportsRtl="true"
android:theme="@style/Theme.Omnivore"
android:largeHeap="true"
tools:targetApi="31">
<activity
@ -38,5 +39,15 @@
<data android:mimeType="text/*" />
</intent-filter>
</activity>
<activity
android:name="com.pspdfkit.ui.PdfActivity"
android:theme="@style/Theme.AppCompat.NoActionBar"
android:windowSoftInputMode="adjustNothing" />
<activity
android:name=".ui.reader.PDFReaderActivity"
android:theme="@style/Theme.AppCompat.NoActionBar"
android:windowSoftInputMode="adjustNothing" />
</application>
</manifest>

Binary file not shown.

View File

@ -16,6 +16,7 @@ import androidx.core.view.WindowCompat
import androidx.core.view.WindowInsetsCompat
import app.omnivore.omnivore.ui.auth.LoginViewModel
import app.omnivore.omnivore.ui.home.HomeViewModel
import app.omnivore.omnivore.ui.reader.PDFReaderViewModel
import app.omnivore.omnivore.ui.reader.WebReaderViewModel
import app.omnivore.omnivore.ui.root.RootView
import dagger.hilt.android.AndroidEntryPoint

View File

@ -12,3 +12,4 @@ data class Highlight(
val updatedAt: Any?,
val createdByMe : Boolean,
)

View File

@ -26,4 +26,9 @@ data class LinkedItem(
fun publisherDisplayName(): String? {
return publisherURLString?.toUri()?.host
}
fun isPDF(): Boolean {
val hasPDFSuffix = pageURLString.endsWith("pdf")
return contentReader == "PDF" || hasPDFSuffix
}
}

View File

@ -1,5 +1,6 @@
package app.omnivore.omnivore.ui.home
import android.content.Intent
import androidx.compose.foundation.background
import androidx.compose.foundation.layout.*
import androidx.compose.foundation.lazy.LazyColumn
@ -11,10 +12,12 @@ import androidx.compose.runtime.*
import androidx.compose.runtime.livedata.observeAsState
import androidx.compose.ui.Alignment
import androidx.compose.ui.Modifier
import androidx.compose.ui.platform.LocalContext
import androidx.compose.ui.unit.dp
import androidx.navigation.NavHostController
import app.omnivore.omnivore.Routes
import app.omnivore.omnivore.models.LinkedItem
import app.omnivore.omnivore.ui.reader.PDFReaderActivity
import kotlinx.coroutines.flow.distinctUntilChanged
@ -53,6 +56,7 @@ fun HomeViewContent(
navController: NavHostController,
modifier: Modifier
) {
val context = LocalContext.current
val listState = rememberLazyListState()
val linkedItems: List<LinkedItem> by homeViewModel.itemsLiveData.observeAsState(listOf())
@ -69,7 +73,13 @@ fun HomeViewContent(
LinkedItemCard(
item = item,
onClickHandler = {
navController.navigate("WebReader/${item.slug}")
if (item.isPDF()) {
val intent = Intent(context, PDFReaderActivity::class.java)
intent.putExtra("LINKED_ITEM_SLUG", item.slug)
context.startActivity(intent)
} else {
navController.navigate("WebReader/${item.slug}")
}
}
)
}

View File

@ -24,7 +24,7 @@ class HomeViewModel @Inject constructor(
private var receivedIdx = 0
// Live Data
val searchTextLiveData = MutableLiveData<String>("")
val searchTextLiveData = MutableLiveData("")
val itemsLiveData = MutableLiveData<List<LinkedItem>>(listOf())
fun updateSearchText(text: String) {

View File

@ -0,0 +1,94 @@
package app.omnivore.omnivore.ui.reader
import android.graphics.RectF
import android.net.Uri
import android.os.Bundle
import android.util.Log
import androidx.activity.viewModels
import androidx.annotation.UiThread
import androidx.appcompat.app.AppCompatActivity
import androidx.lifecycle.Observer
import app.omnivore.omnivore.R
import com.google.gson.Gson
import com.pspdfkit.annotations.HighlightAnnotation
import com.pspdfkit.configuration.PdfConfiguration
import com.pspdfkit.configuration.page.PageScrollDirection
import com.pspdfkit.document.PdfDocument
import com.pspdfkit.listeners.DocumentListener
import com.pspdfkit.ui.PdfFragment
import dagger.hilt.android.AndroidEntryPoint
@AndroidEntryPoint
class PDFReaderActivity: AppCompatActivity(), DocumentListener {
private var hasLoadedHighlights = false
private lateinit var fragment: PdfFragment
val viewModel: PDFReaderViewModel by viewModels()
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
setContentView(R.layout.pdf_reader_fragment)
// Create the observer which updates the UI.
val pdfParamsObserver = Observer<PDFReaderParams?> { params ->
if (params != null) {
load(params)
}
}
// Observe the LiveData, passing in this activity as the LifecycleOwner and the observer.
viewModel.pdfReaderParamsLiveData.observe(this, pdfParamsObserver)
val slug = intent.getStringExtra("LINKED_ITEM_SLUG") ?: ""
viewModel.loadItem(slug, this)
}
private fun load(params: PDFReaderParams) {
val configuration = PdfConfiguration.Builder()
.scrollDirection(PageScrollDirection.HORIZONTAL)
.build()
// First, try to restore a previously created fragment.
// If no fragment exists, create a new one.
fragment = supportFragmentManager.findFragmentById(R.id.fragmentContainer) as PdfFragment?
?: createFragment(params.localFileUri, configuration)
fragment.apply {
addDocumentListener(this@PDFReaderActivity)
}
}
override fun onDocumentLoaded(document: PdfDocument) {
if (hasLoadedHighlights) return
hasLoadedHighlights = true
val params = viewModel.pdfReaderParamsLiveData.value
params?.let {
for (highlight in it.articleContent.highlights) {
val highlightAnnotation = fragment
.document
?.annotationProvider
?.createAnnotationFromInstantJson(highlight.patch)
highlightAnnotation?.let {
fragment.addAnnotationToPage(highlightAnnotation, true)
}
}
fragment.scrollTo(
RectF(0f, 0f, 0f, 0f),
params.item.readingProgressAnchor,
0,
true
)
}
}
private fun createFragment(documentUri: Uri, configuration: PdfConfiguration): PdfFragment {
val fragment = PdfFragment.newInstance(documentUri, configuration)
supportFragmentManager.beginTransaction()
.replace(R.id.fragmentContainer, fragment)
.commit()
return fragment
}
}

View File

@ -0,0 +1,77 @@
package app.omnivore.omnivore.ui.reader
import android.content.Context
import android.net.Uri
import android.util.Log
import androidx.lifecycle.MutableLiveData
import androidx.lifecycle.ViewModel
import androidx.lifecycle.viewModelScope
import app.omnivore.omnivore.DatastoreRepository
import app.omnivore.omnivore.models.LinkedItem
import app.omnivore.omnivore.networking.Networker
import app.omnivore.omnivore.networking.linkedItem
import com.google.gson.Gson
import com.pspdfkit.annotations.Annotation
import com.pspdfkit.document.download.DownloadJob
import com.pspdfkit.document.download.DownloadRequest
import com.pspdfkit.document.download.Progress
import dagger.hilt.android.lifecycle.HiltViewModel
import kotlinx.coroutines.launch
import java.io.File
import javax.inject.Inject
data class PDFReaderParams(
val item: LinkedItem,
val articleContent: ArticleContent,
val localFileUri: Uri
)
@HiltViewModel
class PDFReaderViewModel @Inject constructor(
private val datastoreRepo: DatastoreRepository,
private val networker: Networker
): ViewModel() {
val pdfReaderParamsLiveData = MutableLiveData<PDFReaderParams?>(null)
var annotations: List<Annotation> = listOf()
fun loadItem(slug: String, context: Context) {
viewModelScope.launch {
val articleQueryResult = networker.linkedItem(slug)
val article = articleQueryResult.item ?: return@launch
val request = DownloadRequest.Builder(context)
.uri(article.pageURLString)
.build()
val job = DownloadJob.startDownload(request)
job.setProgressListener(object : DownloadJob.ProgressListenerAdapter() {
override fun onProgress(progress: Progress) {
// progressBar.setProgress((100f * progress.bytesReceived / progress.totalBytes).toInt())
}
override fun onComplete(output: File) {
val articleContent = ArticleContent(
title = article.title,
htmlContent = article.content ?: "",
highlights = articleQueryResult.highlights,
contentStatus = "SUCCEEDED",
objectID = "",
labelsJSONString = Gson().toJson(articleQueryResult.labels)
)
pdfReaderParamsLiveData.postValue(PDFReaderParams(article, articleContent, Uri.fromFile(output)))
}
override fun onError(exception: Throwable) {
// handleDownloadError(exception)
}
})
}
}
fun reset() {
pdfReaderParamsLiveData.postValue(null)
}
}

View File

@ -17,9 +17,7 @@ import androidx.compose.runtime.mutableStateOf
import androidx.compose.runtime.remember
import androidx.compose.ui.viewinterop.AndroidView
import app.omnivore.omnivore.R
import app.omnivore.omnivore.networking.ReadingProgressParams
import com.google.gson.Gson
import org.json.JSONObject
@Composable

View File

@ -1,7 +1,9 @@
package app.omnivore.omnivore.ui.reader
import android.util.Log
import app.omnivore.omnivore.models.Highlight
import app.omnivore.omnivore.models.LinkedItem
import com.google.gson.Gson
enum class WebFont(val displayText: String, val rawValue: String) {
INTER("Inter", "Inter"),
@ -26,11 +28,15 @@ enum class ArticleContentStatus(val rawValue: String) {
data class ArticleContent(
val title: String,
val htmlContent: String,
val highlightsJSONString: String,
val highlights: List<Highlight>,
val contentStatus: String, // ArticleContentStatus,
val objectID: String?, // whatever the Room Equivalent of objectID is
val labelsJSONString: String
)
) {
fun highlightsJSONString(): String {
return Gson().toJson(highlights)
}
}
data class WebReaderContent(
val textFontSize: Int,
@ -86,7 +92,7 @@ data class WebReaderContent(
readingProgressPercent: ${item.readingProgress},
readingProgressAnchorIndex: ${item.readingProgressAnchor},
labels: ${articleContent.labelsJSONString},
highlights: ${articleContent.highlightsJSONString},
highlights: ${articleContent.highlightsJSONString()},
}
window.fontSize = $textFontSize

View File

@ -39,7 +39,7 @@ class WebReaderViewModel @Inject constructor(
val articleContent = ArticleContent(
title = article.title,
htmlContent = article.content ?: "",
highlightsJSONString = Gson().toJson(articleQueryResult.highlights),
highlights = articleQueryResult.highlights,
contentStatus = "SUCCEEDED",
objectID = "",
labelsJSONString = Gson().toJson(articleQueryResult.labels)

View File

@ -18,10 +18,7 @@ import app.omnivore.omnivore.ui.auth.LoginViewModel
import app.omnivore.omnivore.ui.auth.WelcomeScreen
import app.omnivore.omnivore.ui.home.HomeView
import app.omnivore.omnivore.ui.home.HomeViewModel
import app.omnivore.omnivore.ui.reader.ArticleWebView
import app.omnivore.omnivore.ui.reader.WebReader
import app.omnivore.omnivore.ui.reader.WebReaderLoadingContainer
import app.omnivore.omnivore.ui.reader.WebReaderViewModel
import app.omnivore.omnivore.ui.reader.*
import com.google.accompanist.systemuicontroller.rememberSystemUiController
@Composable

View File

@ -0,0 +1,36 @@
<FrameLayout xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:tools="http://schemas.android.com/tools"
android:layout_width="match_parent"
android:layout_height="match_parent"
tools:ignore="UnusedAttribute">
<FrameLayout
android:id="@+id/fragmentContainer"
android:layout_width="match_parent"
android:layout_height="match_parent"/>
<com.pspdfkit.ui.PdfThumbnailBar
android:id="@+id/thumbnailBar"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_gravity="bottom"/>
<com.pspdfkit.ui.search.PdfSearchViewModular
android:id="@+id/modularSearchView"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:elevation="8dp"
android:visibility="visible"/>
<com.pspdfkit.ui.PdfThumbnailGrid
android:id="@+id/thumbnailGrid"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:visibility="invisible"/>
<com.pspdfkit.ui.PdfOutlineView
android:id="@+id/outlineView"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:visibility="invisible"/>
</FrameLayout>

View File

@ -10,6 +10,9 @@ dependencyResolutionManagement {
repositories {
google()
mavenCentral()
maven {
url = uri("https://customers.pspdfkit.com/maven")
}
}
}
rootProject.name = "Omnivore"