feat(files): открытие .md/.whiteboard + предпрогрев WebView для office

- .md/.markdown/.txt/.whiteboard теперь открываются: WebOpenableFiles →
  каноническая ссылка /index.php/f/<fileId> в авторизованном WebView,
  сервер сам направляет в Text/Whiteboard (office остаётся на Collabora).
  Тап по таким файлам в списке разрешён.
- Предпрогрев: OfficeWarmup.warmWebViewEngine создаёт и уничтожает пустой
  WebView при входе в Файлы — первое создание WebView в процессе тянет
  провайдер Chromium (сотни мс), из-за чего первый docx/xlsx открывался
  медленно; теперь движок прогрет заранее.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
This commit is contained in:
b-dev-mobile
2026-07-10 08:17:41 +00:00
parent 1e39b2e2fe
commit a96eda5b7c
4 changed files with 60 additions and 1 deletions
@@ -113,6 +113,7 @@ fun FilesScreen(
LaunchedEffect(session.serverUrl, session.username, session.davUserId) {
OfficeWarmup.warm(session)
OfficeWarmup.warmWebViewEngine(context) // прогрев WebView-движка для быстрого docx/xlsx
vm.load(session)
vm.loadSidebarData(session)
}
@@ -241,7 +242,11 @@ fun FilesScreen(
) {
val openItem: (FileItem) -> Unit = { item ->
val openable = !item.isDirectory &&
(OfficeFiles.isOfficeFile(item.name) || OpenableFiles.isOpenable(item.name))
(
OfficeFiles.isOfficeFile(item.name) ||
WebOpenableFiles.isWebOpenable(item.name) ||
OpenableFiles.isOpenable(item.name)
)
when {
item.isDirectory -> vm.openFolder(session, item)
openable -> vm.openItem(session, item)
@@ -487,6 +487,7 @@ class FilesViewModel(
if (item.isDirectory) return
when {
OfficeFiles.isOfficeFile(item.name) -> openOfficeFile(session, item)
WebOpenableFiles.isWebOpenable(item.name) -> openWebFile(session, item)
ArchiveFiles.isArchive(item.name) -> openArchiveFile(session, item)
OpenableFiles.isImage(item.name) -> openImageFile(session, item)
else -> _state.value = _state.value.copy(
@@ -495,6 +496,26 @@ class FilesViewModel(
}
}
/** Markdown/текст/доска — открываем каноническую ссылку /f/<id> в авторизованном WebView. */
private fun openWebFile(session: AuthSession, item: FileItem) {
val fileId = item.fileId ?: run {
_state.value = _state.value.copy(error = "Не удалось определить ID файла")
return
}
val base = session.serverUrl.trimEnd('/')
_state.value = _state.value.copy(
editorLaunch = OfficeEditorLaunch(
url = "$base/index.php/f/$fileId",
title = item.name,
username = session.username,
password = session.appPassword,
trustAllCerts = session.trustAllCerts,
serverUrl = session.serverUrl,
collaboraBaseUrl = "",
),
)
}
private fun openOfficeFile(session: AuthSession, item: FileItem) {
val fileId = item.fileId ?: run {
_state.value = _state.value.copy(error = "Не удалось определить ID файла")
@@ -63,6 +63,23 @@ object OfficeWarmup {
}
}
@Volatile
private var engineWarmed = false
/**
* Прогрев WebView-движка: ПЕРВОЕ создание WebView в процессе тянет провайдер Chromium
* (сотни мс), из-за чего первый docx/xlsx открывается медленно. Создаём и сразу уничтожаем
* пустой WebView заранее (при входе в Файлы) — движок загружается в фоне. Только UI-поток.
*/
fun warmWebViewEngine(context: android.content.Context) {
if (engineWarmed) return
engineWarmed = true
runCatching {
val wv = android.webkit.WebView(context.applicationContext)
wv.destroy()
}
}
fun clear() {
cachedServerUrl = null
cachedCollaboraUrl = null
@@ -0,0 +1,16 @@
package ru.forbion.f7cloud.feature.files
/**
* Файлы, которые открываются во ВЕБ-вьюере Nextcloud по канонической ссылке
* `/index.php/f/<fileId>` (сервер сам направляет в нужное приложение):
* markdown/текст → Text, доска → Whiteboard. В отличие от office (Collabora direct-edit).
*/
object WebOpenableFiles {
private val EXTENSIONS = setOf(
"md", "markdown", "txt", "text", "org",
"whiteboard",
)
fun isWebOpenable(name: String): Boolean =
name.substringAfterLast('.', "").lowercase() in EXTENSIONS
}