diff --git a/android/Omnivore/app/build.gradle b/android/Omnivore/app/build.gradle
index a7a6c744b..66cccad8f 100644
--- a/android/Omnivore/app/build.gradle
+++ b/android/Omnivore/app/build.gradle
@@ -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 {
diff --git a/android/Omnivore/app/src/main/AndroidManifest.xml b/android/Omnivore/app/src/main/AndroidManifest.xml
index e0b3e5563..0bbedccff 100644
--- a/android/Omnivore/app/src/main/AndroidManifest.xml
+++ b/android/Omnivore/app/src/main/AndroidManifest.xml
@@ -15,6 +15,7 @@
android:roundIcon="@mipmap/ic_launcher_round"
android:supportsRtl="true"
android:theme="@style/Theme.Omnivore"
+ android:largeHeap="true"
tools:targetApi="31">
+
+
+
+
diff --git a/android/Omnivore/app/src/main/assets/test.pdf b/android/Omnivore/app/src/main/assets/test.pdf
new file mode 100644
index 000000000..2887e5b76
Binary files /dev/null and b/android/Omnivore/app/src/main/assets/test.pdf differ
diff --git a/android/Omnivore/app/src/main/java/app/omnivore/omnivore/MainActivity.kt b/android/Omnivore/app/src/main/java/app/omnivore/omnivore/MainActivity.kt
index 2a07a427b..ff2d4f155 100644
--- a/android/Omnivore/app/src/main/java/app/omnivore/omnivore/MainActivity.kt
+++ b/android/Omnivore/app/src/main/java/app/omnivore/omnivore/MainActivity.kt
@@ -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
diff --git a/android/Omnivore/app/src/main/java/app/omnivore/omnivore/models/Highlight.kt b/android/Omnivore/app/src/main/java/app/omnivore/omnivore/models/Highlight.kt
index f7c4fe365..0a7c90423 100644
--- a/android/Omnivore/app/src/main/java/app/omnivore/omnivore/models/Highlight.kt
+++ b/android/Omnivore/app/src/main/java/app/omnivore/omnivore/models/Highlight.kt
@@ -12,3 +12,4 @@ data class Highlight(
val updatedAt: Any?,
val createdByMe : Boolean,
)
+
diff --git a/android/Omnivore/app/src/main/java/app/omnivore/omnivore/models/LinkedItem.kt b/android/Omnivore/app/src/main/java/app/omnivore/omnivore/models/LinkedItem.kt
index 0d5a75efd..960348b8b 100644
--- a/android/Omnivore/app/src/main/java/app/omnivore/omnivore/models/LinkedItem.kt
+++ b/android/Omnivore/app/src/main/java/app/omnivore/omnivore/models/LinkedItem.kt
@@ -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
+ }
}
diff --git a/android/Omnivore/app/src/main/java/app/omnivore/omnivore/ui/home/HomeView.kt b/android/Omnivore/app/src/main/java/app/omnivore/omnivore/ui/home/HomeView.kt
index ed17d858a..54e6ab667 100644
--- a/android/Omnivore/app/src/main/java/app/omnivore/omnivore/ui/home/HomeView.kt
+++ b/android/Omnivore/app/src/main/java/app/omnivore/omnivore/ui/home/HomeView.kt
@@ -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 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}")
+ }
}
)
}
diff --git a/android/Omnivore/app/src/main/java/app/omnivore/omnivore/ui/home/HomeViewModel.kt b/android/Omnivore/app/src/main/java/app/omnivore/omnivore/ui/home/HomeViewModel.kt
index e07e2c09d..6fc76e977 100644
--- a/android/Omnivore/app/src/main/java/app/omnivore/omnivore/ui/home/HomeViewModel.kt
+++ b/android/Omnivore/app/src/main/java/app/omnivore/omnivore/ui/home/HomeViewModel.kt
@@ -24,7 +24,7 @@ class HomeViewModel @Inject constructor(
private var receivedIdx = 0
// Live Data
- val searchTextLiveData = MutableLiveData("")
+ val searchTextLiveData = MutableLiveData("")
val itemsLiveData = MutableLiveData>(listOf())
fun updateSearchText(text: String) {
diff --git a/android/Omnivore/app/src/main/java/app/omnivore/omnivore/ui/reader/PDFReader.kt b/android/Omnivore/app/src/main/java/app/omnivore/omnivore/ui/reader/PDFReader.kt
new file mode 100644
index 000000000..d8a4c3612
--- /dev/null
+++ b/android/Omnivore/app/src/main/java/app/omnivore/omnivore/ui/reader/PDFReader.kt
@@ -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 { 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
+ }
+}
diff --git a/android/Omnivore/app/src/main/java/app/omnivore/omnivore/ui/reader/PDFReaderViewModel.kt b/android/Omnivore/app/src/main/java/app/omnivore/omnivore/ui/reader/PDFReaderViewModel.kt
new file mode 100644
index 000000000..c673cfdd2
--- /dev/null
+++ b/android/Omnivore/app/src/main/java/app/omnivore/omnivore/ui/reader/PDFReaderViewModel.kt
@@ -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(null)
+ var annotations: List = 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)
+ }
+}
diff --git a/android/Omnivore/app/src/main/java/app/omnivore/omnivore/ui/reader/WebReader.kt b/android/Omnivore/app/src/main/java/app/omnivore/omnivore/ui/reader/WebReader.kt
index c141a78ef..20e3e0439 100644
--- a/android/Omnivore/app/src/main/java/app/omnivore/omnivore/ui/reader/WebReader.kt
+++ b/android/Omnivore/app/src/main/java/app/omnivore/omnivore/ui/reader/WebReader.kt
@@ -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
diff --git a/android/Omnivore/app/src/main/java/app/omnivore/omnivore/ui/reader/WebReaderContent.kt b/android/Omnivore/app/src/main/java/app/omnivore/omnivore/ui/reader/WebReaderContent.kt
index a3342fb35..599c55078 100644
--- a/android/Omnivore/app/src/main/java/app/omnivore/omnivore/ui/reader/WebReaderContent.kt
+++ b/android/Omnivore/app/src/main/java/app/omnivore/omnivore/ui/reader/WebReaderContent.kt
@@ -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,
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
diff --git a/android/Omnivore/app/src/main/java/app/omnivore/omnivore/ui/reader/WebReaderViewModel.kt b/android/Omnivore/app/src/main/java/app/omnivore/omnivore/ui/reader/WebReaderViewModel.kt
index cb0908ed5..e9a6f4aaf 100644
--- a/android/Omnivore/app/src/main/java/app/omnivore/omnivore/ui/reader/WebReaderViewModel.kt
+++ b/android/Omnivore/app/src/main/java/app/omnivore/omnivore/ui/reader/WebReaderViewModel.kt
@@ -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)
diff --git a/android/Omnivore/app/src/main/java/app/omnivore/omnivore/ui/root/RootView.kt b/android/Omnivore/app/src/main/java/app/omnivore/omnivore/ui/root/RootView.kt
index 60619015c..c669be8b6 100644
--- a/android/Omnivore/app/src/main/java/app/omnivore/omnivore/ui/root/RootView.kt
+++ b/android/Omnivore/app/src/main/java/app/omnivore/omnivore/ui/root/RootView.kt
@@ -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
diff --git a/android/Omnivore/app/src/main/res/layout/pdf_reader_fragment.xml b/android/Omnivore/app/src/main/res/layout/pdf_reader_fragment.xml
new file mode 100644
index 000000000..c1c022b28
--- /dev/null
+++ b/android/Omnivore/app/src/main/res/layout/pdf_reader_fragment.xml
@@ -0,0 +1,36 @@
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/android/Omnivore/settings.gradle b/android/Omnivore/settings.gradle
index 78303f486..8a45271fe 100644
--- a/android/Omnivore/settings.gradle
+++ b/android/Omnivore/settings.gradle
@@ -10,6 +10,9 @@ dependencyResolutionManagement {
repositories {
google()
mavenCentral()
+ maven {
+ url = uri("https://customers.pspdfkit.com/maven")
+ }
}
}
rootProject.name = "Omnivore"