Commit dbc6a6c1 by huangzhicong
parent 92a44089
......@@ -49,7 +49,10 @@ dependencies {
implementation 'androidx.constraintlayout:constraintlayout:2.1.3'
implementation 'com.google.code.gson:gson:2.9.0'
implementation('com.journeyapps:zxing-android-embedded:4.3.0') { transitive = false }
implementation 'com.google.zxing:core:3.3.0'
implementation 'com.google.zxing:core:3.4.0'
implementation 'com.github.bumptech.glide:glide:4.13.0'
annotationProcessor 'com.github.bumptech.glide:compiler:4.13.0'
implementation 'io.github.lucksiege:pictureselector:v3.0.5'
def datastore_version = "1.0.0"
implementation "androidx.datastore:datastore:$datastore_version"
......
const _Jssdk = class {
constructor({ timeout, isNative }) {
this.eventList = /* @__PURE__ */ new Map();
this.callBackName = "__maxrockyWebViewJavascriptBridgeCallBack__";
this.timeout = timeout != null ? timeout : 1e3 * 5;
this.isNative = isNative != null ? isNative : false;
window.onMaxrockyReady = () => {
this.isNative = true;
};
window.__maxrockyWebViewJavascriptBridgeCallBack__ = (...res) => {
try {
const [_, response] = res;
const { data, code, msg, sessionId } = JSON.parse(response);
const promiseFun = this.eventList.get(sessionId);
if (!promiseFun)
return;
const [resolve, reject, timer] = promiseFun;
code === 0 ? resolve(data) : reject(msg);
clearTimeout(timer);
this.eventList.delete(sessionId);
} catch (error) {
console.error(error);
}
};
window.__maxrockyWebViewJavascriptBridgeCallBack__ = this.jsBridgeCallBack.bind(this);
}
getSessionid() {
if (_Jssdk.sessionid >= 1e4)
_Jssdk.sessionid = 0;
return _Jssdk.sessionid;
}
jsBridgeCallBack(...res) {
jsBridgeCallBack(_, response) {
try {
const [_, response] = res;
const { data, code, msg, sessionId } = JSON.parse(response);
const promiseFun = this.eventList.get(sessionId);
if (!promiseFun)
......@@ -54,7 +38,7 @@ const _Jssdk = class {
window.maxrocky.execute(sessionId, methodName, params);
});
}
onSetLocalStroge(key, value) {
onSetLocalStorage(key, value) {
if (this.isNative) {
return this.callHandler("storageValue", JSON.stringify({ key, value }));
}
......@@ -62,7 +46,7 @@ const _Jssdk = class {
resolve(localStorage.setItem(key, value));
});
}
onGetLocalStroge(key) {
onGetLocalStorage(key) {
if (this.isNative) {
return this.callHandler("acquireValue", key);
}
......@@ -70,7 +54,7 @@ const _Jssdk = class {
resolve(localStorage.getItem(key));
});
}
onRemoveLocalStroge(key) {
onRemoveLocalStorage(key) {
if (this.isNative) {
return this.callHandler("clearValue", key);
}
......@@ -78,7 +62,7 @@ const _Jssdk = class {
resolve(localStorage.removeItem(key));
});
}
onClearLocalStroge() {
onClearLocalStorage() {
if (this.isNative) {
return new Promise((_, reject) => {
reject("TODO");
......@@ -96,9 +80,9 @@ const _Jssdk = class {
reject("");
});
}
pickerPhoto() {
pickerPhoto(count = 1) {
if (this.isNative) {
return this.callHandler("selectPhoto");
return this.callHandler("selectPhoto", JSON.stringify({ count }));
}
return new Promise((_, reject) => {
reject();
......@@ -112,9 +96,9 @@ const _Jssdk = class {
reject();
});
}
startRecordAudio() {
startRecordAudio(duration = 60) {
if (this.isNative) {
return this.callHandler("recordAudio");
return this.callHandler("recordAudio", duration);
}
return new Promise((_, reject) => {
reject();
......@@ -153,6 +137,4 @@ const _Jssdk = class {
};
let Jssdk = _Jssdk;
Jssdk.sessionid = 0;
var jssdk = new Jssdk({});
// export { jssdk as default };
//# sourceMappingURL=MRJsskd.es.js.map
const jssdk = new Jssdk({});
......@@ -30,12 +30,21 @@ class WebViewProxy(
fun imgSuccess(method: String, result: Result<MediaResult>, uri: Uri?) =
fileToBase64(method, result, uri, "data:image/png")
fun imgsSuccess(method: String, result: Result<List<MediaResult>>, uris: List<Uri>) =
filesToBase64(method, result, uris, "data:image/png")
fun audioSuccess(method: String, result: Result<MediaResult>, uri: Uri?) =
fileToBase64(method, result, uri, "data:audio/wav")
fun audiosSuccess(method: String, result: Result<List<MediaResult>>, uris: List<Uri>) =
filesToBase64(method, result, uris, "data:audio/wav")
fun videoSuccess(method: String, result: Result<MediaResult>, uri: Uri?) =
fileToBase64(method, result, uri, "data:video/mp4")
fun videosSuccess(method: String, result: Result<List<MediaResult>>, uris: List<Uri>) =
filesToBase64(method, result, uris, "data:video/mp4")
fun <T> success(method: String, result: Result<T>, data: T? = null) {
val json = gson.toJson(result.apply { success(data) })
webView.evaluateJavascript("javascript:__maxrockyWebViewJavascriptBridgeCallBack__('$method', '$json')", null)
......@@ -76,4 +85,37 @@ class WebViewProxy(
}
}
}
private fun filesToBase64(
method: String,
result: Result<List<MediaResult>>,
uri: List<Uri>,
encoder: String
) {
if (uri.isEmpty()) {
failed(method, result, "文件不存在")
return
}
fragment.lifecycleScope.launch(Dispatchers.IO) {
val resultList = uri.mapNotNull {
contentResolver.openInputStream(it).runCatching {
if (this == null) {
return@runCatching null
}
val bytes = ByteArray(this.available())
this.read(bytes)
val base64 = Base64.encodeToString(bytes, Base64.DEFAULT)
val encodeBase64 = "$encoder;base64,${URLEncoder.encode(base64, "UTF-8")}"
return@runCatching MediaResult(it.toString(), encodeBase64)
}.getOrNull()
}
if (resultList.isEmpty()) {
withContext(Dispatchers.Main) { failed(method, result, "文件打开异常") }
return@launch
}
withContext(Dispatchers.Main) {
withContext(Dispatchers.Main) { success(method, result, resultList) }
}
}
}
}
\ No newline at end of file
package com.maxrocky.nativeview.contract
import android.Manifest
import android.net.Uri
import android.util.Log
import android.widget.Toast
import androidx.activity.result.ActivityResultLauncher
import androidx.activity.result.contract.ActivityResultContracts
import androidx.fragment.app.Fragment
import com.luck.picture.lib.basic.PictureSelector
import com.luck.picture.lib.config.SelectMimeType
import com.luck.picture.lib.config.SelectModeConfig
import com.luck.picture.lib.entity.LocalMedia
import com.luck.picture.lib.interfaces.OnResultCallbackListener
import com.luck.picture.lib.language.LanguageConfig
import com.maxrocky.nativeview.R
import com.maxrocky.nativeview.WebViewProxy
import com.maxrocky.nativeview.model.MediaResult
import com.maxrocky.nativeview.model.Result
import com.maxrocky.nativeview.utils.GlideEngine
import com.maxrocky.nativeview.utils.canMediaGetAbsolutePath
import com.maxrocky.nativeview.utils.mediaAbsolutePath
class SelectPhotoContract(fragment: Fragment, proxy: WebViewProxy) : IContract(fragment, proxy) {
companion object {
private const val TAG = "SelectPhotoContract"
private const val METHOD = "selectPhoto"
}
private val result: Result<MediaResult> = Result()
private val result: Result<List<MediaResult>> = Result()
private var maxSelectNum: Int = 1
private var requestPermission: ActivityResultLauncher<String>
private var selectPhoto: ActivityResultLauncher<String> =
fragment.registerForActivityResult(ActivityResultContracts.GetContent()) { uri ->
if (uri == null) {
val errorMsg = fragment.getString(R.string.select_photo_failed)
private var requestPermission: ActivityResultLauncher<String> =
fragment.registerForActivityResult(ActivityResultContracts.RequestPermission()) { permission ->
if (!permission) {
val errorMsg = fragment.getString(R.string.read_storage_denied)
Toast.makeText(context, errorMsg, Toast.LENGTH_LONG).show()
proxy.failed(METHOD, result, errorMsg)
result.reset()
return@registerForActivityResult
}
Log.d(TAG, "select photo: $uri")
val absolutePath: String?
PictureSelector.create(fragment)
.openGallery(SelectMimeType.ofImage())
.setLanguage(LanguageConfig.CHINESE)
.setImageEngine(GlideEngine.createGlideEngine())
.isDisplayCamera(false)
.setSelectionMode(SelectModeConfig.MULTIPLE)
.setMinSelectNum(1)
.setMaxSelectNum(maxSelectNum)
.forResult(object : OnResultCallbackListener<LocalMedia?> {
override fun onResult(list: ArrayList<LocalMedia?>?) {
if (list == null || list.isEmpty()) {
val errorMsg = fragment.getString(R.string.select_photo_failed)
Toast.makeText(context, errorMsg, Toast.LENGTH_LONG).show()
proxy.failed(METHOD, result, errorMsg)
result.reset()
return
}
Log.d(TAG, "select photos: ${list.size}")
val absolutePath: List<String?>
if (canMediaGetAbsolutePath()) {
absolutePath = uri.mediaAbsolutePath(contentResolver)
Log.d(TAG, "absolute path: $absolutePath")
absolutePath = list.mapNotNull { it?.originalPath }
Log.d(TAG, "absolute path: ${absolutePath.size}")
}
proxy.imgSuccess(METHOD, result, uri)
proxy.imgsSuccess(METHOD, result, list.mapNotNull { Uri.parse(it?.path) })
result.reset()
}
init {
requestPermission = fragment.registerForActivityResult(ActivityResultContracts.RequestPermission()) { permission ->
if (!permission) {
val errorMsg = fragment.getString(R.string.read_storage_denied)
Toast.makeText(context, errorMsg, Toast.LENGTH_LONG).show()
override fun onCancel() {
val errorMsg = fragment.getString(R.string.select_photo_failed)
proxy.failed(METHOD, result, errorMsg)
result.reset()
return@registerForActivityResult
}
selectPhoto.launch("image/*")
}
})
}
fun selectPhoto(sessionId: Int) {
fun selectPhoto(sessionId: Int, selectNum: Int) {
maxSelectNum = selectNum
result.setupSessionId(sessionId)
requestPermission.launch(Manifest.permission.READ_EXTERNAL_STORAGE)
}
......
......@@ -18,9 +18,11 @@ import com.maxrocky.nativeview.R
import com.maxrocky.nativeview.WebViewProxy
import com.maxrocky.nativeview.contract.*
import com.maxrocky.nativeview.model.StorageOptions
import com.maxrocky.nativeview.model.TakePhotoOptions
import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.launch
class MainFragment : Fragment() {
companion object {
private const val TAG = "MainFragment"
......@@ -82,19 +84,6 @@ class MainFragment : Fragment() {
}
}
wv.webViewClient = object : WebViewClient() {
override fun shouldOverrideUrlLoading(view: WebView?, url: String?): Boolean {
Log.d(TAG, "shouldOverrideUrlLoading old: $url")
return super.shouldOverrideUrlLoading(view, url)
}
override fun shouldOverrideUrlLoading(
view: WebView?,
request: WebResourceRequest?
): Boolean {
Log.d(TAG, "shouldOverrideUrlLoading new: ${request?.url}")
return super.shouldOverrideUrlLoading(view, request)
}
override fun onReceivedSslError(
view: WebView?,
handler: SslErrorHandler?,
......@@ -152,7 +141,14 @@ class MainFragment : Fragment() {
fun execute(sessionId: Int, method: String, args: String) {
when (method) {
"selectFile" -> selectFileContract.selectFile(sessionId, args)
"selectPhoto" -> selectPhotoContract.selectPhoto(sessionId)
"selectPhoto" -> {
if (args.isEmpty()) {
selectPhotoContract.selectPhoto(sessionId, 1)
} else {
val options = gson.fromJson(args, TakePhotoOptions::class.java)
selectPhotoContract.selectPhoto(sessionId, options.count)
}
}
"openCamera" -> cameraContract.openCamera(sessionId)
"recordAudio" ->
recordAudioContract.recordAudio(sessionId, args.toIntOrNull())
......@@ -161,10 +157,7 @@ class MainFragment : Fragment() {
"captureVideo" -> videoContract.captureVideo(sessionId)
"qrcode" -> qrCodeContract.qrcode(sessionId)
"storageValue" -> {
val options = gson.fromJson<StorageOptions<String, String>>(
args,
StorageOptions::class.java
)
val options = gson.fromJson(args, StorageOptions::class.java)
storageContract.storageValue(sessionId, options.key, options.value)
}
"acquireValue" -> storageContract.acquireValue(sessionId, args)
......
package com.maxrocky.nativeview.model
data class StorageOptions<T, U>(val key: T, val value: U)
data class TakePhotoOptions(val count: Int)
data class StorageOptions(val key: String, val value: String)
package com.maxrocky.nativeview.utils
import android.content.Context
import android.graphics.Bitmap
import android.graphics.drawable.Drawable
import android.widget.ImageView
import com.bumptech.glide.Glide
import com.bumptech.glide.load.resource.bitmap.CenterCrop
import com.bumptech.glide.load.resource.bitmap.RoundedCorners
import com.bumptech.glide.request.target.CustomTarget
import com.bumptech.glide.request.transition.Transition
import com.luck.picture.lib.engine.ImageEngine
import com.luck.picture.lib.interfaces.OnCallbackListener
import com.luck.picture.lib.utils.ActivityCompatHelper
import com.maxrocky.nativeview.R
/**
* @author:luck
* @date:2019-11-13 17:02
* @describe:Glide加载引擎
*/
class GlideEngine private constructor() : ImageEngine {
/**
* 加载图片
*
* @param context 上下文
* @param url 资源url
* @param imageView 图片承载控件
*/
override fun loadImage(context: Context, url: String, imageView: ImageView) {
if (!ActivityCompatHelper.assertValidRequest(context)) {
return
}
Glide.with(context)
.load(url)
.into(imageView)
}
/**
* 加载指定url并返回bitmap
*
* @param context 上下文
* @param url 资源url
* @param maxWidth 资源最大加载尺寸
* @param maxHeight 资源最大加载尺寸
* @param call 回调接口
*/
override fun loadImageBitmap(
context: Context,
url: String,
maxWidth: Int,
maxHeight: Int,
call: OnCallbackListener<Bitmap>?
) {
if (!ActivityCompatHelper.assertValidRequest(context)) {
return
}
Glide.with(context)
.asBitmap()
.override(maxWidth, maxHeight)
.load(url)
.into(object : CustomTarget<Bitmap?>() {
override fun onResourceReady(resource: Bitmap, transition: Transition<in Bitmap?>?) {
call?.onCall(resource)
}
override fun onLoadFailed(errorDrawable: Drawable?) {
call?.onCall(null)
}
override fun onLoadCleared(placeholder: Drawable?) {}
})
}
/**
* 加载相册目录封面
*
* @param context 上下文
* @param url 图片路径
* @param imageView 承载图片ImageView
*/
override fun loadAlbumCover(context: Context, url: String, imageView: ImageView) {
if (!ActivityCompatHelper.assertValidRequest(context)) {
return
}
Glide.with(context)
.asBitmap()
.load(url)
.override(180, 180)
.sizeMultiplier(0.5f)
.transform(CenterCrop(), RoundedCorners(8))
.placeholder(R.drawable.ps_image_placeholder)
.into(imageView)
}
/**
* 加载图片列表图片
*
* @param context 上下文
* @param url 图片路径
* @param imageView 承载图片ImageView
*/
override fun loadGridImage(context: Context, url: String, imageView: ImageView) {
if (!ActivityCompatHelper.assertValidRequest(context)) {
return
}
Glide.with(context)
.load(url)
.override(200, 200)
.centerCrop()
.placeholder(R.drawable.ps_image_placeholder)
.into(imageView)
}
override fun pauseRequests(context: Context) {
Glide.with(context).pauseRequests()
}
override fun resumeRequests(context: Context) {
Glide.with(context).resumeRequests()
}
companion object {
private var instance: GlideEngine? = null
fun createGlideEngine(): GlideEngine? {
if (null == instance) {
synchronized(GlideEngine::class.java) {
if (null == instance) {
instance = GlideEngine()
}
}
}
return instance
}
}
}
\ No newline at end of file
<vector xmlns:android="http://schemas.android.com/apk/res/android"
android:width="24dp"
android:height="24dp"
android:viewportWidth="24"
android:viewportHeight="24"
android:tint="?attr/colorControlNormal">
<path
android:fillColor="@android:color/white"
android:pathData="M21,19V5c0,-1.1 -0.9,-2 -2,-2H5c-1.1,0 -2,0.9 -2,2v14c0,1.1 0.9,2 2,2h14c1.1,0 2,-0.9 2,-2zM8.5,13.5l2.5,3.01L14.5,12l4.5,6H5l3.5,-4.5z"/>
</vector>
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or sign in to comment