Commit 3eaf2c42 by 王佳洋

课程顾问-保存图片到相册加入权限适配,暂时关闭android10版本适配,因为应用还未适配到10

音频播放bug修复
parent 75cc4bd7
...@@ -20,8 +20,8 @@ import java.util.List; ...@@ -20,8 +20,8 @@ import java.util.List;
*/ */
public final class DemoGlobalConfig implements IConfigModule { public final class DemoGlobalConfig implements IConfigModule {
String APP_DOMAIN = "https://api.github.com/"; String APP_DOMAIN = "https://api.github.com/";
// public static String appEnv = YDLConstants.ENV_TEST; public static String appEnv = YDLConstants.ENV_TEST;
public static String appEnv = YDLConstants.ENV_TEST; // public static String appEnv = YDLConstants.ENV_PROD;
// public static String appEnv = YDLConstants.ENV_NEW_TEST;//配置未上传到maven库 // public static String appEnv = YDLConstants.ENV_NEW_TEST;//配置未上传到maven库
@Override @Override
......
...@@ -14,6 +14,7 @@ import com.ydl.webview.H5JsBean; ...@@ -14,6 +14,7 @@ import com.ydl.webview.H5JsBean;
import com.ydl.webview.H5Params; import com.ydl.webview.H5Params;
import com.ydl.webview.NewH5Activity; import com.ydl.webview.NewH5Activity;
import com.ydl.ydlcommon.modular.ModularServiceManager; import com.ydl.ydlcommon.modular.ModularServiceManager;
import com.ydl.ydlcommon.utils.ImageUtil;
import com.yidianling.common.tools.LogUtil; import com.yidianling.common.tools.LogUtil;
import com.yidianling.common.tools.ToastUtil; import com.yidianling.common.tools.ToastUtil;
import com.yidianling.consultant.preview.GPreviewBuilder; import com.yidianling.consultant.preview.GPreviewBuilder;
...@@ -378,7 +379,7 @@ public class WVClickAbstractListener implements WebViewClientClickListener { ...@@ -378,7 +379,7 @@ public class WVClickAbstractListener implements WebViewClientClickListener {
@Override @Override
public void saveImage(H5JsBean.H5JsCmd.Params jsData) { public void saveImage(H5JsBean.H5JsCmd.Params jsData) {
ImageUtil.Companion.savePicture(mContext, jsData.getImageBase64());
} }
@Override @Override
......
ext { ext {
dev_mode = true//组件发布的时候需要设置为false dev_mode = true//组件发布的时候需要设置为false
ydl2PublishVersion = "0.2.0.17" ydl2PublishVersion = "0.2.0.20-wjy"
ydlPublishVersion = [ ydlPublishVersion = [
// -------------- 业务模块 -------------- // -------------- 业务模块 --------------
//第三步 若干 //第三步 若干
......
...@@ -5,12 +5,12 @@ import android.app.Activity ...@@ -5,12 +5,12 @@ import android.app.Activity
import android.content.Intent import android.content.Intent
import android.graphics.Bitmap import android.graphics.Bitmap
import android.graphics.Canvas import android.graphics.Canvas
import androidx.viewpager.widget.PagerAdapter
import androidx.viewpager.widget.ViewPager
import android.text.TextUtils import android.text.TextUtils
import android.view.View import android.view.View
import android.view.ViewGroup import android.view.ViewGroup
import android.view.WindowManager import android.view.WindowManager
import androidx.viewpager.widget.PagerAdapter
import androidx.viewpager.widget.ViewPager
import com.alibaba.android.arouter.facade.annotation.Route import com.alibaba.android.arouter.facade.annotation.Route
import com.ydl.ydlcommon.actions.share.ShareUtils import com.ydl.ydlcommon.actions.share.ShareUtils
import com.ydl.ydlcommon.base.BaseActivity import com.ydl.ydlcommon.base.BaseActivity
......
...@@ -77,7 +77,7 @@ class AudioPlayPresenter : BasePresenter<IAudioPlayContract.View, IAudioPlayCont ...@@ -77,7 +77,7 @@ class AudioPlayPresenter : BasePresenter<IAudioPlayContract.View, IAudioPlayCont
override fun getCoursePlayData() { override fun getCoursePlayData() {
mView.showProgressDialog() mView.showProgressDialog()
mModel.getCoursePlayData(mCourseId) mModel.getCoursePlayData(mCourseId)
.compose(RxUtils.applySchedulers()) .compose(RxUtils.applySchedulers(mView))
.subscribe(object : CommonObserver<BaseResponse<CourseMediaDetailBean>>() { .subscribe(object : CommonObserver<BaseResponse<CourseMediaDetailBean>>() {
override fun onError(s: String?) { override fun onError(s: String?) {
mView.dismissProgressDialog() mView.dismissProgressDialog()
...@@ -91,10 +91,8 @@ class AudioPlayPresenter : BasePresenter<IAudioPlayContract.View, IAudioPlayCont ...@@ -91,10 +91,8 @@ class AudioPlayPresenter : BasePresenter<IAudioPlayContract.View, IAudioPlayCont
mCourPlayBean = bean mCourPlayBean = bean
setPlayList(bean) setPlayList(bean)
if (!mPlayUrl.isNullOrEmpty()) { if (!mPlayUrl.isNullOrEmpty()) {
val regex = Regex("(?<=video).*?(?=&token)")
val playUrl = regex.find(mPlayUrl!!)?.value
val position = mPlayList.indexOfLast { val position = mPlayList.indexOfLast {
playUrl == regex.find(it.url)?.value mPlayUrl == it.url
} }
mCurrentPosition = if (-1 == position) 0 else position mCurrentPosition = if (-1 == position) 0 else position
} }
...@@ -113,7 +111,7 @@ class AudioPlayPresenter : BasePresenter<IAudioPlayContract.View, IAudioPlayCont ...@@ -113,7 +111,7 @@ class AudioPlayPresenter : BasePresenter<IAudioPlayContract.View, IAudioPlayCont
override fun getConsultantInfo() { override fun getConsultantInfo() {
mModel.getConsultantInfo(mCourseId) mModel.getConsultantInfo(mCourseId)
.compose(RxUtils.applySchedulers()) .compose(RxUtils.applySchedulers(mView))
.subscribe(object : CommonObserver<BaseResponse<CourseConsultant>>() { .subscribe(object : CommonObserver<BaseResponse<CourseConsultant>>() {
override fun onError(s: String?) {} override fun onError(s: String?) {}
...@@ -200,7 +198,7 @@ class AudioPlayPresenter : BasePresenter<IAudioPlayContract.View, IAudioPlayCont ...@@ -200,7 +198,7 @@ class AudioPlayPresenter : BasePresenter<IAudioPlayContract.View, IAudioPlayCont
if (!bean.isDemo && !extra.isBuy) { if (!bean.isDemo && !extra.isBuy) {
if (mView.audioIsPlaying()) mView.audioPausePlay() if (mView.audioIsPlaying()) mView.audioPausePlay()
mView.buyCourseTipDialog() mView.buyCourseTipDialog()
} else if (bean.mediaType == COURSE_AUDIO) { } else {
mCurrentPosition = playPosition mCurrentPosition = playPosition
mView.setTitle(bean.title) mView.setTitle(bean.title)
return true return true
......
@file:JvmName("ImageExt")
@file:Suppress("unused")
import android.content.*
import android.graphics.Bitmap
import android.net.Uri
import android.os.Build
import android.os.Environment
import android.provider.MediaStore
import android.util.Log
import com.ydl.ydlcommon.utils.extend.ifNotNull
import java.io.File
import java.io.FileNotFoundException
import java.io.InputStream
import java.io.OutputStream
import java.util.*
private const val TAG = "ImageExt"
private val ALBUM_DIR = Environment.DIRECTORY_PICTURES
private class OutputFileTaker(var file: File? = null)
/**
* 复制图片文件到相册的Pictures文件夹
*
* @param context 上下文
* @param fileName 文件名。 需要携带后缀
* @param relativePath 相对于Pictures的路径
*/
fun File.copyToAlbum(context: Context, fileName: String, relativePath: String?): Uri? {
if (!this.canRead() || !this.exists()) {
Log.w(TAG, "check: read file error: $this")
return null
}
return this.inputStream().use {
it.saveToAlbum(context, fileName, relativePath)
}
}
/**
* 保存图片Stream到相册的Pictures文件夹
*
* @param context 上下文
* @param fileName 文件名。 需要携带后缀
* @param relativePath 相对于Pictures的路径
*/
fun InputStream.saveToAlbum(context: Context, fileName: String, relativePath: String?): Uri? {
val resolver = context.contentResolver
val outputFile = OutputFileTaker()
val imageUri = resolver.insertMediaImage(fileName, relativePath, outputFile)
if (imageUri == null) {
Log.w(TAG, "insert: error: uri == null")
return null
}
(imageUri.outputStream(resolver) ?: return null).use { output ->
this.use { input ->
input.copyTo(output)
imageUri.finishPending(context, resolver, outputFile.file)
}
}
return imageUri
}
/**
* 保存Bitmap到相册的Pictures文件夹
*
* https://developer.android.google.cn/training/data-storage/shared/media
*
* @param context 上下文
* @param fileName 文件名。 需要携带后缀
* @param relativePath 相对于Pictures的路径
* @param quality 质量
*/
fun Bitmap.saveToAlbum(
context: Context,
fileName: String,
relativePath: String? = null,
quality: Int = 100
): Uri? {
// 插入图片信息
val resolver = context.contentResolver
val outputFile = OutputFileTaker()
val imageUri = resolver.insertMediaImage(fileName, relativePath, outputFile)
if (imageUri == null) {
Log.w(TAG, "insert: error: uri == null")
return null
}
// 保存图片
(imageUri.outputStream(resolver) ?: return null).use {
val format = fileName.getBitmapFormat()
this@saveToAlbum.compress(format, quality, it)
imageUri.finishPending(context, resolver, outputFile.file)
}
return imageUri
}
private fun Uri.outputStream(resolver: ContentResolver): OutputStream? {
return try {
resolver.openOutputStream(this)
} catch (e: FileNotFoundException) {
Log.e(TAG, "save: open stream error: $e")
null
}
}
private fun Uri.finishPending(
context: Context,
resolver: ContentResolver,
outputFile: File?
) {
val imageValues = ContentValues()
// if (Build.VERSION.SDK_INT < Build.VERSION_CODES.Q) {
if (outputFile != null) {
imageValues.put(MediaStore.Images.Media.SIZE, outputFile.length())
}
resolver.update(this, imageValues, null, null)
// 通知媒体库更新
val intent = Intent(@Suppress("DEPRECATION") Intent.ACTION_MEDIA_SCANNER_SCAN_FILE, this)
context.sendBroadcast(intent)
// } else {
// Android Q添加了IS_PENDING状态,为0时其他应用才可见
// imageValues.put(MediaStore.Images.Media.IS_PENDING, 0)
// resolver.update(this, imageValues, null, null)
// }
}
private fun String.getBitmapFormat(): Bitmap.CompressFormat {
val fileName = this.toLowerCase(Locale.getDefault())
return when {
fileName.endsWith(".png") -> Bitmap.CompressFormat.PNG
fileName.endsWith(".jpg") || fileName.endsWith(".jpeg") -> Bitmap.CompressFormat.JPEG
fileName.endsWith(".webp") ->
// if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.R)
// Bitmap.CompressFormat.WEBP_LOSSLESS
// else
Bitmap.CompressFormat.WEBP
else -> Bitmap.CompressFormat.PNG
}
}
private fun String.getMimeType(): String? {
val fileName = this.toLowerCase(Locale.getDefault())
return when {
fileName.endsWith(".png") -> "image/png"
fileName.endsWith(".jpg") || fileName.endsWith(".jpeg") -> "image/jpeg"
fileName.endsWith(".webp") -> "image/webp"
fileName.endsWith(".gif") -> "image/gif"
else -> null
}
}
/**
* 插入图片到媒体库
*/
private fun ContentResolver.insertMediaImage(
fileName: String,
relativePath: String?,
outputFileTaker: OutputFileTaker? = null
): Uri? {
// 图片信息
val imageValues = ContentValues().apply {
val mimeType = fileName.getMimeType()
if (mimeType != null) {
put(MediaStore.Images.Media.MIME_TYPE, mimeType)
}
val date = System.currentTimeMillis() / 1000
put(MediaStore.Images.Media.DATE_ADDED, date)
put(MediaStore.Images.Media.DATE_MODIFIED, date)
}
// 保存的位置
val collection: Uri
// if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.Q) {
// val path = if (relativePath != null) "$ALBUM_DIR/${relativePath}" else ALBUM_DIR
// imageValues.apply {
// put(MediaStore.Images.Media.DISPLAY_NAME, fileName)
// put(MediaStore.Images.Media.RELATIVE_PATH, path)
// put(MediaStore.Images.Media.IS_PENDING, 1)
// }
// collection = MediaStore.Images.Media.getContentUri(MediaStore.VOLUME_EXTERNAL_PRIMARY)
// // 高版本不用查重直接插入,会自动重命名
// } else {
// 老版本
val pictures =
@Suppress("DEPRECATION") Environment.getExternalStoragePublicDirectory(ALBUM_DIR)
val saveDir = if (relativePath != null) File(pictures, relativePath) else pictures
if (!saveDir.exists() && !saveDir.mkdirs()) {
Log.e(TAG, "save: error: can't create Pictures directory")
return null
}
// 文件路径查重,重复的话在文件名后拼接数字
var imageFile = File(saveDir, fileName)
val fileNameWithoutExtension = imageFile.nameWithoutExtension
val fileExtension = imageFile.extension
var queryUri = this.queryMediaImage28(imageFile.absolutePath)
var suffix = 1
while (queryUri != null) {
val newName = fileNameWithoutExtension + "(${suffix++})." + fileExtension
imageFile = File(saveDir, newName)
queryUri = this.queryMediaImage28(imageFile.absolutePath)
}
imageValues.apply {
put(MediaStore.Images.Media.DISPLAY_NAME, imageFile.name)
// 保存路径
val imagePath = imageFile.absolutePath
Log.v(TAG, "save file: $imagePath")
put(@Suppress("DEPRECATION") MediaStore.Images.Media.DATA, imagePath)
}
outputFileTaker?.file = imageFile// 回传文件路径,用于设置文件大小
collection = MediaStore.Images.Media.EXTERNAL_CONTENT_URI
// }
// 插入图片信息
return this.insert(collection, imageValues)
}
/**
* Android Q以下版本,查询媒体库中当前路径是否存在
* @return Uri 返回null时说明不存在,可以进行图片插入逻辑
*/
private fun ContentResolver.queryMediaImage28(imagePath: String): Uri? {
// if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.Q) return null
val imageFile = File(imagePath)
if (imageFile.canRead() && imageFile.exists()) {
Log.v(TAG, "query: path: $imagePath exists")
// 文件已存在,返回一个file://xxx的uri
return Uri.fromFile(imageFile)
}
// 保存的位置
val collection = MediaStore.Images.Media.EXTERNAL_CONTENT_URI
// 查询是否已经存在相同图片
val query = this.query(
collection,
arrayOf(MediaStore.Images.Media._ID, @Suppress("DEPRECATION") MediaStore.Images.Media.DATA),
"${@Suppress("DEPRECATION") MediaStore.Images.Media.DATA} == ?",
arrayOf(imagePath), null
)
query?.use {
while (it.moveToNext()) {
val idColumn = it.getColumnIndexOrThrow(MediaStore.Images.Media._ID)
val id = it.getLong(idColumn)
val existsUri = ContentUris.withAppendedId(collection, id)
Log.v(TAG, "query: path: $imagePath exists uri: $existsUri")
return existsUri
}
}
return null
}
package com.ydl.ydlcommon.utils;
import android.content.Context;
import android.content.Intent;
import android.graphics.Bitmap;
import android.net.Uri;
import android.os.Environment;
import android.util.Base64;
import com.yidianling.common.tools.ToastUtil;
import java.io.*;
public class ImageUtil {
public static boolean savePicture(Context context, String base64DataStr) {
// 去掉base64中的前缀
// String base64Str = base64DataStr.substring(base64DataStr.indexOf(",")+1, base64DataStr.length());
File appDir = new File(Environment.getExternalStorageDirectory(), "Camera");// 图片保存的文件夹的名称
if (!appDir.exists()) {
appDir.mkdir();
}
String imgName = System.currentTimeMillis() + ".png";
File fileTest = new File(appDir, imgName);
byte[] data = Base64.decode(base64DataStr, Base64.DEFAULT);
for (int i = 0; i < data.length; i++) {
if (data[i] < 0) {
data[i] += 256;//调整异常数据
}
}
OutputStream os = null;
try {
os = new FileOutputStream(fileTest);
os.write(data);
os.flush();
os.close();
// 通知系统刷新图库
updateAlbum(context, fileTest);
ToastUtil.toastShort("图片已保存至相册");
return true;
} catch (FileNotFoundException e) {
e.printStackTrace();
ToastUtil.toastShort("保存失败");
return false;
} catch (IOException e) {
e.printStackTrace();
ToastUtil.toastShort("保存失败");
return false;
}
}
/**
* 通知图库更新数据
* context
* fileName
* file
*/
private static void updateAlbum(Context context, File file) {
// 最后通知图库更新
context.sendBroadcast(new Intent(Intent.ACTION_MEDIA_SCANNER_SCAN_FILE, Uri.fromFile(file)));
}
public static boolean savePicture(Context context, Bitmap bitmap) {
if (bitmap == null) {
return false;
}
// 去掉base64中的前缀
// String base64Str = base64DataStr.substring(base64DataStr.indexOf(",")+1, base64DataStr.length());
File appDir = new File(Environment.getExternalStorageDirectory(), "Camera");// 图片保存的文件夹的名称
if (!appDir.exists()) {
appDir.mkdir();
}
String imgName = System.currentTimeMillis() + ".png";
File fileTest = new File(appDir, imgName);
ByteArrayOutputStream byteArrayOutputStream = new ByteArrayOutputStream();
bitmap.compress(Bitmap.CompressFormat.PNG, 100, byteArrayOutputStream);
byte[] data = byteArrayOutputStream.toByteArray();
for (int i = 0; i < data.length; i++) {
if (data[i] < 0) {
data[i] += 256;//调整异常数据
}
}
OutputStream os = null;
try {
os = new FileOutputStream(fileTest);
os.write(data);
os.flush();
os.close();
// 通知系统刷新图库
updateAlbum(context, fileTest);
ToastUtil.toastShort("图片已保存至相册");
return true;
} catch (FileNotFoundException e) {
e.printStackTrace();
ToastUtil.toastShort("保存失败");
return false;
} catch (IOException e) {
e.printStackTrace();
ToastUtil.toastShort("保存失败");
return false;
}
}
}
package com.ydl.ydlcommon.utils
import android.content.Context
import android.graphics.Bitmap
import android.graphics.BitmapFactory
import android.util.Base64
import com.hjq.permissions.Permission
import com.hjq.permissions.XXPermissions
import com.yidianling.common.tools.ToastUtil
import io.reactivex.Observable
import io.reactivex.disposables.Disposable
import io.reactivex.schedulers.Schedulers
import saveToAlbum
class ImageUtil {
companion object {
private fun saveToAlbum(context: Context, bitmap: Bitmap?): Disposable {
return Observable.create<Boolean> {
bitmap?.let { pic ->
pic.saveToAlbum(
context,
"${System.currentTimeMillis()}.png"
)
it.onNext(true)
} ?: let { _ ->
it.onNext(false)
}
}.subscribeOn(Schedulers.io())
.subscribe {
if (it) {
ToastUtil.toastShort("图片已保存至相册")
}
else ToastUtil.toastShort("保存失败")
}
}
private fun convertBase64ToBitmap(base64: String): Bitmap? {
val decode = Base64.decode(base64, Base64.DEFAULT)
return BitmapFactory.decodeByteArray(decode, 0, decode.size)
}
/**
* base64字符串
* 保存图片到相册
*/
fun savePicture(context: Context, base64: String?): Disposable? {
var disposable: Disposable? = null
base64?.run {
// if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.Q) {
// disposable = saveToAlbum(context, convertBase64ToPic(this))
// } else {
val permissions =
arrayOf(Permission.READ_EXTERNAL_STORAGE, Permission.WRITE_EXTERNAL_STORAGE)
if (XXPermissions.isGrantedPermission(context, permissions)) {
disposable = saveToAlbum(context, convertBase64ToBitmap(this))
} else {
XXPermissions.with(context)
.permission(permissions)
.request { _, _ ->
disposable = saveToAlbum(context, convertBase64ToBitmap(this))
}
}
// }
}
return disposable
}
/**
* bitmap
* 保存图片到相册
*/
fun savePicture(context: Context, bitmap: Bitmap?): Disposable? {
var disposable: Disposable? = null
// if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.Q) {
// disposable = saveToAlbum(context, bitmap)
// } else {
val permissions =
arrayOf(Permission.READ_EXTERNAL_STORAGE, Permission.WRITE_EXTERNAL_STORAGE)
if (XXPermissions.isGrantedPermission(context, permissions)) {
disposable = saveToAlbum(context, bitmap)
} else {
XXPermissions.with(context)
.permission(permissions)
.request { _, _ ->
disposable = saveToAlbum(context, bitmap)
}
}
// }
return disposable
}
}
}
\ No newline at end of file
...@@ -292,7 +292,9 @@ class YDLShareDialog : DialogFragment { ...@@ -292,7 +292,9 @@ class YDLShareDialog : DialogFragment {
} }
tv_save_pic.setOnClickListener { tv_save_pic.setOnClickListener {
ImageUtil.savePicture(mActivity, base64DataStr) mActivity?.let {
ImageUtil.savePicture(it, base64DataStr)
}
} }
} }
......
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 to comment