feat: 对齐常驻商城与七鱼客服交互

- 同步商城 H5 登录态与原生 token
- 完善商城/原生 tab 切换、动画及返回交互
- 修复七鱼客服键盘避让和状态栏恢复
- 调整开屏跳过按钮与登录入口交互
This commit is contained in:
jingyun committed 2026-09-07 15:47:01 +08:00
1 parent b4343ea528
commit 3b089e2205
37 files changed
+2970 -375

No files matched your search

+13
View File
@@ -3,6 +3,9 @@
<uses-permission android:name="android.permission.ACCESS_NETWORK_STATE" />
<uses-permission android:name="android.permission.ACCESS_WIFI_STATE" />
<uses-permission android:name="android.permission.READ_PHONE_STATE" />
<uses-permission android:name="android.permission.CAMERA" />
<uses-permission android:name="android.permission.RECORD_AUDIO" />
<uses-permission android:name="android.permission.MODIFY_AUDIO_SETTINGS" />
<uses-permission android:name="android.permission.REQUEST_INSTALL_PACKAGES" />
<uses-permission android:name="android.permission.POST_NOTIFICATIONS" />
<application
@@ -55,6 +58,16 @@
android:name="UMENG_MESSAGE_SECRET"
android:value="${UMENG_MESSAGE_SECRET}" />
<provider
android:name="androidx.core.content.FileProvider"
android:authorities="${applicationId}.fileprovider"
android:exported="false"
android:grantUriPermissions="true">
<meta-data
android:name="android.support.FILE_PROVIDER_PATHS"
android:resource="@xml/web_capture_paths" />
</provider>
</application>
<!-- Required to query activities that can process text, see:
https://developer.android.com/training/package-visibility and
@@ -1,11 +1,28 @@
package com.jinzhi.app
import android.app.Activity
import android.content.ActivityNotFoundException
import android.content.ClipData
import android.content.Intent
import android.net.Uri
import android.provider.MediaStore
import androidx.core.content.FileProvider
import com.umeng.commonsdk.UMConfigure
import io.flutter.embedding.android.FlutterActivity
import io.flutter.embedding.engine.FlutterEngine
import io.flutter.plugin.common.MethodCall
import io.flutter.plugin.common.MethodChannel
import java.io.File
class MainActivity : FlutterActivity() {
companion object {
private const val WEB_FILE_PICKER_CHANNEL = "com.jinzhi.app/web_file_picker"
private const val WEB_FILE_PICKER_REQUEST_CODE = 9814
}
private var pendingFilePickerResult: MethodChannel.Result? = null
private var pendingCapturedFile: File? = null
override fun configureFlutterEngine(flutterEngine: FlutterEngine) {
super.configureFlutterEngine(flutterEngine)
MethodChannel(flutterEngine.dartExecutor.binaryMessenger, "jinzhi/privacy")
@@ -17,5 +34,140 @@ class MainActivity : FlutterActivity() {
result.notImplemented()
}
}
MethodChannel(flutterEngine.dartExecutor.binaryMessenger, WEB_FILE_PICKER_CHANNEL)
.setMethodCallHandler { call, result ->
if (call.method != "pickFiles") {
result.notImplemented()
return@setMethodCallHandler
}
openWebFilePicker(call, result)
}
}
private fun openWebFilePicker(call: MethodCall, result: MethodChannel.Result) {
if (pendingFilePickerResult != null) {
result.error("picker_in_progress", "A file picker is already open.", null)
return
}
val acceptTypes = call.argument<List<String>>("acceptTypes")
.orEmpty().map(String::trim).filter(String::isNotEmpty).distinct()
val allowMultiple = call.argument<Boolean>("allowMultiple") ?: false
val captureEnabled = call.argument<Boolean>("captureEnabled") ?: false
val isQiyuSession = call.argument<Boolean>("isQiyuSession") ?: false
if (captureEnabled && !allowMultiple) {
captureIntentFor(acceptTypes)?.let { intent ->
pendingFilePickerResult = result
launchWebFilePicker(intent, result)
return
}
}
if (isQiyuSession && captureEnabled && isImageOrVideoRequest(acceptTypes)) {
val captureIntent = captureIntentFor(acceptTypes)
if (captureIntent != null) {
pendingFilePickerResult = result
val chooser = Intent.createChooser(
fileContentIntent(acceptTypes, allowMultiple),
"选择操作",
).putExtra(Intent.EXTRA_INITIAL_INTENTS, arrayOf(captureIntent))
launchWebFilePicker(chooser, result)
return
}
}
pendingFilePickerResult = result
launchWebFilePicker(
Intent.createChooser(fileContentIntent(acceptTypes, allowMultiple), "选择文件"),
result,
)
}
private fun fileContentIntent(acceptTypes: List<String>, allowMultiple: Boolean) =
Intent(Intent.ACTION_GET_CONTENT)
.addCategory(Intent.CATEGORY_OPENABLE)
.putExtra(Intent.EXTRA_ALLOW_MULTIPLE, allowMultiple)
.apply {
if (acceptTypes.size == 1) type = acceptTypes.first()
else {
type = "*/*"
if (acceptTypes.isNotEmpty()) {
putExtra(Intent.EXTRA_MIME_TYPES, acceptTypes.toTypedArray())
}
}
}
private fun isImageOrVideoRequest(acceptTypes: List<String>): Boolean {
if (acceptTypes.isEmpty()) return false
val onlyImages = acceptTypes.all {
it.equals("image/*", ignoreCase = true) || it.startsWith("image/", ignoreCase = true)
}
val onlyVideos = acceptTypes.all {
it.equals("video/*", ignoreCase = true) || it.startsWith("video/", ignoreCase = true)
}
return onlyImages || onlyVideos
}
private fun captureIntentFor(acceptTypes: List<String>): Intent? {
val acceptsVideo = acceptTypes.any {
it.equals("video/*", ignoreCase = true) || it.startsWith("video/", ignoreCase = true)
}
val acceptsImage = acceptTypes.isEmpty() || acceptTypes.any {
it.equals("image/*", ignoreCase = true) || it.startsWith("image/", ignoreCase = true)
}
val action = when {
acceptsVideo && !acceptsImage -> MediaStore.ACTION_VIDEO_CAPTURE
acceptsImage -> MediaStore.ACTION_IMAGE_CAPTURE
else -> return null
}
val directory = File(cacheDir, "web_captures").apply { mkdirs() }
val suffix = if (action == MediaStore.ACTION_VIDEO_CAPTURE) ".mp4" else ".jpg"
val output = File.createTempFile("web_", suffix, directory)
val uri: Uri = FileProvider.getUriForFile(this, "$packageName.fileprovider", output)
pendingCapturedFile = output
return Intent(action)
.putExtra(MediaStore.EXTRA_OUTPUT, uri)
.addFlags(Intent.FLAG_GRANT_WRITE_URI_PERMISSION or Intent.FLAG_GRANT_READ_URI_PERMISSION)
.apply { clipData = ClipData.newRawUri("web_capture", uri) }
}
private fun launchWebFilePicker(intent: Intent, result: MethodChannel.Result) {
try {
startActivityForResult(intent, WEB_FILE_PICKER_REQUEST_CODE)
} catch (_: ActivityNotFoundException) {
pendingFilePickerResult = null
pendingCapturedFile?.delete()
pendingCapturedFile = null
result.success(emptyList<String>())
}
}
@Deprecated("Deprecated in Android, retained for FlutterActivity compatibility")
override fun onActivityResult(requestCode: Int, resultCode: Int, data: Intent?) {
super.onActivityResult(requestCode, resultCode, data)
if (requestCode != WEB_FILE_PICKER_REQUEST_CODE) return
val result = pendingFilePickerResult ?: return
pendingFilePickerResult = null
if (resultCode != Activity.RESULT_OK) {
pendingCapturedFile?.delete()
pendingCapturedFile = null
result.success(emptyList<String>())
return
}
val capturedFile = pendingCapturedFile
val hasCapturedFile = capturedFile?.length()?.let { it > 0L } == true
val uris = buildList {
if (hasCapturedFile) {
add(FileProvider.getUriForFile(
this@MainActivity, "$packageName.fileprovider", capturedFile!!,
).toString())
} else {
data?.data?.let { add(it.toString()) }
data?.clipData?.let { clip ->
for (index in 0 until clip.itemCount) add(clip.getItemAt(index).uri.toString())
}
}
}.distinct()
if (!hasCapturedFile) capturedFile?.delete()
pendingCapturedFile = null
result.success(uris)
}
}
@@ -0,0 +1,4 @@
<?xml version="1.0" encoding="utf-8"?>
<paths xmlns:android="http://schemas.android.com/apk/res/android">
<cache-path name="web_captures" path="web_captures/" />
</paths>