From b9acccfed01878dfdec92f688b813e7ca87425f5 Mon Sep 17 00:00:00 2001 From: VickyLeu Date: Thu, 10 Sep 2026 00:44:18 -0700 Subject: [PATCH 01/11] fix: preserve TBS init error codes and diagnostics --- .../org/uooc/document/DocumentPreviewer.kt | 83 ++++++++----------- 1 file changed, 34 insertions(+), 49 deletions(-) diff --git a/viewer/src/commonMain/kotlin/org/uooc/document/DocumentPreviewer.kt b/viewer/src/commonMain/kotlin/org/uooc/document/DocumentPreviewer.kt index 64f2689..9d2bd53 100644 --- a/viewer/src/commonMain/kotlin/org/uooc/document/DocumentPreviewer.kt +++ b/viewer/src/commonMain/kotlin/org/uooc/document/DocumentPreviewer.kt @@ -6,75 +6,60 @@ import com.github.jing332.filepicker.base.FileImpl object DocumentPreviewer { var currentState = TMResult.UNKNOWN internal set + + /** Raw TBS initEngine return code. Kept separately so unknown SDK codes are not lost. */ + var currentInitCode: Int = TMResult.UNKNOWN.code + internal set + @Composable - fun previewDocument(document: FileImpl,callback: (Boolean, String) -> Unit) { + fun previewDocument(document: FileImpl, callback: (Boolean, String) -> Unit) { println("Previewing document at ${document.getAbsolutePath()}") - documentView(document,callback) + documentView(document, callback) } fun setup(license: String, applicationContext: coil3.PlatformContext) { - println("Setting up document previewer with license $license") + // Never print the license key. setupLicense() logs only non-sensitive diagnostics. setupLicense(license, applicationContext) } - - /** - * 初始化接口错误码 - * intEngine 接口错误码为方法返回值。 - * initEngineAsync 接口错误码为回调 actionType == ITbsReader.OPEN_FILEREADER_ASYNC_LOAD_READER_ENTRY_CALLBACK 时 args 的值。 - * 错误码 - * 说明 - * 102 - * 未设置 licenseKey。 - * 202 - * 请检查调用接口是否正确,应调用 setLicenseKey 接口而不是 setLicense 接口。 - * 103 、305 - * 1. 请检查设备网络是否连通。 - * 2. 尝试切换网络。 - * 212、322 - * 调用量包次数用完。 - * 4001 - * licenseKey 不存在,请检查设置的 licenseKey 是否正确。 - * 4002 - * 客户端包名和 licenseKey 不匹配。 - * - * - * - */ + /** TBS File Engine initialization result codes. */ enum class TMResult(val code: Int = 0, val message: String = "") { SUCCESS(0, "Success"), - MISMATCH(4002, "License key mismatch"), - UNSET(102, "Unset license key"), - CHECK(202, "Check if the interface is called correctly, should call setLicenseKey instead of setLicense"), - NETWORK_MAYBE1(103, "Check if the device network is connected, try switching networks"), - NETWORK_MAYBE2(305, "Check if the device network is connected, try switching networks"), - QUOTA1(212, "The number of calls is used up"), - QUOTA2(322, "The number of calls is used up"), - NOT_EXIST(4001, "License key does not exist, please check if the set license key is correct"), - PACKAGE(4002, "The client package name does not match the license key"), - UNKNOWN(-1, "Unknown error"); + UNSET(102, "License key is not set"), + NETWORK_MAYBE1(103, "Network unavailable; check connectivity or try another network"), + MISMATCH(209, "License key mismatch"), + CHECK(202, "Invalid license API usage; setLicenseKey must be used"), + QUOTA1(212, "TBS File quota is exhausted or unavailable to this app"), + NETWORK_MAYBE2(305, "Network unavailable; check connectivity or try another network"), + QUOTA2(322, "TBS File quota is exhausted or unavailable to this app"), + NOT_EXIST(4001, "License key does not exist"), + PACKAGE(4002, "Application package name does not match the license key"), + UNKNOWN(-1, "Unknown TBS File initialization error"); companion object { - fun fromCode(code: Int): TMResult { - return values().find { it.code == code }?.apply { - println("Found code $code ${this.message}") - } ?: run{ - println("Unknown code $code") - UNKNOWN - } - } + fun fromCode(code: Int): TMResult = values().firstOrNull { it.code == code } ?: UNKNOWN } } -} + internal fun describeInitResult(code: Int): String { + val result = TMResult.fromCode(code) + return if (result == TMResult.SUCCESS) { + "TbsFile Engine initialized" + } else if (result == TMResult.UNKNOWN) { + "TbsFile Engine initialization failed (code=$code, reason=${result.message})" + } else { + "TbsFile Engine initialization failed (code=$code, reason=${result.message})" + } + } +} @Composable internal expect fun DocumentPreviewer.documentView( document: FileImpl, - callback: (Boolean, String) -> Unit + callback: (Boolean, String) -> Unit, ) internal expect fun DocumentPreviewer.setupLicense( license: String, - applicationContext: coil3.PlatformContext -) \ No newline at end of file + applicationContext: coil3.PlatformContext, +) From 818fc78ebd93d086101908e16674929f033aee01 Mon Sep 17 00:00:00 2001 From: VickyLeu Date: Thu, 10 Sep 2026 00:44:38 -0700 Subject: [PATCH 02/11] fix: expose actionable TBS initialization failures --- .../document/DocumentPreviewer.android.kt | 110 +++++++++--------- 1 file changed, 52 insertions(+), 58 deletions(-) diff --git a/viewer/src/androidMain/kotlin/org/uooc/document/DocumentPreviewer.android.kt b/viewer/src/androidMain/kotlin/org/uooc/document/DocumentPreviewer.android.kt index 9e65aef..9cbad46 100644 --- a/viewer/src/androidMain/kotlin/org/uooc/document/DocumentPreviewer.android.kt +++ b/viewer/src/androidMain/kotlin/org/uooc/document/DocumentPreviewer.android.kt @@ -1,9 +1,7 @@ package org.uooc.document import android.content.Context -import android.content.Intent -import android.os.Build -import android.provider.Settings +import android.util.Log import androidx.compose.foundation.layout.BoxWithConstraints import androidx.compose.foundation.layout.Column import androidx.compose.foundation.layout.fillMaxSize @@ -21,57 +19,63 @@ import androidx.compose.ui.Alignment import androidx.compose.ui.Modifier import androidx.compose.ui.platform.LocalDensity import androidx.compose.ui.viewinterop.AndroidView -import coil3.Uri import com.github.jing332.filepicker.base.FileImpl import com.tencent.tbs.reader.TbsFileInterfaceImpl import kotlinx.coroutines.flow.distinctUntilChanged import kotlinx.coroutines.flow.drop import kotlinx.coroutines.launch +private const val TAG = "DocumentPreviewer" internal actual fun DocumentPreviewer.setupLicense( license: String, - applicationContext: coil3.PlatformContext + applicationContext: coil3.PlatformContext, ) { val ctx = applicationContext.applicationContext as Context - TbsFileInterfaceImpl.setLicenseKey(license) - TbsFileInterfaceImpl.fileEnginePreCheck(ctx) - //初始化Engine - val isInit = if(TbsFileInterfaceImpl.isEngineLoaded().not()){ - TbsFileInterfaceImpl.initEngine(ctx) - }else { - DocumentPreviewer.TMResult.SUCCESS.code - } -// if(Build.VERSION.SDK_INT >= Build.VERSION_CODES.M){ -// if(!Settings.System.canWrite(ctx)){ -// val intent = Intent(Settings.ACTION_MANAGE_WRITE_SETTINGS) -// intent.setData(android.net.Uri.parse("package:" + ctx.packageName)) -// intent.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK) -// ctx.startActivity(intent) -// } -// } - this.currentState = DocumentPreviewer.TMResult.fromCode(isInit) - println("TbsFileInterfaceImpl.initEngine: ${this.currentState.message}") -} + val initCode = + try { + if (license.isBlank()) { + DocumentPreviewer.TMResult.UNSET.code + } else { + TbsFileInterfaceImpl.setLicenseKey(license) + TbsFileInterfaceImpl.fileEnginePreCheck(ctx) + if (TbsFileInterfaceImpl.isEngineLoaded()) { + DocumentPreviewer.TMResult.SUCCESS.code + } else { + TbsFileInterfaceImpl.initEngine(ctx) + } + } + } catch (t: Throwable) { + Log.e(TAG, "TbsFile initEngine threw an exception for package=${ctx.packageName}", t) + DocumentPreviewer.TMResult.UNKNOWN.code + } + this.currentInitCode = initCode + this.currentState = DocumentPreviewer.TMResult.fromCode(initCode) + Log.i( + TAG, + "TbsFile initEngine package=${ctx.packageName}, code=$initCode, " + + "state=${this.currentState.name}, engineLoaded=${runCatching { TbsFileInterfaceImpl.isEngineLoaded() }.getOrDefault(false)}", + ) +} @Composable internal actual fun DocumentPreviewer.documentView( document: FileImpl, - callback: (Boolean, String) -> Unit + callback: (Boolean, String) -> Unit, ) { - val file = remember { - mutableStateOf(document) - } + val file = remember { mutableStateOf(document) } val density = LocalDensity.current val scope = rememberCoroutineScope() - BoxWithConstraints(modifier = Modifier.fillMaxSize(), contentAlignment = Alignment.Center) { + + BoxWithConstraints( + modifier = Modifier.fillMaxSize(), + contentAlignment = Alignment.Center, + ) { val loadState = remember { mutableStateOf(false to "Loading document...") } + val documentView = remember { mutableStateOf(null) } - val documentView = remember { - mutableStateOf(null) - } - with(LocalDensity.current){ + with(LocalDensity.current) { Column(modifier = Modifier.fillMaxSize()) { AndroidView( factory = { context -> @@ -81,29 +85,24 @@ internal actual fun DocumentPreviewer.documentView( documentView.value = this } }, - update = { - - }, - modifier = Modifier.fillMaxWidth() - .wrapContentHeight() - + update = {}, + modifier = Modifier.fillMaxWidth().wrapContentHeight(), ) } } LaunchedEffect(documentView.value) { - if(documentView.value==null){ - return@LaunchedEffect - } + val view = documentView.value ?: return@LaunchedEffect scope.launch { - documentView.value?.setDocument(scope, file.value, density,this@documentView.currentState) { success, message -> + view.setDocument(scope, file.value, density, this@documentView.currentState) { success, message -> loadState.value = success to message } } } + DisposableEffect(documentView.value) { - if(documentView.value==null){ - return@DisposableEffect onDispose { } + if (documentView.value == null) { + return@DisposableEffect onDispose {} } onDispose { documentView.value?.dispose() @@ -111,26 +110,21 @@ internal actual fun DocumentPreviewer.documentView( } } - if (loadState.value.first.not()) { + if (!loadState.value.first) { + // Do not translate a missing/mismatched license into "not recharged"; those are different failures. Text( - text = loadState.value.second.let { - if(it.contains("未设置 licenseKey")){ - "tbs未充值,请联系管理员" - }else{ - it - } - }, - modifier = Modifier.align(Alignment.Center) + text = loadState.value.second, + modifier = Modifier.align(Alignment.Center), ) } + LaunchedEffect(Unit) { snapshotFlow { loadState.value } .drop(1) .distinctUntilChanged() - .collect{ - callback(it.first,it.second) + .collect { + callback(it.first, it.second) } } - } -} \ No newline at end of file +} From 3e25bc94abb1f02f7f6134f6863d0b7d57dee8c5 Mon Sep 17 00:00:00 2001 From: VickyLeu Date: Thu, 10 Sep 2026 00:45:05 -0700 Subject: [PATCH 03/11] fix: improve TBS file open diagnostics and cleanup --- .../kotlin/org/uooc/document/DocumentView.kt | 233 ++++++++++-------- 1 file changed, 135 insertions(+), 98 deletions(-) diff --git a/viewer/src/androidMain/kotlin/org/uooc/document/DocumentView.kt b/viewer/src/androidMain/kotlin/org/uooc/document/DocumentView.kt index d72d387..5bfac56 100644 --- a/viewer/src/androidMain/kotlin/org/uooc/document/DocumentView.kt +++ b/viewer/src/androidMain/kotlin/org/uooc/document/DocumentView.kt @@ -8,7 +8,6 @@ import android.util.Log import android.view.ViewTreeObserver import android.widget.FrameLayout import androidx.compose.ui.unit.Density -import androidx.compose.ui.unit.dp import com.github.jing332.filepicker.base.FileImpl import com.tencent.tbs.reader.ITbsReader import com.tencent.tbs.reader.TbsFileInterfaceImpl @@ -18,139 +17,177 @@ import kotlinx.coroutines.Dispatchers import kotlinx.coroutines.launch import kotlinx.coroutines.withContext import java.io.File -import kotlin.math.roundToInt -private val TAG = "DocumentPreviewer" +private const val TAG = "DocumentPreviewer" class DocumentView @JvmOverloads constructor( - context: Context, attrs: AttributeSet? = null + context: Context, + attrs: AttributeSet? = null, ) : FrameLayout(context, attrs) { private lateinit var currentState: DocumentPreviewer.TMResult + + @Suppress("UNUSED_PARAMETER") suspend fun setDocument( scope: CoroutineScope, file: FileImpl, density: Density, currentState: DocumentPreviewer.TMResult, - callback: (Boolean, String) -> Unit + callback: (Boolean, String) -> Unit, ) { this.currentState = currentState val completer = CompletableDeferred>() + scope.launch { - withContext(Dispatchers.IO) { - //增加下面一句解决没有TbsReaderTemp文件夹存在导致加载文件失败 - val bsReaderTemp = - FileUtils.getDir(context).toString() + File.separator + "TbsReaderTemp" - val bsReaderTempFile = File(bsReaderTemp) - if (!bsReaderTempFile.exists()) { - val mkdir: Boolean = bsReaderTempFile.mkdir() - if (!mkdir) { - Log.e(TAG, "创建$bsReaderTemp 失败") - completer.complete(false to "TbsReaderTemp缓存文件创建失败") + try { + withContext(Dispatchers.IO) { + val tbsReaderTemp = File(FileUtils.getDir(context), "TbsReaderTemp") + if (!tbsReaderTemp.exists() && !tbsReaderTemp.mkdirs()) { + Log.e(TAG, "Failed to create TBS temp directory: $tbsReaderTemp") + completeOnce(completer, false, "TbsReaderTemp缓存文件创建失败") return@withContext } - } - if (this@DocumentView.currentState.code != 0) { - completer.complete(false to "TbsFile Engine初始化失败") - return@withContext - } - //文件格式 - val fileExt = FileUtils.getFileType(file.toString()) - println("文件格式:$fileExt") - - - withContext(Dispatchers.Main) { - val bool = TbsFileInterfaceImpl.canOpenFileExt(fileExt) - Log.d(TAG, "文件是否支持$bool 文件路径:$file $bsReaderTemp $fileExt") - if (bool) { - //加载文件 - val localBundle = Bundle() - localBundle.putString("filePath", file.absolutePath.toString()) - localBundle.putString("tempPath", bsReaderTemp) - localBundle.putString("fileExt", fileExt) - - localBundle.putInt( - "set_content_view_width", - with(density) { measuredWidth.toFloat().dp.value.roundToInt() }) -// localBundle.putBoolean("file_reader_stream_mode", false)//设置为文件流打开模式 - localBundle.putInt( - "set_content_view_height", - with(density) { - measuredHeight.toFloat().dp.value.roundToInt().coerceAtLeast(200) - }) + + if (this@DocumentView.currentState != DocumentPreviewer.TMResult.SUCCESS) { + val initCode = DocumentPreviewer.currentInitCode + completeOnce( + completer, + false, + DocumentPreviewer.describeInitResult(initCode), + ) + return@withContext + } + + val fileExt = FileUtils.getFileType(file.toString()) + Log.d(TAG, "Opening document: ext=$fileExt") + + withContext(Dispatchers.Main) { + if (!TbsFileInterfaceImpl.canOpenFileExt(fileExt)) { + Log.e(TAG, "TBS cannot open extension: $fileExt") + completeOnce(completer, false, "文件格式不支持或者打开失败: $fileExt") + return@withContext + } + + val localBundle = Bundle().apply { + putString("filePath", file.absolutePath.toString()) + putString("tempPath", tbsReaderTemp.absolutePath) + putString("fileExt", fileExt) + // These values are pixels already. Converting px -> dp -> numeric px was incorrect. + putInt("set_content_view_width", measuredWidth.coerceAtLeast(1)) + putInt("set_content_view_height", measuredHeight.coerceAtLeast(200)) + } + this@DocumentView.post { - val ret = TbsFileInterfaceImpl.getInstance().openFileReader( - context, localBundle, - { code, args, msg -> - Log.e(TAG, "文件打开回调 $code $args $msg") - when (code) { - ITbsReader.OPEN_FILEREADER_STATUS_UI_CALLBACK -> { - if (args is Bundle) { - val id = args.getInt("typeId", 0) - val typeDes = - args.getString("typeDes", "fileReaderOpened") - if (ITbsReader.TBS_READER_TYPE_STATUS_UI_OPENED == id) { - //加密文档弹框取消需关闭activity -// Navigation.findNavController(getView()).popBackStack() - - } else if (ITbsReader.TBS_READER_TYPE_STATUS_UI_SHUTDOWN == id) { - //加密文档弹框取消需关闭activity -// Navigation.findNavController(getView()).popBackStack() - if (completer.isCompleted.not()) { - completer.complete(false to "文件打开2失败:${msg}") + try { + val ret = TbsFileInterfaceImpl.getInstance().openFileReader( + context, + localBundle, + { code, args, msg -> + Log.d(TAG, "TBS open callback code=$code, message=$msg") + when (code) { + ITbsReader.OPEN_FILEREADER_STATUS_UI_CALLBACK -> { + if (args is Bundle) { + val id = args.getInt("typeId", 0) + if (ITbsReader.TBS_READER_TYPE_STATUS_UI_SHUTDOWN == id) { + completeOnce( + completer, + false, + "文件阅读器已关闭${msg?.takeIf { it.isNotBlank() }?.let { ": $it" } ?: ""}", + ) } - } - } else { - if (completer.isCompleted.not()) { - completer.complete(false to "文件打开3失败:${msg}") + } else { + completeOnce( + completer, + false, + "文件阅读器状态异常${msg?.takeIf { it.isNotBlank() }?.let { ": $it" } ?: ""}", + ) } } - } - ITbsReader.NOTIFY_CANDISPLAY -> { - //文件即将显示 - Log.wtf("NOTIFY_CANDISPLAY", "文件即将显示") - completer.complete(true to "") + ITbsReader.NOTIFY_CANDISPLAY -> { + completeOnce(completer, true, "") + } } + }, + this@DocumentView, + ) - else -> Unit - } - }, this@DocumentView - ) - if (ret == 0) { - } else { - completer.complete(false to "error:$ret") + if (ret != 0) { + completeOnce( + completer, + false, + describeOpenReaderFailure(ret), + ) + } + } catch (t: Throwable) { + Log.e(TAG, "TBS openFileReader threw", t) + completeOnce( + completer, + false, + "TbsFile 打开文件异常: ${t.message ?: t::class.simpleName}", + ) } } - } else { - Log.e(TAG, "文件打开失败!文件格式暂不支持") - completer.complete(false to "文件格式不支持或者打开失败") } } + } catch (t: Throwable) { + Log.e(TAG, "Document preparation failed", t) + completeOnce( + completer, + false, + "文档预览初始化异常: ${t.message ?: t::class.simpleName}", + ) } } - val (rlt, msg) = completer.await() - callback.invoke(rlt, msg) + + val (result, message) = completer.await() + callback(result, message) } override fun onConfigurationChanged(newConfig: Configuration?) { super.onConfigurationChanged(newConfig) - this.getViewTreeObserver().addOnGlobalLayoutListener(object : - ViewTreeObserver.OnGlobalLayoutListener { - override fun onGlobalLayout() { - this@DocumentView.getViewTreeObserver().removeOnGlobalLayoutListener(this) - val w: Int = this@DocumentView.width - val h: Int = this@DocumentView.height - TbsFileInterfaceImpl.getInstance().onSizeChanged(w, h) - } - }) + viewTreeObserver.addOnGlobalLayoutListener( + object : ViewTreeObserver.OnGlobalLayoutListener { + override fun onGlobalLayout() { + viewTreeObserver.removeOnGlobalLayoutListener(this) + TbsFileInterfaceImpl.getInstance().onSizeChanged(width, height) + } + }, + ) } fun dispose() { try { - this.removeAllViews() - val instance = TbsFileInterfaceImpl.getInstance() - instance.closeFileReader() - } catch (ignore: Exception) { + TbsFileInterfaceImpl.getInstance().closeFileReader() + } catch (t: Throwable) { + Log.w(TAG, "TBS closeFileReader failed", t) + } finally { + removeAllViews() } } -} \ No newline at end of file + + private fun completeOnce( + completer: CompletableDeferred>, + success: Boolean, + message: String, + ) { + if (!completer.isCompleted) { + completer.complete(success to message) + } + } + + private fun describeOpenReaderFailure(code: Int): String { + val reason = + when (code) { + -1 -> "参数错误" + -2 -> "Reader 尚未加载" + -3 -> "鉴权失败" + -4 -> "Engine 正在加载" + -5 -> "阅读器 View 初始化失败" + -6 -> "文件格式不支持" + -7 -> "Reader 入口正在异步加载" + -8 -> "TBS Core 正在下载或尚未就绪" + else -> "未知错误" + } + return "TbsFile 打开文件失败 (code=$code, reason=$reason)" + } +} From 8f904cda28f12922b43e514692ba23986e75e4e8 Mon Sep 17 00:00:00 2001 From: VickyLeu Date: Thu, 10 Sep 2026 00:45:22 -0700 Subject: [PATCH 04/11] chore: bump DocumentViewer to 2.0.2 --- build.gradle.kts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/build.gradle.kts b/build.gradle.kts index 16f845e..a31e196 100644 --- a/build.gradle.kts +++ b/build.gradle.kts @@ -15,7 +15,7 @@ plugins { } val publishGroup = "io.github.vickyleu.documentviewer" -val publishVersion = "2.0.1" +val publishVersion = "2.0.2" val publishRepo = "DocumentViewer" val publishUrl = "https://github.com/vickyleu/$publishRepo" val publishCoordinates = mapOf( From e27f4ed6d658f0ffbbdbffe7dad4d89a370ae47f Mon Sep 17 00:00:00 2001 From: VickyLeu Date: Thu, 10 Sep 2026 01:10:20 -0700 Subject: [PATCH 05/11] fix: align TbsFile engine init with current SDK --- .../kotlin/org/uooc/document/DocumentPreviewer.android.kt | 2 -- 1 file changed, 2 deletions(-) diff --git a/viewer/src/androidMain/kotlin/org/uooc/document/DocumentPreviewer.android.kt b/viewer/src/androidMain/kotlin/org/uooc/document/DocumentPreviewer.android.kt index 9cbad46..254f251 100644 --- a/viewer/src/androidMain/kotlin/org/uooc/document/DocumentPreviewer.android.kt +++ b/viewer/src/androidMain/kotlin/org/uooc/document/DocumentPreviewer.android.kt @@ -38,7 +38,6 @@ internal actual fun DocumentPreviewer.setupLicense( DocumentPreviewer.TMResult.UNSET.code } else { TbsFileInterfaceImpl.setLicenseKey(license) - TbsFileInterfaceImpl.fileEnginePreCheck(ctx) if (TbsFileInterfaceImpl.isEngineLoaded()) { DocumentPreviewer.TMResult.SUCCESS.code } else { @@ -111,7 +110,6 @@ internal actual fun DocumentPreviewer.documentView( } if (!loadState.value.first) { - // Do not translate a missing/mismatched license into "not recharged"; those are different failures. Text( text = loadState.value.second, modifier = Modifier.align(Alignment.Center), From 6e7504e79e8eaf6cff20f65bc8c2066d76d8a0ab Mon Sep 17 00:00:00 2001 From: VickyLeu Date: Thu, 10 Sep 2026 01:21:20 -0700 Subject: [PATCH 06/11] docs: pin expected Tencent TbsFile SDK --- viewer/libs/TBS_FILE_SDK.md | 14 ++++++++++++++ 1 file changed, 14 insertions(+) create mode 100644 viewer/libs/TBS_FILE_SDK.md diff --git a/viewer/libs/TBS_FILE_SDK.md b/viewer/libs/TBS_FILE_SDK.md new file mode 100644 index 0000000..ad5fb88 --- /dev/null +++ b/viewer/libs/TBS_FILE_SDK.md @@ -0,0 +1,14 @@ +# Tencent TbsFile SDK binary + +This module expects the official Tencent Browsing Service document SDK binary: + +- Version: `V1.0.8.6000124` +- Release date: `2026-08-06` +- Variant: comprehensive document formats, `64-bit + 32-bit` +- Expected file path: `viewer/libs/TbsFileSdk.aar` +- Official SDK page: `https://cloud.tencent.com/document/product/1645/83899` +- Official binary URL at the time of this update: `https://tbs.imtt.qq.com/sdk/release/TbsFileSdk_base_universal_release_1.0.8.6000124.20260806101826.aar` + +The AAR is intentionally replaced and verified on a local build machine because the repository connector used for source maintenance does not upload binary repository contents. + +Before publishing `viewer`, verify the downloaded AAR came from the official Tencent host, record its SHA-256, run a clean Android build, and confirm a smoke-test document can be opened. From 0cfaea35820b9ed33b51c82bde493dd4f4234f76 Mon Sep 17 00:00:00 2001 From: VickyLeu Date: Thu, 10 Sep 2026 18:24:09 -0700 Subject: [PATCH 07/11] docs: fix TBS AAR replacement path --- viewer/libs/TBS_FILE_SDK.md | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/viewer/libs/TBS_FILE_SDK.md b/viewer/libs/TBS_FILE_SDK.md index ad5fb88..b5d1d7b 100644 --- a/viewer/libs/TBS_FILE_SDK.md +++ b/viewer/libs/TBS_FILE_SDK.md @@ -5,10 +5,10 @@ This module expects the official Tencent Browsing Service document SDK binary: - Version: `V1.0.8.6000124` - Release date: `2026-08-06` - Variant: comprehensive document formats, `64-bit + 32-bit` -- Expected file path: `viewer/libs/TbsFileSdk.aar` +- Expected file path: `viewer/src/androidMain/libs/TbsFileSdk.aar` - Official SDK page: `https://cloud.tencent.com/document/product/1645/83899` - Official binary URL at the time of this update: `https://tbs.imtt.qq.com/sdk/release/TbsFileSdk_base_universal_release_1.0.8.6000124.20260806101826.aar` -The AAR is intentionally replaced and verified on a local build machine because the repository connector used for source maintenance does not upload binary repository contents. +The repository currently contains an AAR at that path, but its binary version has not been verified/replaced through the source connector. Replace it on the local build machine with the official `V1.0.8.6000124` binary before validation and publication. Before publishing `viewer`, verify the downloaded AAR came from the official Tencent host, record its SHA-256, run a clean Android build, and confirm a smoke-test document can be opened. From e39101e1c26ae98591ec5ac69d5b4dbc9e048966 Mon Sep 17 00:00:00 2001 From: VickyLeu Date: Thu, 10 Sep 2026 18:36:51 -0700 Subject: [PATCH 08/11] build: align Gradle with Kotlin 2.3.21 --- gradle/wrapper/gradle-wrapper.properties | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/gradle/wrapper/gradle-wrapper.properties b/gradle/wrapper/gradle-wrapper.properties index 77acd81..fac4940 100644 --- a/gradle/wrapper/gradle-wrapper.properties +++ b/gradle/wrapper/gradle-wrapper.properties @@ -1,6 +1,6 @@ #Fri Aug 23 04:29:59 CST 2024 distributionBase=GRADLE_USER_HOME distributionPath=wrapper/dists -distributionUrl=https\://services.gradle.org/distributions/gradle-9.5.0-bin.zip +distributionUrl=https\://services.gradle.org/distributions/gradle-9.3.0-bin.zip zipStoreBase=GRADLE_USER_HOME zipStorePath=wrapper/dists From 0c832f205dfbb0e2cde2be7c3efbf16a6401faa6 Mon Sep 17 00:00:00 2001 From: VickyLeu Date: Thu, 10 Sep 2026 18:36:57 -0700 Subject: [PATCH 09/11] ci: add viewer build validation --- .github/workflows/build.yml | 19 +++++++++++++++++++ 1 file changed, 19 insertions(+) create mode 100644 .github/workflows/build.yml diff --git a/.github/workflows/build.yml b/.github/workflows/build.yml new file mode 100644 index 0000000..562ea23 --- /dev/null +++ b/.github/workflows/build.yml @@ -0,0 +1,19 @@ +name: Build viewer +on: + pull_request: + push: + branches: + - main + +jobs: + build-viewer: + runs-on: ubuntu-24.04 + steps: + - uses: actions/checkout@v4 + - uses: actions/setup-java@v5 + with: + distribution: corretto + java-version: '17' + - name: Build viewer + shell: bash + run: ./gradlew :viewer:assemble --stacktrace --console=plain From 7363cc1103affde531f96cb1a2d2bde810bbaa1b Mon Sep 17 00:00:00 2001 From: VickyLeu Date: Thu, 10 Sep 2026 18:38:18 -0700 Subject: [PATCH 10/11] ci: validate TBS update branch --- .github/workflows/build.yml | 1 + 1 file changed, 1 insertion(+) diff --git a/.github/workflows/build.yml b/.github/workflows/build.yml index 562ea23..3d026ba 100644 --- a/.github/workflows/build.yml +++ b/.github/workflows/build.yml @@ -4,6 +4,7 @@ on: push: branches: - main + - fix/tbs-sdk-1.0.8.6000124 jobs: build-viewer: From a47d2fca03e64fd5003ef6e399fecc59d51e37db Mon Sep 17 00:00:00 2001 From: VickyLeu Date: Thu, 10 Sep 2026 18:38:50 -0700 Subject: [PATCH 11/11] build: make local FilePicker composite optional --- settings.gradle.kts | 10 ++++++---- 1 file changed, 6 insertions(+), 4 deletions(-) diff --git a/settings.gradle.kts b/settings.gradle.kts index ddc32b9..b229b8e 100644 --- a/settings.gradle.kts +++ b/settings.gradle.kts @@ -36,10 +36,12 @@ dependencyResolutionManagement { } } - -includeBuild("../ComposeFilePicker") { - dependencySubstitution { - substitute(module("com.vickyleu.kmp.filepicker:filepicker")).using(project(":filePicker")) +val composeFilePickerDir = file("../ComposeFilePicker") +if (composeFilePickerDir.isDirectory) { + includeBuild(composeFilePickerDir) { + dependencySubstitution { + substitute(module("io.github.vickyleu.filepicker:filepicker")).using(project(":filePicker")) + } } }