- ContentConverter.tipTapToHtml() had same JsonObject.asMap() bug as tiptapToMarkdown - All as? String casts silently returned null, producing empty HTML - Now uses Gson TypeToken<Map<String, Any?>> for proper deserialization - Added contentToHtml() that handles String (HTML), Map (TipTap), and JSON string - Fixed heading level cast (Gson deserializes numbers as Double, not Int) - VersionViewerActivity handles all content types correctly
274 lines
11 KiB
Kotlin
274 lines
11 KiB
Kotlin
package com.docmost.app.ui
|
|
|
|
import android.content.Intent
|
|
import android.os.Bundle
|
|
import android.view.View
|
|
import android.webkit.WebView
|
|
import android.webkit.WebViewClient
|
|
import android.widget.ImageView
|
|
import android.widget.LinearLayout
|
|
import android.widget.TextView
|
|
import androidx.appcompat.app.AppCompatActivity
|
|
import com.docmost.app.R
|
|
import com.docmost.app.api.DocmostApi
|
|
import com.docmost.app.data.Credentials
|
|
import com.docmost.app.data.SessionManager
|
|
import com.docmost.app.utils.ContentConverter
|
|
import com.google.android.material.button.MaterialButton
|
|
import com.google.android.material.imageview.ShapeableImageView
|
|
import com.google.android.material.progressindicator.CircularProgressIndicator
|
|
import com.google.gson.Gson
|
|
import kotlinx.coroutines.CoroutineScope
|
|
import kotlinx.coroutines.Dispatchers
|
|
import kotlinx.coroutines.launch
|
|
import kotlinx.coroutines.withContext
|
|
|
|
class VersionViewerActivity : AppCompatActivity() {
|
|
|
|
private lateinit var webView: WebView
|
|
private lateinit var progress: CircularProgressIndicator
|
|
private lateinit var actionBar: LinearLayout
|
|
private lateinit var ivAvatar: ShapeableImageView
|
|
private lateinit var tvAuthor: TextView
|
|
private lateinit var tvDateTime: TextView
|
|
private lateinit var btnLoadVersion: MaterialButton
|
|
private lateinit var api: DocmostApi
|
|
private var creds: Credentials? = null
|
|
private var currentPageContent: String = ""
|
|
private var originalTipTapJson: String = ""
|
|
|
|
override fun onCreate(savedInstanceState: Bundle?) {
|
|
super.onCreate(savedInstanceState)
|
|
setContentView(R.layout.activity_version_viewer)
|
|
|
|
// Hide the default ActionBar
|
|
supportActionBar?.hide()
|
|
|
|
webView = findViewById(R.id.webViewVersion)
|
|
progress = findViewById(R.id.progressVersion)
|
|
actionBar = findViewById(R.id.actionBar)
|
|
ivAvatar = findViewById(R.id.ivAvatar)
|
|
tvAuthor = findViewById(R.id.tvAuthor)
|
|
tvDateTime = findViewById(R.id.tvDateTime)
|
|
btnLoadVersion = findViewById(R.id.btnLoadVersion)
|
|
|
|
val pageId = intent.getStringExtra("pageId") ?: return finish()
|
|
val historyId = intent.getStringExtra("historyId") ?: return finish()
|
|
val author = intent.getStringExtra("author") ?: "Desconocido"
|
|
val dateTime = intent.getStringExtra("dateTime") ?: ""
|
|
|
|
val sessionManager = SessionManager(this)
|
|
com.docmost.app.utils.ImageLoader.init(this, sessionManager)
|
|
creds = sessionManager.getCredentials()
|
|
if (creds == null) {
|
|
finish()
|
|
return
|
|
}
|
|
|
|
api = DocmostApi(creds!!.url)
|
|
api.setAuthToken(creds!!.authToken)
|
|
|
|
tvAuthor.text = author
|
|
tvDateTime.text = dateTime
|
|
|
|
// Cargar avatar del autor
|
|
CoroutineScope(Dispatchers.IO).launch {
|
|
try {
|
|
val history = api.getPageHistoryInfo(pageId, historyId)
|
|
val avatarUrl = history.lastUpdatedBy?.avatarUrl
|
|
if (!avatarUrl.isNullOrBlank()) {
|
|
withContext(Dispatchers.Main) {
|
|
val fullAvatarUrl = "${creds?.url}/api/attachments/img/avatar/$avatarUrl"
|
|
com.docmost.app.utils.ImageLoader.loadSync(fullAvatarUrl, ivAvatar, R.drawable.ic_page)
|
|
}
|
|
} else {
|
|
withContext(Dispatchers.Main) {
|
|
ivAvatar.setImageResource(R.drawable.ic_page)
|
|
}
|
|
}
|
|
} catch (e: Exception) {
|
|
withContext(Dispatchers.Main) {
|
|
ivAvatar.setImageResource(R.drawable.ic_page)
|
|
}
|
|
}
|
|
}
|
|
|
|
btnLoadVersion.setOnClickListener {
|
|
loadVersionInEditor(pageId, historyId)
|
|
}
|
|
|
|
setupWebView(pageId, historyId)
|
|
}
|
|
|
|
private fun setupWebView(pageId: String, historyId: String) {
|
|
val isDark = when (resources.configuration.uiMode and android.content.res.Configuration.UI_MODE_NIGHT_MASK) {
|
|
android.content.res.Configuration.UI_MODE_NIGHT_YES -> true
|
|
else -> false
|
|
}
|
|
|
|
CoroutineScope(Dispatchers.IO).launch {
|
|
try {
|
|
val history = api.getPageHistoryInfo(pageId, historyId)
|
|
val htmlContent: String
|
|
if (history.content != null) {
|
|
val content = history.content
|
|
if (content is String && content.trimStart().startsWith("<")) {
|
|
htmlContent = content
|
|
originalTipTapJson = ""
|
|
} else if (content is Map<*, *>) {
|
|
val gson = Gson()
|
|
originalTipTapJson = gson.toJson(content)
|
|
htmlContent = ContentConverter.contentToHtml(content)
|
|
} else if (content is String) {
|
|
originalTipTapJson = content
|
|
htmlContent = ContentConverter.tipTapToHtml(content)
|
|
} else {
|
|
htmlContent = "<p>Sin contenido</p>"
|
|
originalTipTapJson = ""
|
|
}
|
|
} else {
|
|
htmlContent = "<p>Sin contenido</p>"
|
|
originalTipTapJson = ""
|
|
}
|
|
|
|
withContext(Dispatchers.Main) {
|
|
webView.settings.apply {
|
|
javaScriptEnabled = true
|
|
domStorageEnabled = true
|
|
allowFileAccess = false
|
|
allowContentAccess = false
|
|
builtInZoomControls = true
|
|
displayZoomControls = false
|
|
loadWithOverviewMode = true
|
|
useWideViewPort = true
|
|
}
|
|
|
|
webView.webViewClient = object : WebViewClient() {
|
|
override fun onPageFinished(view: WebView?, url: String?) {
|
|
progress.visibility = View.GONE
|
|
}
|
|
}
|
|
|
|
val darkModeCss = if (isDark) """
|
|
body {
|
|
background-color: #1a1a1a !important;
|
|
color: #e0e0e0 !important;
|
|
}
|
|
.version-banner {
|
|
background: #2d2d2d !important;
|
|
border-color: #404040 !important;
|
|
color: #e0e0e0 !important;
|
|
}
|
|
pre, code {
|
|
background: #2d2d2d !important;
|
|
color: #e0e0e0 !important;
|
|
}
|
|
blockquote {
|
|
border-left-color: #404040 !important;
|
|
color: #9ca3af !important;
|
|
}
|
|
th {
|
|
background: #2a2a2a !important;
|
|
}
|
|
td, th {
|
|
border-color: #404040 !important;
|
|
}
|
|
a {
|
|
color: #64b5f6 !important;
|
|
}
|
|
""" else ""
|
|
|
|
val fullHtml = """
|
|
<!DOCTYPE html>
|
|
<html>
|
|
<head>
|
|
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
|
<style>
|
|
body {
|
|
font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, Oxygen, Ubuntu, Cantarell, sans-serif;
|
|
padding: 16px;
|
|
line-height: 1.6;
|
|
color: #333;
|
|
}
|
|
.version-banner {
|
|
background: #fff3cd;
|
|
border: 1px solid #ffc107;
|
|
border-radius: 4px;
|
|
padding: 12px;
|
|
margin-bottom: 16px;
|
|
color: #856404;
|
|
}
|
|
.content {
|
|
max-width: 800px;
|
|
margin: 0 auto;
|
|
}
|
|
p { margin: 8px 0; }
|
|
h1, h2, h3, h4, h5, h6 { margin: 16px 0 8px 0; }
|
|
ul, ol { padding-left: 20px; }
|
|
pre { background: #f4f4f4; padding: 12px; border-radius: 4px; overflow-x: auto; }
|
|
code { background: #f4f4f4; padding: 2px 6px; border-radius: 3px; }
|
|
blockquote { border-left: 3px solid #ccc; padding-left: 12px; margin: 8px 0; color: #666; }
|
|
img { max-width: 100%; height: auto; }
|
|
table { border-collapse: collapse; width: 100%; }
|
|
th, td { border: 1px solid #ddd; padding: 8px; }
|
|
th { background: #f4f4f4; }
|
|
a { color: #0066cc; }
|
|
$darkModeCss
|
|
</style>
|
|
</head>
|
|
<body>
|
|
<div class="version-banner">
|
|
<strong>Versión de solo lectura</strong>
|
|
</div>
|
|
<div class="content">
|
|
$htmlContent
|
|
</div>
|
|
</body>
|
|
</html>
|
|
""".trimIndent()
|
|
|
|
webView.loadDataWithBaseURL(
|
|
"${creds?.url}/",
|
|
fullHtml,
|
|
"text/html",
|
|
"UTF-8",
|
|
null
|
|
)
|
|
|
|
currentPageContent = htmlContent
|
|
|
|
// Mostrar barra de acción con info del usuario y botón
|
|
actionBar.visibility = View.VISIBLE
|
|
}
|
|
} catch (e: Exception) {
|
|
withContext(Dispatchers.Main) {
|
|
progress.visibility = View.GONE
|
|
webView.loadData(
|
|
"<html><body><h1>Error</h1><p>${e.message}</p></body></html>",
|
|
"text/html",
|
|
"UTF-8"
|
|
)
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
private fun loadVersionInEditor(pageId: String, historyId: String) {
|
|
val resultIntent = Intent()
|
|
resultIntent.putExtra("loadVersion", true)
|
|
resultIntent.putExtra("pageId", pageId)
|
|
resultIntent.putExtra("historyId", historyId)
|
|
resultIntent.putExtra("tipTapJson", originalTipTapJson)
|
|
setResult(RESULT_OK, resultIntent)
|
|
finish()
|
|
}
|
|
|
|
override fun onConfigurationChanged(newConfig: android.content.res.Configuration) {
|
|
super.onConfigurationChanged(newConfig)
|
|
setupWebView(
|
|
intent.getStringExtra("pageId") ?: return,
|
|
intent.getStringExtra("historyId") ?: return
|
|
)
|
|
}
|
|
}
|