Commit 3eaf2c42 by 王佳洋

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

音频播放bug修复
parent 75cc4bd7
......@@ -20,8 +20,8 @@ import java.util.List;
*/
public final class DemoGlobalConfig implements IConfigModule {
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库
@Override
......
......@@ -14,6 +14,7 @@ import com.ydl.webview.H5JsBean;
import com.ydl.webview.H5Params;
import com.ydl.webview.NewH5Activity;
import com.ydl.ydlcommon.modular.ModularServiceManager;
import com.ydl.ydlcommon.utils.ImageUtil;
import com.yidianling.common.tools.LogUtil;
import com.yidianling.common.tools.ToastUtil;
import com.yidianling.consultant.preview.GPreviewBuilder;
......@@ -378,7 +379,7 @@ public class WVClickAbstractListener implements WebViewClientClickListener {
@Override
public void saveImage(H5JsBean.H5JsCmd.Params jsData) {
ImageUtil.Companion.savePicture(mContext, jsData.getImageBase64());
}
@Override
......
ext {
dev_mode = true//组件发布的时候需要设置为false
ydl2PublishVersion = "0.2.0.17"
ydl2PublishVersion = "0.2.0.20-wjy"
ydlPublishVersion = [
// -------------- 业务模块 --------------
//第三步 若干
......
......@@ -5,12 +5,12 @@ import android.app.Activity
import android.content.Intent
import android.graphics.Bitmap
import android.graphics.Canvas
import androidx.viewpager.widget.PagerAdapter
import androidx.viewpager.widget.ViewPager
import android.text.TextUtils
import android.view.View
import android.view.ViewGroup
import android.view.WindowManager
import androidx.viewpager.widget.PagerAdapter
import androidx.viewpager.widget.ViewPager
import com.alibaba.android.arouter.facade.annotation.Route
import com.ydl.ydlcommon.actions.share.ShareUtils
import com.ydl.ydlcommon.base.BaseActivity
......
......@@ -77,7 +77,7 @@ class AudioPlayPresenter : BasePresenter<IAudioPlayContract.View, IAudioPlayCont
override fun getCoursePlayData() {
mView.showProgressDialog()
mModel.getCoursePlayData(mCourseId)
.compose(RxUtils.applySchedulers())
.compose(RxUtils.applySchedulers(mView))
.subscribe(object : CommonObserver<BaseResponse<CourseMediaDetailBean>>() {
override fun onError(s: String?) {
mView.dismissProgressDialog()
......@@ -91,10 +91,8 @@ class AudioPlayPresenter : BasePresenter<IAudioPlayContract.View, IAudioPlayCont
mCourPlayBean = bean
setPlayList(bean)
if (!mPlayUrl.isNullOrEmpty()) {
val regex = Regex("(?<=video).*?(?=&token)")
val playUrl = regex.find(mPlayUrl!!)?.value
val position = mPlayList.indexOfLast {
playUrl == regex.find(it.url)?.value
mPlayUrl == it.url
}
mCurrentPosition = if (-1 == position) 0 else position
}
......@@ -113,7 +111,7 @@ class AudioPlayPresenter : BasePresenter<IAudioPlayContract.View, IAudioPlayCont
override fun getConsultantInfo() {
mModel.getConsultantInfo(mCourseId)
.compose(RxUtils.applySchedulers())
.compose(RxUtils.applySchedulers(mView))
.subscribe(object : CommonObserver<BaseResponse<CourseConsultant>>() {
override fun onError(s: String?) {}
......@@ -200,7 +198,7 @@ class AudioPlayPresenter : BasePresenter<IAudioPlayContract.View, IAudioPlayCont
if (!bean.isDemo && !extra.isBuy) {
if (mView.audioIsPlaying()) mView.audioPausePlay()
mView.buyCourseTipDialog()
} else if (bean.mediaType == COURSE_AUDIO) {
} else {
mCurrentPosition = playPosition
mView.setTitle(bean.title)
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 {
}
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