Skip to content
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
@@ -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
Expand All @@ -21,13 +19,13 @@ 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,
Expand All @@ -36,22 +34,27 @@ internal actual fun DocumentPreviewer.setupLicense(
val ctx = applicationContext.applicationContext as Context
TbsFileInterfaceImpl.setLicenseKey(license)
TbsFileInterfaceImpl.fileEnginePreCheck(ctx)
//初始化Engine
val isInit = if(TbsFileInterfaceImpl.isEngineLoaded().not()){

val initCode = if (!TbsFileInterfaceImpl.isEngineLoaded()) {
TbsFileInterfaceImpl.initEngine(ctx)
}else {
} 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}")

this.currentStateCode = initCode
this.currentState = DocumentPreviewer.TMResult.fromCode(initCode)

val message = if (initCode == DocumentPreviewer.TMResult.SUCCESS.code) {
"TbsFile initEngine success(package=${ctx.packageName})"
} else {
"${DocumentPreviewer.TMResult.diagnosticMessage(initCode, this.currentState)}, package=${ctx.packageName}"
}

if (initCode == DocumentPreviewer.TMResult.SUCCESS.code) {
Log.i(TAG, message)
} else {
Log.e(TAG, message)
}
}


Expand All @@ -71,7 +74,7 @@ internal actual fun DocumentPreviewer.documentView(
val documentView = remember {
mutableStateOf<DocumentView?>(null)
}
with(LocalDensity.current){
with(LocalDensity.current) {
Column(modifier = Modifier.fillMaxSize()) {
AndroidView(
factory = { context ->
Expand All @@ -82,41 +85,45 @@ internal actual fun DocumentPreviewer.documentView(
}
},
update = {

},
modifier = Modifier.fillMaxWidth()
.wrapContentHeight()

)
}
}

LaunchedEffect(documentView.value) {
if(documentView.value==null){
if (documentView.value == null) {
return@LaunchedEffect
}
scope.launch {
documentView.value?.setDocument(scope, file.value, density,this@documentView.currentState) { success, message ->
documentView.value?.setDocument(
scope,
file.value,
density,
this@documentView.currentState,
this@documentView.currentStateCode
) { 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()
documentView.value = null
}
}

if (loadState.value.first.not()) {
if (!loadState.value.first) {
Text(
text = loadState.value.second.let {
if(it.contains("未设置 licenseKey")){
if (it.contains("未设置 licenseKey")) {
"tbs未充值,请联系管理员"
}else{
} else {
it
}
},
Expand All @@ -127,10 +134,9 @@ internal actual fun DocumentPreviewer.documentView(
snapshotFlow { loadState.value }
.drop(1)
.distinctUntilChanged()
.collect{
callback(it.first,it.second)
.collect {
callback(it.first, it.second)
}
}

}
}
}
74 changes: 39 additions & 35 deletions viewer/src/androidMain/kotlin/org/uooc/document/DocumentView.kt
Original file line number Diff line number Diff line change
Expand Up @@ -20,24 +20,28 @@ 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
) : FrameLayout(context, attrs) {
private lateinit var currentState: DocumentPreviewer.TMResult
private var currentStateCode: Int = DocumentPreviewer.TMResult.UNKNOWN.code

suspend fun setDocument(
scope: CoroutineScope,
file: FileImpl,
density: Density,
currentState: DocumentPreviewer.TMResult,
currentStateCode: Int,
callback: (Boolean, String) -> Unit
) {
this.currentState = currentState
this.currentStateCode = currentStateCode
val completer = CompletableDeferred<Pair<Boolean, String>>()
scope.launch {
withContext(Dispatchers.IO) {
//增加下面一句解决没有TbsReaderTemp文件夹存在导致加载文件失败
// 增加下面一句解决没有 TbsReaderTemp 文件夹存在导致加载文件失败
val bsReaderTemp =
FileUtils.getDir(context).toString() + File.separator + "TbsReaderTemp"
val bsReaderTempFile = File(bsReaderTemp)
Expand All @@ -49,76 +53,76 @@ class DocumentView @JvmOverloads constructor(
return@withContext
}
}
if (this@DocumentView.currentState.code != 0) {
completer.complete(false to "TbsFile Engine初始化失败")

if (this@DocumentView.currentStateCode != DocumentPreviewer.TMResult.SUCCESS.code) {
val diagnostic = DocumentPreviewer.TMResult.diagnosticMessage(
this@DocumentView.currentStateCode,
this@DocumentView.currentState
)
Log.e(TAG, "$diagnostic, package=${context.packageName}")
completer.complete(false to diagnostic)
return@withContext
}
//文件格式
val fileExt = FileUtils.getFileType(file.toString())
println("文件格式:$fileExt")

// 文件格式
val fileExt = FileUtils.getFileType(file.toString())
Log.d(TAG, "文件格式:$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() })
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)
})
}
)
this@DocumentView.post {
val ret = TbsFileInterfaceImpl.getInstance().openFileReader(
context, localBundle,
{ code, args, msg ->
Log.e(TAG, "文件打开回调 $code $args $msg")
Log.e(TAG, "文件打开回调 code=$code args=$args msg=$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}")
if (ITbsReader.TBS_READER_TYPE_STATUS_UI_SHUTDOWN == id) {
if (!completer.isCompleted) {
completer.complete(false to "文件打开失败(code=$code): $msg")
}
}
} else {
if (completer.isCompleted.not()) {
completer.complete(false to "文件打开3失败:${msg}")
}
} else if (!completer.isCompleted) {
completer.complete(false to "文件打开失败(code=$code): $msg")
}
}

ITbsReader.NOTIFY_CANDISPLAY -> {
//文件即将显示
Log.wtf("NOTIFY_CANDISPLAY", "文件即将显示")
completer.complete(true to "")
Log.d(TAG, "文件即将显示")
if (!completer.isCompleted) {
completer.complete(true to "")
}
}

else -> Unit
}
}, this@DocumentView
)
if (ret == 0) {
} else {
completer.complete(false to "error:$ret")
if (ret != 0 && !completer.isCompleted) {
val message = "openFileReader失败(code=$ret)"
Log.e(TAG, "$message, file=$file")
completer.complete(false to message)
}
}
} else {
Expand All @@ -134,10 +138,10 @@ class DocumentView @JvmOverloads constructor(

override fun onConfigurationChanged(newConfig: Configuration?) {
super.onConfigurationChanged(newConfig)
this.getViewTreeObserver().addOnGlobalLayoutListener(object :
this.viewTreeObserver.addOnGlobalLayoutListener(object :
ViewTreeObserver.OnGlobalLayoutListener {
override fun onGlobalLayout() {
this@DocumentView.getViewTreeObserver().removeOnGlobalLayoutListener(this)
this@DocumentView.viewTreeObserver.removeOnGlobalLayoutListener(this)
val w: Int = this@DocumentView.width
val h: Int = this@DocumentView.height
TbsFileInterfaceImpl.getInstance().onSizeChanged(w, h)
Expand All @@ -153,4 +157,4 @@ class DocumentView @JvmOverloads constructor(
} catch (ignore: Exception) {
}
}
}
}
52 changes: 28 additions & 24 deletions viewer/src/commonMain/kotlin/org/uooc/document/DocumentPreviewer.kt
Original file line number Diff line number Diff line change
Expand Up @@ -6,44 +6,44 @@ import com.github.jing332.filepicker.base.FileImpl
object DocumentPreviewer {
var currentState = TMResult.UNKNOWN
internal set

/**
* Raw return value from TbsFileInterfaceImpl.initEngine().
* Keep this separately because UNKNOWN must not hide an unrecognised SDK error code.
*/
var currentStateCode: 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")
// Do not print the license key. It is a credential and may end up in CI/logcat logs.
println("Setting up document previewer (licenseConfigured=${license.isNotBlank()})")
setupLicense(license, applicationContext)
}


/**
* 初始化接口错误码
* intEngine 接口错误码为方法返回值。
* initEngine 接口错误码为方法返回值。
* 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 不匹配。
*
*
*
* 102: 未设置 licenseKey。
* 202: 请检查调用接口是否正确,应调用 setLicenseKey 接口而不是 setLicense 接口。
* 209: licenseKey 不匹配,需要检查 licenseKey 是否正确。
* 103、305: 请检查设备网络是否连通,并尝试切换网络。
* 212、322: 调用量包次数用完。
* 4001: licenseKey 不存在,请检查设置的 licenseKey 是否正确。
* 4002: 客户端包名和 licenseKey 不匹配。
*/
enum class TMResult(val code: Int = 0, val message: String = "") {
SUCCESS(0, "Success"),
MISMATCH(4002, "License key mismatch"),
// Keep the existing enum name for source compatibility; the documented code is 209.
MISMATCH(209, "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"),
Expand All @@ -58,11 +58,15 @@ object DocumentPreviewer {
fun fromCode(code: Int): TMResult {
return values().find { it.code == code }?.apply {
println("Found code $code ${this.message}")
} ?: run{
} ?: run {
println("Unknown code $code")
UNKNOWN
}
}

fun diagnosticMessage(code: Int, state: TMResult = fromCode(code)): String {
return "TbsFile Engine初始化失败(code=$code, state=${state.name}): ${state.message}"
}
}
}
}
Expand All @@ -77,4 +81,4 @@ internal expect fun DocumentPreviewer.documentView(
internal expect fun DocumentPreviewer.setupLicense(
license: String,
applicationContext: coil3.PlatformContext
)
)