Merge pull request #1216 from omnivore-app/feature/android-bundled-web-reader
Bundled Web Reader - Android
This commit is contained in:
@ -153,7 +153,9 @@ dependencies {
|
||||
implementation 'com.google.android.gms:play-services-auth:20.2.0'
|
||||
implementation "com.google.accompanist:accompanist-systemuicontroller:0.25.1"
|
||||
|
||||
implementation("io.coil-kt:coil-compose:2.2.0")
|
||||
implementation 'io.coil-kt:coil-compose:2.2.0'
|
||||
|
||||
implementation 'com.google.code.gson:gson:2.8.6'
|
||||
}
|
||||
|
||||
apollo {
|
||||
|
||||
BIN
android/Omnivore/app/src/main/assets/CrimsonText-Bold.ttf
Normal file
BIN
android/Omnivore/app/src/main/assets/CrimsonText-Bold.ttf
Normal file
Binary file not shown.
BIN
android/Omnivore/app/src/main/assets/CrimsonText-Italic.ttf
Normal file
BIN
android/Omnivore/app/src/main/assets/CrimsonText-Italic.ttf
Normal file
Binary file not shown.
BIN
android/Omnivore/app/src/main/assets/CrimsonText-Regular.ttf
Normal file
BIN
android/Omnivore/app/src/main/assets/CrimsonText-Regular.ttf
Normal file
Binary file not shown.
BIN
android/Omnivore/app/src/main/assets/Inter-Black-900.ttf
Normal file
BIN
android/Omnivore/app/src/main/assets/Inter-Black-900.ttf
Normal file
Binary file not shown.
BIN
android/Omnivore/app/src/main/assets/Inter-Bold-700.ttf
Normal file
BIN
android/Omnivore/app/src/main/assets/Inter-Bold-700.ttf
Normal file
Binary file not shown.
BIN
android/Omnivore/app/src/main/assets/Inter-ExtraBold-800.ttf
Normal file
BIN
android/Omnivore/app/src/main/assets/Inter-ExtraBold-800.ttf
Normal file
Binary file not shown.
BIN
android/Omnivore/app/src/main/assets/Inter-ExtraLight-200.ttf
Normal file
BIN
android/Omnivore/app/src/main/assets/Inter-ExtraLight-200.ttf
Normal file
Binary file not shown.
BIN
android/Omnivore/app/src/main/assets/Inter-Light-300.ttf
Normal file
BIN
android/Omnivore/app/src/main/assets/Inter-Light-300.ttf
Normal file
Binary file not shown.
BIN
android/Omnivore/app/src/main/assets/Inter-Medium-500.ttf
Normal file
BIN
android/Omnivore/app/src/main/assets/Inter-Medium-500.ttf
Normal file
Binary file not shown.
BIN
android/Omnivore/app/src/main/assets/Inter-Regular-400.ttf
Normal file
BIN
android/Omnivore/app/src/main/assets/Inter-Regular-400.ttf
Normal file
Binary file not shown.
BIN
android/Omnivore/app/src/main/assets/Inter-SemiBold-600.ttf
Normal file
BIN
android/Omnivore/app/src/main/assets/Inter-SemiBold-600.ttf
Normal file
Binary file not shown.
BIN
android/Omnivore/app/src/main/assets/Inter-Thin.ttf
Normal file
BIN
android/Omnivore/app/src/main/assets/Inter-Thin.ttf
Normal file
Binary file not shown.
BIN
android/Omnivore/app/src/main/assets/Lora-Bold.ttf
Normal file
BIN
android/Omnivore/app/src/main/assets/Lora-Bold.ttf
Normal file
Binary file not shown.
BIN
android/Omnivore/app/src/main/assets/Lora-Italic.ttf
Normal file
BIN
android/Omnivore/app/src/main/assets/Lora-Italic.ttf
Normal file
Binary file not shown.
BIN
android/Omnivore/app/src/main/assets/Lora-Regular.ttf
Normal file
BIN
android/Omnivore/app/src/main/assets/Lora-Regular.ttf
Normal file
Binary file not shown.
BIN
android/Omnivore/app/src/main/assets/Merriweather-Bold.ttf
Normal file
BIN
android/Omnivore/app/src/main/assets/Merriweather-Bold.ttf
Normal file
Binary file not shown.
BIN
android/Omnivore/app/src/main/assets/Merriweather-Italic.ttf
Normal file
BIN
android/Omnivore/app/src/main/assets/Merriweather-Italic.ttf
Normal file
Binary file not shown.
BIN
android/Omnivore/app/src/main/assets/Merriweather-Regular.ttf
Normal file
BIN
android/Omnivore/app/src/main/assets/Merriweather-Regular.ttf
Normal file
Binary file not shown.
BIN
android/Omnivore/app/src/main/assets/OpenDyslexicAlta-Bold.otf
Normal file
BIN
android/Omnivore/app/src/main/assets/OpenDyslexicAlta-Bold.otf
Normal file
Binary file not shown.
Binary file not shown.
BIN
android/Omnivore/app/src/main/assets/OpenDyslexicAlta-Italic.otf
Normal file
BIN
android/Omnivore/app/src/main/assets/OpenDyslexicAlta-Italic.otf
Normal file
Binary file not shown.
Binary file not shown.
BIN
android/Omnivore/app/src/main/assets/OpenSans-Bold.ttf
Normal file
BIN
android/Omnivore/app/src/main/assets/OpenSans-Bold.ttf
Normal file
Binary file not shown.
BIN
android/Omnivore/app/src/main/assets/OpenSans-Italic.ttf
Normal file
BIN
android/Omnivore/app/src/main/assets/OpenSans-Italic.ttf
Normal file
Binary file not shown.
BIN
android/Omnivore/app/src/main/assets/OpenSans-Regular.ttf
Normal file
BIN
android/Omnivore/app/src/main/assets/OpenSans-Regular.ttf
Normal file
Binary file not shown.
BIN
android/Omnivore/app/src/main/assets/Roboto-Bold.ttf
Normal file
BIN
android/Omnivore/app/src/main/assets/Roboto-Bold.ttf
Normal file
Binary file not shown.
BIN
android/Omnivore/app/src/main/assets/Roboto-Italic.ttf
Normal file
BIN
android/Omnivore/app/src/main/assets/Roboto-Italic.ttf
Normal file
Binary file not shown.
BIN
android/Omnivore/app/src/main/assets/Roboto-Regular.ttf
Normal file
BIN
android/Omnivore/app/src/main/assets/Roboto-Regular.ttf
Normal file
Binary file not shown.
BIN
android/Omnivore/app/src/main/assets/SFMonoRegular.otf
Normal file
BIN
android/Omnivore/app/src/main/assets/SFMonoRegular.otf
Normal file
Binary file not shown.
BIN
android/Omnivore/app/src/main/assets/SourceSerifPro-Bold.ttf
Normal file
BIN
android/Omnivore/app/src/main/assets/SourceSerifPro-Bold.ttf
Normal file
Binary file not shown.
BIN
android/Omnivore/app/src/main/assets/SourceSerifPro-Italic.ttf
Normal file
BIN
android/Omnivore/app/src/main/assets/SourceSerifPro-Italic.ttf
Normal file
Binary file not shown.
BIN
android/Omnivore/app/src/main/assets/SourceSerifPro-Regular.ttf
Normal file
BIN
android/Omnivore/app/src/main/assets/SourceSerifPro-Regular.ttf
Normal file
Binary file not shown.
2
android/Omnivore/app/src/main/assets/bundle.js
Normal file
2
android/Omnivore/app/src/main/assets/bundle.js
Normal file
File diff suppressed because one or more lines are too long
1
android/Omnivore/app/src/main/assets/highlight-dark.css
Normal file
1
android/Omnivore/app/src/main/assets/highlight-dark.css
Normal file
@ -0,0 +1 @@
|
||||
pre code.hljs{display:block;overflow-x:auto;padding:1em}code.hljs{padding:3px 5px}.hljs{color:#ddd;background:#303030}.hljs-keyword,.hljs-link,.hljs-literal,.hljs-section,.hljs-selector-tag{color:#fff}.hljs-addition,.hljs-attribute,.hljs-built_in,.hljs-bullet,.hljs-name,.hljs-string,.hljs-symbol,.hljs-template-tag,.hljs-template-variable,.hljs-title,.hljs-type,.hljs-variable{color:#d88}.hljs-comment,.hljs-deletion,.hljs-meta,.hljs-quote{color:#979797}.hljs-doctag,.hljs-keyword,.hljs-literal,.hljs-name,.hljs-section,.hljs-selector-tag,.hljs-strong,.hljs-title,.hljs-type{font-weight:700}.hljs-emphasis{font-style:italic}
|
||||
1
android/Omnivore/app/src/main/assets/highlight.css
Normal file
1
android/Omnivore/app/src/main/assets/highlight.css
Normal file
@ -0,0 +1 @@
|
||||
pre code.hljs{display:block;overflow-x:auto;padding:1em}code.hljs{padding:3px 5px}.hljs{background:#f3f3f3;color:#444}.hljs-comment{color:#697070}.hljs-punctuation,.hljs-tag{color:#444a}.hljs-tag .hljs-attr,.hljs-tag .hljs-name{color:#444}.hljs-attribute,.hljs-doctag,.hljs-keyword,.hljs-meta .hljs-keyword,.hljs-name,.hljs-selector-tag{font-weight:700}.hljs-deletion,.hljs-number,.hljs-quote,.hljs-selector-class,.hljs-selector-id,.hljs-string,.hljs-template-tag,.hljs-type{color:#800}.hljs-section,.hljs-title{color:#800;font-weight:700}.hljs-link,.hljs-operator,.hljs-regexp,.hljs-selector-attr,.hljs-selector-pseudo,.hljs-symbol,.hljs-template-variable,.hljs-variable{color:#ab5656}.hljs-literal{color:#695}.hljs-addition,.hljs-built_in,.hljs-bullet,.hljs-code{color:#397300}.hljs-meta{color:#1f7199}.hljs-meta .hljs-string{color:#38a}.hljs-emphasis{font-style:italic}.hljs-strong{font-weight:700}
|
||||
@ -0,0 +1,8 @@
|
||||
MathJax = {
|
||||
tex: {
|
||||
inlineMath: [
|
||||
['$latex', '$'],
|
||||
['\\(', '\\)'],
|
||||
],
|
||||
},
|
||||
}
|
||||
1
android/Omnivore/app/src/main/assets/mathjax.js
Normal file
1
android/Omnivore/app/src/main/assets/mathjax.js
Normal file
File diff suppressed because one or more lines are too long
64
android/Omnivore/app/src/main/graphql/ArticleContent.graphql
Normal file
64
android/Omnivore/app/src/main/graphql/ArticleContent.graphql
Normal file
@ -0,0 +1,64 @@
|
||||
query GetArticle($slug: String!) {
|
||||
article(username: "me", slug: $slug) {
|
||||
... on ArticleSuccess {
|
||||
article {
|
||||
...ArticleFields
|
||||
content
|
||||
highlights(input: { includeFriends: false }) {
|
||||
...HighlightFields
|
||||
}
|
||||
labels {
|
||||
...LabelFields
|
||||
}
|
||||
}
|
||||
}
|
||||
... on ArticleError {
|
||||
errorCodes
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
fragment ArticleFields on Article {
|
||||
id
|
||||
title
|
||||
url
|
||||
author
|
||||
image
|
||||
savedAt
|
||||
createdAt
|
||||
publishedAt
|
||||
contentReader
|
||||
originalArticleUrl
|
||||
readingProgressPercent
|
||||
readingProgressAnchorIndex
|
||||
slug
|
||||
isArchived
|
||||
description
|
||||
linkId
|
||||
siteName
|
||||
state
|
||||
readAt
|
||||
updatedAt
|
||||
content
|
||||
}
|
||||
|
||||
fragment HighlightFields on Highlight {
|
||||
id
|
||||
shortId
|
||||
quote
|
||||
prefix
|
||||
suffix
|
||||
patch
|
||||
annotation
|
||||
createdByMe
|
||||
updatedAt
|
||||
sharedAt
|
||||
}
|
||||
|
||||
fragment LabelFields on Label {
|
||||
id
|
||||
name
|
||||
color
|
||||
description
|
||||
createdAt
|
||||
}
|
||||
@ -34,6 +34,8 @@ query Search($after: String, $first: Int, $query: String) {
|
||||
siteName
|
||||
subscription
|
||||
readAt
|
||||
savedAt
|
||||
updatedAt
|
||||
}
|
||||
}
|
||||
pageInfo {
|
||||
|
||||
@ -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.WebReaderViewModel
|
||||
import app.omnivore.omnivore.ui.root.RootView
|
||||
import dagger.hilt.android.AndroidEntryPoint
|
||||
|
||||
@ -26,6 +27,7 @@ class MainActivity : ComponentActivity() {
|
||||
|
||||
val loginViewModel: LoginViewModel by viewModels()
|
||||
val homeViewModel: HomeViewModel by viewModels()
|
||||
val webReaderViewModel: WebReaderViewModel by viewModels()
|
||||
|
||||
setContent {
|
||||
OmnivoreTheme {
|
||||
@ -34,7 +36,7 @@ class MainActivity : ComponentActivity() {
|
||||
.fillMaxSize()
|
||||
.background(color = Color.Black)
|
||||
) {
|
||||
RootView(loginViewModel, homeViewModel)
|
||||
RootView(loginViewModel, homeViewModel, webReaderViewModel)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@ -2,6 +2,6 @@ package app.omnivore.omnivore
|
||||
|
||||
sealed class Routes(val route: String) {
|
||||
object Home : Routes("Home")
|
||||
object WebReader : Routes("WebReader")
|
||||
object WebAppReader : Routes("WebAppReader")
|
||||
object Settings: Routes("Settings")
|
||||
}
|
||||
|
||||
@ -0,0 +1,14 @@
|
||||
package app.omnivore.omnivore.models
|
||||
|
||||
data class Highlight(
|
||||
val id: String,
|
||||
val shortId: String,
|
||||
val quote: String,
|
||||
val prefix: String?,
|
||||
val suffix: String?,
|
||||
val patch: String,
|
||||
val annotation: String?,
|
||||
val createdAt: Any?,
|
||||
val updatedAt: Any?,
|
||||
val createdByMe : Boolean,
|
||||
)
|
||||
@ -0,0 +1,29 @@
|
||||
package app.omnivore.omnivore.models
|
||||
|
||||
import androidx.core.net.toUri
|
||||
|
||||
data class LinkedItem(
|
||||
val id: String,
|
||||
val title: String,
|
||||
val createdAt: Any,
|
||||
val savedAt: Any,
|
||||
val readAt: Any?,
|
||||
val updatedAt: Any?,
|
||||
val readingProgress: Double,
|
||||
val readingProgressAnchor: Int,
|
||||
val imageURLString: String?,
|
||||
val pageURLString: String,
|
||||
val descriptionText: String?,
|
||||
val publisherURLString: String?,
|
||||
val siteName: String?,
|
||||
val author: String?,
|
||||
val publishDate: Any?,
|
||||
val slug: String,
|
||||
val isArchived: Boolean,
|
||||
val contentReader: String?,
|
||||
val content: String?
|
||||
) {
|
||||
fun publisherDisplayName(): String? {
|
||||
return publisherURLString?.toUri()?.host
|
||||
}
|
||||
}
|
||||
@ -0,0 +1,9 @@
|
||||
package app.omnivore.omnivore.models
|
||||
|
||||
data class LinkedItemLabel(
|
||||
val id: String,
|
||||
val name: String,
|
||||
val color: String,
|
||||
val createdAt: Any?,
|
||||
val labelDescription: String?,
|
||||
)
|
||||
@ -14,6 +14,7 @@ import androidx.compose.ui.Modifier
|
||||
import androidx.compose.ui.unit.dp
|
||||
import androidx.navigation.NavHostController
|
||||
import app.omnivore.omnivore.Routes
|
||||
import app.omnivore.omnivore.models.LinkedItem
|
||||
import kotlinx.coroutines.flow.distinctUntilChanged
|
||||
|
||||
|
||||
|
||||
@ -1,6 +1,5 @@
|
||||
package app.omnivore.omnivore.ui.home
|
||||
|
||||
import androidx.core.net.toUri
|
||||
import androidx.lifecycle.MutableLiveData
|
||||
import androidx.lifecycle.ViewModel
|
||||
import androidx.lifecycle.viewModelScope
|
||||
@ -8,6 +7,7 @@ import app.omnivore.omnivore.Constants
|
||||
import app.omnivore.omnivore.DatastoreKeys
|
||||
import app.omnivore.omnivore.DatastoreRepository
|
||||
import app.omnivore.omnivore.graphql.generated.SearchQuery
|
||||
import app.omnivore.omnivore.models.LinkedItem
|
||||
import com.apollographql.apollo3.ApolloClient
|
||||
import com.apollographql.apollo3.api.Optional
|
||||
import dagger.hilt.android.lifecycle.HiltViewModel
|
||||
@ -88,14 +88,22 @@ class HomeViewModel @Inject constructor(
|
||||
id = it.node.id,
|
||||
title = it.node.title,
|
||||
createdAt = it.node.createdAt,
|
||||
savedAt = it.node.savedAt,
|
||||
readAt = it.node.readAt,
|
||||
updatedAt = it.node.updatedAt,
|
||||
readingProgress = it.node.readingProgressPercent,
|
||||
readingProgressAnchor = it.node.readingProgressAnchorIndex,
|
||||
imageURLString = it.node.image,
|
||||
pageURLString = it.node.url,
|
||||
descriptionText = it.node.description,
|
||||
publisherURLString = it.node.originalArticleUrl,
|
||||
siteName = it.node.siteName,
|
||||
author = it.node.author,
|
||||
slug = it.node.slug
|
||||
publishDate = it.node.publishedAt,
|
||||
slug = it.node.slug,
|
||||
isArchived = it.node.isArchived,
|
||||
contentReader = it.node.contentReader.rawValue,
|
||||
content = null
|
||||
)
|
||||
}
|
||||
|
||||
@ -121,30 +129,3 @@ class HomeViewModel @Inject constructor(
|
||||
}
|
||||
}
|
||||
|
||||
public data class LinkedItem(
|
||||
public val id: String,
|
||||
public val title: String,
|
||||
public val createdAt: Any,
|
||||
// public val savedAt: Any,
|
||||
public val readAt: Any?,
|
||||
// public val updatedAt: Any,
|
||||
public val readingProgress: Double,
|
||||
public val readingProgressAnchor: Int,
|
||||
public val imageURLString: String?,
|
||||
// public val onDeviceImageURLString: String?,
|
||||
// public val documentDirectoryPath: String?,
|
||||
// public val pageURLString: String,
|
||||
public val descriptionText: String?,
|
||||
public val publisherURLString: String?,
|
||||
// public val siteName: String?,
|
||||
public val author: String?,
|
||||
// public val publishDate: Any?,
|
||||
public val slug: String,
|
||||
// public val isArchived: Boolean,
|
||||
// public val contentReader: String?,
|
||||
// public val originalHtml: String?,
|
||||
) {
|
||||
fun publisherDisplayName(): String? {
|
||||
return publisherURLString?.toUri()?.host
|
||||
}
|
||||
}
|
||||
|
||||
@ -15,6 +15,7 @@ import androidx.compose.ui.graphics.Color
|
||||
import androidx.compose.ui.text.style.TextOverflow
|
||||
import androidx.compose.ui.unit.dp
|
||||
import androidx.compose.ui.unit.sp
|
||||
import app.omnivore.omnivore.models.LinkedItem
|
||||
import coil.compose.rememberAsyncImagePainter
|
||||
|
||||
@Composable
|
||||
|
||||
@ -0,0 +1,68 @@
|
||||
package app.omnivore.omnivore.ui.reader
|
||||
|
||||
import android.annotation.SuppressLint
|
||||
import android.view.ViewGroup
|
||||
import android.webkit.WebView
|
||||
import android.webkit.WebViewClient
|
||||
import androidx.compose.material3.Text
|
||||
import androidx.compose.runtime.Composable
|
||||
import androidx.compose.runtime.getValue
|
||||
import androidx.compose.runtime.livedata.observeAsState
|
||||
import androidx.compose.ui.viewinterop.AndroidView
|
||||
|
||||
@Composable
|
||||
fun WebReaderLoadingContainer(slug: String, webReaderViewModel: WebReaderViewModel) {
|
||||
val webReaderParams: WebReaderParams? by webReaderViewModel.webReaderParamsLiveData.observeAsState(null)
|
||||
|
||||
if (webReaderParams == null) {
|
||||
webReaderViewModel.loadItem(slug = slug)
|
||||
}
|
||||
|
||||
if (webReaderParams != null) {
|
||||
WebReader(webReaderParams!!)
|
||||
} else {
|
||||
// TODO: add a proper loading view
|
||||
Text("Loading...")
|
||||
}
|
||||
}
|
||||
|
||||
@SuppressLint("SetJavaScriptEnabled")
|
||||
@Composable
|
||||
fun WebReader(params: WebReaderParams) {
|
||||
WebView.setWebContentsDebuggingEnabled(true)
|
||||
|
||||
val webReaderContent = WebReaderContent(
|
||||
textFontSize = 12,
|
||||
lineHeight = 150,
|
||||
maxWidthPercentage = 100,
|
||||
item = params.item,
|
||||
themeKey = "LightGray",
|
||||
fontFamily = WebFont.SYSTEM ,
|
||||
articleContent = params.articleContent,
|
||||
prefersHighContrastText = false,
|
||||
)
|
||||
|
||||
val styledContent = webReaderContent.styledContent()
|
||||
|
||||
AndroidView(factory = {
|
||||
WebView(it).apply {
|
||||
layoutParams = ViewGroup.LayoutParams(
|
||||
ViewGroup.LayoutParams.MATCH_PARENT,
|
||||
ViewGroup.LayoutParams.MATCH_PARENT
|
||||
)
|
||||
|
||||
settings.javaScriptEnabled = true
|
||||
settings.allowContentAccess = true
|
||||
settings.allowFileAccess = true
|
||||
settings.domStorageEnabled = true
|
||||
|
||||
webViewClient = object : WebViewClient() {
|
||||
}
|
||||
|
||||
loadDataWithBaseURL("file:///android_asset/", styledContent, "text/html; charset=utf-8", "utf-8", null);
|
||||
|
||||
}
|
||||
}, update = {
|
||||
it.loadDataWithBaseURL("file:///android_asset/", styledContent, "text/html; charset=utf-8", "utf-8", null);
|
||||
})
|
||||
}
|
||||
@ -0,0 +1,111 @@
|
||||
package app.omnivore.omnivore.ui.reader
|
||||
|
||||
import android.util.Log
|
||||
import app.omnivore.omnivore.models.LinkedItem
|
||||
|
||||
enum class WebFont(val displayText: String, val rawValue: String) {
|
||||
INTER("Inter", "Inter"),
|
||||
SYSTEM("System Default", "unset"),
|
||||
OPEN_DYSLEXIC("Open Dyslexic", "OpenDyslexic"),
|
||||
MERRIWEATHER("Merriweather", "Merriweather"),
|
||||
LORA("Lora", "Lora"),
|
||||
OPEN_SANS("Open Sans", "Open Sans"),
|
||||
ROBOTO("Roboto", "Roboto"),
|
||||
CRIMSON_TEXT("Crimson Text", "Crimson Text"),
|
||||
SOURCE_SERIF_PRO("Source Serif Pro", "Source Serif Pro"),
|
||||
Inter("Inter", "Inter"),
|
||||
}
|
||||
|
||||
enum class ArticleContentStatus(val rawValue: String) {
|
||||
FAILED("FAILED"),
|
||||
PROCESSING("PROCESSING"),
|
||||
SUCCEEDED("SUCCEEDED"),
|
||||
UNKNOWN("UNKNOWN")
|
||||
}
|
||||
|
||||
data class ArticleContent(
|
||||
val title: String,
|
||||
val htmlContent: String,
|
||||
val highlightsJSONString: String,
|
||||
val contentStatus: String, // ArticleContentStatus,
|
||||
val objectID: String?, // whatever the Room Equivalent of objectID is
|
||||
val labelsJSONString: String
|
||||
)
|
||||
|
||||
data class WebReaderContent(
|
||||
val textFontSize: Int,
|
||||
val lineHeight: Int,
|
||||
val maxWidthPercentage: Int,
|
||||
val item: LinkedItem,
|
||||
val themeKey: String,
|
||||
val fontFamily: WebFont,
|
||||
val articleContent: ArticleContent,
|
||||
val prefersHighContrastText: Boolean
|
||||
) {
|
||||
fun styledContent(): String {
|
||||
// TODO: Kotlinize these three values (pasted from Swift)
|
||||
val savedAt = "new Date(1662571290735.0).toISOString()"
|
||||
val createdAt = "new Date().toISOString()"
|
||||
val publishedAt = "new Date().toISOString()" //if (item.publishDate != null) "new Date((item.publishDate!.timeIntervalSince1970 * 1000)).toISOString()" else "undefined"
|
||||
|
||||
val content = """
|
||||
<!DOCTYPE html>
|
||||
<html>
|
||||
<head>
|
||||
<meta charset="utf-8" />
|
||||
<meta name='viewport' content='width=device-width, initial-scale=1.0, maximum-scale=1.0, minimum-scale=1.0, user-scalable=no' />
|
||||
<style>
|
||||
@import url("highlight${if (themeKey == "Gray") "-dark" else ""}.css");
|
||||
</style>
|
||||
</head>
|
||||
<body>
|
||||
<div id="root" />
|
||||
<div id='_omnivore-htmlContent'>
|
||||
${articleContent.htmlContent}
|
||||
</div>
|
||||
<script type="text/javascript">
|
||||
window.omnivoreEnv = {
|
||||
"NEXT_PUBLIC_APP_ENV": "prod",
|
||||
"NEXT_PUBLIC_BASE_URL": "unset",
|
||||
"NEXT_PUBLIC_SERVER_BASE_URL": "unset",
|
||||
"NEXT_PUBLIC_HIGHLIGHTS_BASE_URL": "unset"
|
||||
}
|
||||
|
||||
window.omnivoreArticle = {
|
||||
id: "${item.id}",
|
||||
linkId: "${item.id}",
|
||||
slug: "${item.slug}",
|
||||
createdAt: new Date(1662571290735.0).toISOString(),
|
||||
savedAt: new Date(1662571290981.0).toISOString(),
|
||||
publishedAt: new Date(1662454816000.0).toISOString(),
|
||||
url: `${item.pageURLString}`,
|
||||
title: `${articleContent.title.replace("`", "\\`")}`,
|
||||
content: document.getElementById('_omnivore-htmlContent').innerHTML,
|
||||
originalArticleUrl: "${item.pageURLString}",
|
||||
contentReader: "WEB",
|
||||
readingProgressPercent: ${item.readingProgress},
|
||||
readingProgressAnchorIndex: ${item.readingProgressAnchor},
|
||||
labels: ${articleContent.labelsJSONString},
|
||||
highlights: ${articleContent.highlightsJSONString},
|
||||
}
|
||||
|
||||
window.fontSize = $textFontSize
|
||||
window.fontFamily = "${fontFamily.rawValue}"
|
||||
window.maxWidthPercentage = $maxWidthPercentage
|
||||
window.lineHeight = $lineHeight
|
||||
window.localStorage.setItem("theme", "$themeKey")
|
||||
window.prefersHighContrastFont = $prefersHighContrastText
|
||||
window.enableHighlightBar = false
|
||||
</script>
|
||||
<script src="bundle.js"></script>
|
||||
<script src="mathJaxConfiguration.js" id="MathJax-script"></script>
|
||||
<script src="mathjax.js" id="MathJax-script"></script>
|
||||
</body>
|
||||
</html>
|
||||
"""
|
||||
|
||||
Log.d("Loggo", content)
|
||||
|
||||
return content
|
||||
}
|
||||
}
|
||||
@ -0,0 +1,117 @@
|
||||
package app.omnivore.omnivore.ui.reader
|
||||
|
||||
import androidx.lifecycle.MutableLiveData
|
||||
import androidx.lifecycle.ViewModel
|
||||
import androidx.lifecycle.viewModelScope
|
||||
import app.omnivore.omnivore.Constants
|
||||
import app.omnivore.omnivore.DatastoreKeys
|
||||
import app.omnivore.omnivore.DatastoreRepository
|
||||
import app.omnivore.omnivore.graphql.generated.GetArticleQuery
|
||||
import app.omnivore.omnivore.models.Highlight
|
||||
import app.omnivore.omnivore.models.LinkedItem
|
||||
import app.omnivore.omnivore.models.LinkedItemLabel
|
||||
import com.apollographql.apollo3.ApolloClient
|
||||
import com.google.gson.Gson
|
||||
import dagger.hilt.android.lifecycle.HiltViewModel
|
||||
import kotlinx.coroutines.launch
|
||||
import kotlinx.coroutines.runBlocking
|
||||
import javax.inject.Inject
|
||||
|
||||
data class WebReaderParams(
|
||||
val item: LinkedItem,
|
||||
val articleContent: ArticleContent
|
||||
)
|
||||
|
||||
@HiltViewModel
|
||||
class WebReaderViewModel @Inject constructor(
|
||||
private val datastoreRepo: DatastoreRepository
|
||||
): ViewModel() {
|
||||
val webReaderParamsLiveData = MutableLiveData<WebReaderParams?>(null)
|
||||
|
||||
private fun getAuthToken(): String? = runBlocking {
|
||||
datastoreRepo.getString(DatastoreKeys.omnivoreAuthToken)
|
||||
}
|
||||
|
||||
fun loadItem(slug: String) {
|
||||
viewModelScope.launch {
|
||||
val authToken = getAuthToken()
|
||||
|
||||
val apolloClient = ApolloClient.Builder()
|
||||
.serverUrl("${Constants.apiURL}/api/graphql")
|
||||
.addHttpHeader("Authorization", value = authToken ?: "")
|
||||
.build()
|
||||
|
||||
val response = apolloClient.query(
|
||||
GetArticleQuery(slug = slug)
|
||||
).execute()
|
||||
|
||||
val article = response.data?.article?.onArticleSuccess?.article ?: return@launch
|
||||
|
||||
val labels = article.labels ?: listOf()
|
||||
|
||||
val linkedItemLabels = labels.map {
|
||||
LinkedItemLabel(
|
||||
id = it.labelFields.id,
|
||||
name = it.labelFields.name,
|
||||
color = it.labelFields.color,
|
||||
createdAt = it.labelFields.createdAt,
|
||||
labelDescription = it.labelFields.description
|
||||
)
|
||||
}
|
||||
|
||||
val highlights = article.highlights.map {
|
||||
Highlight(
|
||||
id = it.highlightFields.id,
|
||||
shortId = it.highlightFields.shortId,
|
||||
quote = it.highlightFields.quote,
|
||||
prefix = it.highlightFields.prefix,
|
||||
suffix = it.highlightFields.suffix,
|
||||
patch = it.highlightFields.patch,
|
||||
annotation = it.highlightFields.annotation,
|
||||
createdAt = null,
|
||||
updatedAt = it.highlightFields.updatedAt,
|
||||
createdByMe = it.highlightFields.createdByMe,
|
||||
)
|
||||
}
|
||||
|
||||
// TODO: handle errors
|
||||
|
||||
val linkedItem = LinkedItem(
|
||||
id = article.articleFields.id,
|
||||
title = article.articleFields.title,
|
||||
createdAt = article.articleFields.createdAt,
|
||||
savedAt = article.articleFields.savedAt,
|
||||
readAt = article.articleFields.readAt,
|
||||
updatedAt = article.articleFields.updatedAt,
|
||||
readingProgress = article.articleFields.readingProgressPercent,
|
||||
readingProgressAnchor = article.articleFields.readingProgressAnchorIndex,
|
||||
imageURLString = article.articleFields.image,
|
||||
pageURLString = article.articleFields.url,
|
||||
descriptionText = article.articleFields.description,
|
||||
publisherURLString = article.articleFields.originalArticleUrl,
|
||||
siteName = article.articleFields.siteName,
|
||||
author = article.articleFields.author,
|
||||
publishDate = article.articleFields.publishedAt,
|
||||
slug = article.articleFields.slug,
|
||||
isArchived = article.articleFields.isArchived,
|
||||
contentReader = article.articleFields.contentReader.rawValue,
|
||||
content = article.articleFields.content
|
||||
)
|
||||
|
||||
val articleContent = ArticleContent(
|
||||
title = article.articleFields.title,
|
||||
htmlContent = article.articleFields.content ?: "",
|
||||
highlightsJSONString = Gson().toJson(highlights),
|
||||
contentStatus = "SUCCEEDED",
|
||||
objectID = "",
|
||||
labelsJSONString = Gson().toJson(linkedItemLabels)
|
||||
)
|
||||
|
||||
webReaderParamsLiveData.value = WebReaderParams(linkedItem, articleContent)
|
||||
}
|
||||
}
|
||||
|
||||
fun reset() {
|
||||
webReaderParamsLiveData.value = null
|
||||
}
|
||||
}
|
||||
@ -19,12 +19,16 @@ 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 com.google.accompanist.systemuicontroller.rememberSystemUiController
|
||||
|
||||
@Composable
|
||||
fun RootView(
|
||||
loginViewModel: LoginViewModel,
|
||||
homeViewModel: HomeViewModel
|
||||
homeViewModel: HomeViewModel,
|
||||
webReaderViewModel: WebReaderViewModel
|
||||
) {
|
||||
val hasAuthToken: Boolean by loginViewModel.hasAuthTokenLiveData.observeAsState(false)
|
||||
val systemUiController = rememberSystemUiController()
|
||||
@ -46,7 +50,8 @@ fun RootView(
|
||||
if (hasAuthToken) {
|
||||
PrimaryNavigator(
|
||||
loginViewModel = loginViewModel,
|
||||
homeViewModel = homeViewModel
|
||||
homeViewModel = homeViewModel,
|
||||
webReaderViewModel = webReaderViewModel
|
||||
)
|
||||
} else {
|
||||
WelcomeScreen(viewModel = loginViewModel)
|
||||
@ -57,7 +62,8 @@ fun RootView(
|
||||
@Composable
|
||||
fun PrimaryNavigator(
|
||||
loginViewModel: LoginViewModel,
|
||||
homeViewModel: HomeViewModel
|
||||
homeViewModel: HomeViewModel,
|
||||
webReaderViewModel: WebReaderViewModel
|
||||
) {
|
||||
val navController = rememberNavController()
|
||||
|
||||
@ -69,13 +75,23 @@ fun PrimaryNavigator(
|
||||
)
|
||||
}
|
||||
|
||||
composable("WebReader/{slug}") {
|
||||
// TODO: delete this route and views
|
||||
composable("WebAppReader/{slug}") {
|
||||
ArticleWebView(
|
||||
it.arguments?.getString("slug") ?: "",
|
||||
authCookieString = loginViewModel.getAuthCookieString() ?: ""
|
||||
)
|
||||
}
|
||||
|
||||
composable("WebReader/{slug}") {
|
||||
webReaderViewModel.reset() // clear previously loaded item
|
||||
|
||||
WebReaderLoadingContainer(
|
||||
it.arguments?.getString("slug") ?: "",
|
||||
webReaderViewModel = webReaderViewModel
|
||||
)
|
||||
}
|
||||
|
||||
composable(Routes.Settings.route) {
|
||||
SettingsView(loginViewModel = loginViewModel, navController = navController)
|
||||
}
|
||||
|
||||
@ -51,7 +51,6 @@ struct WebReaderContent {
|
||||
</head>
|
||||
<body>
|
||||
<div id="root" />
|
||||
<div>HIIIIII</div>
|
||||
<div id='_omnivore-htmlContent' style="display: none;">
|
||||
\(articleContent.htmlContent)
|
||||
</div>
|
||||
|
||||
Reference in New Issue
Block a user