Commit dbc6a6c1 by huangzhicong
parent 92a44089
...@@ -49,7 +49,10 @@ dependencies { ...@@ -49,7 +49,10 @@ dependencies {
implementation 'androidx.constraintlayout:constraintlayout:2.1.3' implementation 'androidx.constraintlayout:constraintlayout:2.1.3'
implementation 'com.google.code.gson:gson:2.9.0' implementation 'com.google.code.gson:gson:2.9.0'
implementation('com.journeyapps:zxing-android-embedded:4.3.0') { transitive = false } 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" def datastore_version = "1.0.0"
implementation "androidx.datastore:datastore:$datastore_version" implementation "androidx.datastore:datastore:$datastore_version"
......
const _Jssdk = class { const _Jssdk = class {
constructor({ timeout, isNative }) { constructor({ timeout, isNative }) {
this.eventList = /* @__PURE__ */ new Map(); this.eventList = /* @__PURE__ */ new Map();
this.callBackName = "__maxrockyWebViewJavascriptBridgeCallBack__";
this.timeout = timeout != null ? timeout : 1e3 * 5; this.timeout = timeout != null ? timeout : 1e3 * 5;
this.isNative = isNative != null ? isNative : false; this.isNative = isNative != null ? isNative : false;
window.onMaxrockyReady = () => { window.onMaxrockyReady = () => {
this.isNative = true; this.isNative = true;
}; };
window.__maxrockyWebViewJavascriptBridgeCallBack__ = (...res) => { window.__maxrockyWebViewJavascriptBridgeCallBack__ = this.jsBridgeCallBack.bind(this);
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);
}
};
} }
getSessionid() { getSessionid() {
if (_Jssdk.sessionid >= 1e4) if (_Jssdk.sessionid >= 1e4)
_Jssdk.sessionid = 0; _Jssdk.sessionid = 0;
return _Jssdk.sessionid; return _Jssdk.sessionid;
} }
jsBridgeCallBack(...res) { jsBridgeCallBack(_, response) {
try { try {
const [_, response] = res;
const { data, code, msg, sessionId } = JSON.parse(response); const { data, code, msg, sessionId } = JSON.parse(response);
const promiseFun = this.eventList.get(sessionId); const promiseFun = this.eventList.get(sessionId);
if (!promiseFun) if (!promiseFun)
...@@ -54,7 +38,7 @@ const _Jssdk = class { ...@@ -54,7 +38,7 @@ const _Jssdk = class {
window.maxrocky.execute(sessionId, methodName, params); window.maxrocky.execute(sessionId, methodName, params);
}); });
} }
onSetLocalStroge(key, value) { onSetLocalStorage(key, value) {
if (this.isNative) { if (this.isNative) {
return this.callHandler("storageValue", JSON.stringify({ key, value })); return this.callHandler("storageValue", JSON.stringify({ key, value }));
} }
...@@ -62,7 +46,7 @@ const _Jssdk = class { ...@@ -62,7 +46,7 @@ const _Jssdk = class {
resolve(localStorage.setItem(key, value)); resolve(localStorage.setItem(key, value));
}); });
} }
onGetLocalStroge(key) { onGetLocalStorage(key) {
if (this.isNative) { if (this.isNative) {
return this.callHandler("acquireValue", key); return this.callHandler("acquireValue", key);
} }
...@@ -70,7 +54,7 @@ const _Jssdk = class { ...@@ -70,7 +54,7 @@ const _Jssdk = class {
resolve(localStorage.getItem(key)); resolve(localStorage.getItem(key));
}); });
} }
onRemoveLocalStroge(key) { onRemoveLocalStorage(key) {
if (this.isNative) { if (this.isNative) {
return this.callHandler("clearValue", key); return this.callHandler("clearValue", key);
} }
...@@ -78,7 +62,7 @@ const _Jssdk = class { ...@@ -78,7 +62,7 @@ const _Jssdk = class {
resolve(localStorage.removeItem(key)); resolve(localStorage.removeItem(key));
}); });
} }
onClearLocalStroge() { onClearLocalStorage() {
if (this.isNative) { if (this.isNative) {
return new Promise((_, reject) => { return new Promise((_, reject) => {
reject("TODO"); reject("TODO");
...@@ -96,9 +80,9 @@ const _Jssdk = class { ...@@ -96,9 +80,9 @@ const _Jssdk = class {
reject(""); reject("");
}); });
} }
pickerPhoto() { pickerPhoto(count = 1) {
if (this.isNative) { if (this.isNative) {
return this.callHandler("selectPhoto"); return this.callHandler("selectPhoto", JSON.stringify({ count }));
} }
return new Promise((_, reject) => { return new Promise((_, reject) => {
reject(); reject();
...@@ -112,9 +96,9 @@ const _Jssdk = class { ...@@ -112,9 +96,9 @@ const _Jssdk = class {
reject(); reject();
}); });
} }
startRecordAudio() { startRecordAudio(duration = 60) {
if (this.isNative) { if (this.isNative) {
return this.callHandler("recordAudio"); return this.callHandler("recordAudio", duration);
} }
return new Promise((_, reject) => { return new Promise((_, reject) => {
reject(); reject();
...@@ -153,6 +137,4 @@ const _Jssdk = class { ...@@ -153,6 +137,4 @@ const _Jssdk = class {
}; };
let Jssdk = _Jssdk; let Jssdk = _Jssdk;
Jssdk.sessionid = 0; Jssdk.sessionid = 0;
var jssdk = new Jssdk({}); const jssdk = new Jssdk({});
// export { jssdk as default };
//# sourceMappingURL=MRJsskd.es.js.map
...@@ -30,12 +30,21 @@ class WebViewProxy( ...@@ -30,12 +30,21 @@ class WebViewProxy(
fun imgSuccess(method: String, result: Result<MediaResult>, uri: Uri?) = fun imgSuccess(method: String, result: Result<MediaResult>, uri: Uri?) =
fileToBase64(method, result, uri, "data:image/png") 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?) = fun audioSuccess(method: String, result: Result<MediaResult>, uri: Uri?) =
fileToBase64(method, result, uri, "data:audio/wav") 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?) = fun videoSuccess(method: String, result: Result<MediaResult>, uri: Uri?) =
fileToBase64(method, result, uri, "data:video/mp4") 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) { fun <T> success(method: String, result: Result<T>, data: T? = null) {
val json = gson.toJson(result.apply { success(data) }) val json = gson.toJson(result.apply { success(data) })
webView.evaluateJavascript("javascript:__maxrockyWebViewJavascriptBridgeCallBack__('$method', '$json')", null) webView.evaluateJavascript("javascript:__maxrockyWebViewJavascriptBridgeCallBack__('$method', '$json')", null)
...@@ -76,4 +85,37 @@ class WebViewProxy( ...@@ -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 package com.maxrocky.nativeview.contract
import android.Manifest import android.Manifest
import android.net.Uri
import android.util.Log import android.util.Log
import android.widget.Toast import android.widget.Toast
import androidx.activity.result.ActivityResultLauncher import androidx.activity.result.ActivityResultLauncher
import androidx.activity.result.contract.ActivityResultContracts import androidx.activity.result.contract.ActivityResultContracts
import androidx.fragment.app.Fragment 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.R
import com.maxrocky.nativeview.WebViewProxy import com.maxrocky.nativeview.WebViewProxy
import com.maxrocky.nativeview.model.MediaResult import com.maxrocky.nativeview.model.MediaResult
import com.maxrocky.nativeview.model.Result import com.maxrocky.nativeview.model.Result
import com.maxrocky.nativeview.utils.GlideEngine
import com.maxrocky.nativeview.utils.canMediaGetAbsolutePath import com.maxrocky.nativeview.utils.canMediaGetAbsolutePath
import com.maxrocky.nativeview.utils.mediaAbsolutePath
class SelectPhotoContract(fragment: Fragment, proxy: WebViewProxy) : IContract(fragment, proxy) { class SelectPhotoContract(fragment: Fragment, proxy: WebViewProxy) : IContract(fragment, proxy) {
companion object { companion object {
private const val TAG = "SelectPhotoContract" private const val TAG = "SelectPhotoContract"
private const val METHOD = "selectPhoto" 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 requestPermission: ActivityResultLauncher<String> =
fragment.registerForActivityResult(ActivityResultContracts.RequestPermission()) { permission ->
private var selectPhoto: ActivityResultLauncher<String> =
fragment.registerForActivityResult(ActivityResultContracts.GetContent()) { uri ->
if (uri == null) {
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@registerForActivityResult
}
Log.d(TAG, "select photo: $uri")
val absolutePath: String?
if (canMediaGetAbsolutePath()) {
absolutePath = uri.mediaAbsolutePath(contentResolver)
Log.d(TAG, "absolute path: $absolutePath")
}
proxy.imgSuccess(METHOD, result, uri)
result.reset()
}
init {
requestPermission = fragment.registerForActivityResult(ActivityResultContracts.RequestPermission()) { permission ->
if (!permission) { if (!permission) {
val errorMsg = fragment.getString(R.string.read_storage_denied) val errorMsg = fragment.getString(R.string.read_storage_denied)
Toast.makeText(context, errorMsg, Toast.LENGTH_LONG).show() Toast.makeText(context, errorMsg, Toast.LENGTH_LONG).show()
...@@ -50,11 +37,42 @@ class SelectPhotoContract(fragment: Fragment, proxy: WebViewProxy) : IContract(f ...@@ -50,11 +37,42 @@ class SelectPhotoContract(fragment: Fragment, proxy: WebViewProxy) : IContract(f
result.reset() result.reset()
return@registerForActivityResult return@registerForActivityResult
} }
selectPhoto.launch("image/*") 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 = list.mapNotNull { it?.originalPath }
Log.d(TAG, "absolute path: ${absolutePath.size}")
}
proxy.imgsSuccess(METHOD, result, list.mapNotNull { Uri.parse(it?.path) })
result.reset()
}
override fun onCancel() {
val errorMsg = fragment.getString(R.string.select_photo_failed)
proxy.failed(METHOD, result, errorMsg)
result.reset()
}
})
} }
}
fun selectPhoto(sessionId: Int) { fun selectPhoto(sessionId: Int, selectNum: Int) {
maxSelectNum = selectNum
result.setupSessionId(sessionId) result.setupSessionId(sessionId)
requestPermission.launch(Manifest.permission.READ_EXTERNAL_STORAGE) requestPermission.launch(Manifest.permission.READ_EXTERNAL_STORAGE)
} }
......
...@@ -18,9 +18,11 @@ import com.maxrocky.nativeview.R ...@@ -18,9 +18,11 @@ import com.maxrocky.nativeview.R
import com.maxrocky.nativeview.WebViewProxy import com.maxrocky.nativeview.WebViewProxy
import com.maxrocky.nativeview.contract.* import com.maxrocky.nativeview.contract.*
import com.maxrocky.nativeview.model.StorageOptions import com.maxrocky.nativeview.model.StorageOptions
import com.maxrocky.nativeview.model.TakePhotoOptions
import kotlinx.coroutines.Dispatchers import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.launch import kotlinx.coroutines.launch
class MainFragment : Fragment() { class MainFragment : Fragment() {
companion object { companion object {
private const val TAG = "MainFragment" private const val TAG = "MainFragment"
...@@ -82,19 +84,6 @@ class MainFragment : Fragment() { ...@@ -82,19 +84,6 @@ class MainFragment : Fragment() {
} }
} }
wv.webViewClient = object : WebViewClient() { 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( override fun onReceivedSslError(
view: WebView?, view: WebView?,
handler: SslErrorHandler?, handler: SslErrorHandler?,
...@@ -152,7 +141,14 @@ class MainFragment : Fragment() { ...@@ -152,7 +141,14 @@ class MainFragment : Fragment() {
fun execute(sessionId: Int, method: String, args: String) { fun execute(sessionId: Int, method: String, args: String) {
when (method) { when (method) {
"selectFile" -> selectFileContract.selectFile(sessionId, args) "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) "openCamera" -> cameraContract.openCamera(sessionId)
"recordAudio" -> "recordAudio" ->
recordAudioContract.recordAudio(sessionId, args.toIntOrNull()) recordAudioContract.recordAudio(sessionId, args.toIntOrNull())
...@@ -161,10 +157,7 @@ class MainFragment : Fragment() { ...@@ -161,10 +157,7 @@ class MainFragment : Fragment() {
"captureVideo" -> videoContract.captureVideo(sessionId) "captureVideo" -> videoContract.captureVideo(sessionId)
"qrcode" -> qrCodeContract.qrcode(sessionId) "qrcode" -> qrCodeContract.qrcode(sessionId)
"storageValue" -> { "storageValue" -> {
val options = gson.fromJson<StorageOptions<String, String>>( val options = gson.fromJson(args, StorageOptions::class.java)
args,
StorageOptions::class.java
)
storageContract.storageValue(sessionId, options.key, options.value) storageContract.storageValue(sessionId, options.key, options.value)
} }
"acquireValue" -> storageContract.acquireValue(sessionId, args) "acquireValue" -> storageContract.acquireValue(sessionId, args)
......
package com.maxrocky.nativeview.model 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