From 90cebdb90dd1dfc28acc4a657dc47d029f4db6a8 Mon Sep 17 00:00:00 2001 From: HuanCheng65 <22636177+HuanCheng65@users.noreply.github.com> Date: Sun, 19 Feb 2023 15:37:10 +0800 Subject: [PATCH] =?UTF-8?q?pref:=20=E4=BC=98=E5=8C=96=E5=9B=BE=E7=89=87?= =?UTF-8?q?=E4=BF=9D=E5=AD=98?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../post/activities/PhotoViewActivity.kt | 5 +- .../ui/page/photoview/PhotoViewActivity.kt | 1 + .../tieba/post/utils/ImageUtil.java | 677 ----------------- .../tieba/post/utils/ImageUtil.kt | 701 ++++++++++++++++++ 4 files changed, 705 insertions(+), 679 deletions(-) delete mode 100644 app/src/main/java/com/huanchengfly/tieba/post/utils/ImageUtil.java create mode 100644 app/src/main/java/com/huanchengfly/tieba/post/utils/ImageUtil.kt diff --git a/app/src/main/java/com/huanchengfly/tieba/post/activities/PhotoViewActivity.kt b/app/src/main/java/com/huanchengfly/tieba/post/activities/PhotoViewActivity.kt index 3759e7d2..02dbb3fc 100644 --- a/app/src/main/java/com/huanchengfly/tieba/post/activities/PhotoViewActivity.kt +++ b/app/src/main/java/com/huanchengfly/tieba/post/activities/PhotoViewActivity.kt @@ -30,6 +30,7 @@ import com.huanchengfly.tieba.post.models.PhotoViewBean import com.huanchengfly.tieba.post.ui.common.theme.utils.ThemeUtils import com.huanchengfly.tieba.post.utils.AnimUtil import com.huanchengfly.tieba.post.utils.ImageUtil +import com.huanchengfly.tieba.post.utils.download import retrofit2.Call import retrofit2.Callback import retrofit2.Response @@ -95,7 +96,7 @@ class PhotoViewActivity : BaseActivity(), OnChangeBottomBarVisibilityListener, } picBeans.addAll(data.picList) picBeans.forEach { - it.img.original?.let { it1 -> imgInfoBeans.add(it1) } + it.img.original.let { it1 -> imgInfoBeans.add(it1) } } lastIndex = picBeans.first().overAllIndex.toInt() for (photoViewBean in photoViewBeans) { @@ -277,7 +278,7 @@ class PhotoViewActivity : BaseActivity(), OnChangeBottomBarVisibilityListener, this, mAdapter.getBean(mViewPager.currentItem).originUrl, true - ) { uri: Uri? -> + ) { uri: Uri -> val intent = Intent(Intent.ACTION_SEND) if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.N) { intent.putExtra(Intent.EXTRA_STREAM, uri) diff --git a/app/src/main/java/com/huanchengfly/tieba/post/ui/page/photoview/PhotoViewActivity.kt b/app/src/main/java/com/huanchengfly/tieba/post/ui/page/photoview/PhotoViewActivity.kt index 004c3493..32504c34 100644 --- a/app/src/main/java/com/huanchengfly/tieba/post/ui/page/photoview/PhotoViewActivity.kt +++ b/app/src/main/java/com/huanchengfly/tieba/post/ui/page/photoview/PhotoViewActivity.kt @@ -51,6 +51,7 @@ import com.huanchengfly.tieba.post.toastShort import com.huanchengfly.tieba.post.ui.common.theme.compose.ExtendedTheme import com.huanchengfly.tieba.post.ui.widgets.compose.LazyLoad import com.huanchengfly.tieba.post.utils.ImageUtil +import com.huanchengfly.tieba.post.utils.download import kotlinx.coroutines.launch import kotlin.math.abs import kotlin.math.roundToInt diff --git a/app/src/main/java/com/huanchengfly/tieba/post/utils/ImageUtil.java b/app/src/main/java/com/huanchengfly/tieba/post/utils/ImageUtil.java deleted file mode 100644 index e1033674..00000000 --- a/app/src/main/java/com/huanchengfly/tieba/post/utils/ImageUtil.java +++ /dev/null @@ -1,677 +0,0 @@ -package com.huanchengfly.tieba.post.utils; - -import static com.huanchengfly.tieba.post.utils.FileUtil.FILE_FOLDER; -import static com.huanchengfly.tieba.post.utils.FileUtil.changeFileExtension; - -import android.annotation.SuppressLint; -import android.content.ContentResolver; -import android.content.ContentValues; -import android.content.Context; -import android.content.Intent; -import android.graphics.Bitmap; -import android.graphics.Canvas; -import android.graphics.ColorMatrix; -import android.graphics.ColorMatrixColorFilter; -import android.graphics.PixelFormat; -import android.graphics.drawable.Drawable; -import android.graphics.drawable.GradientDrawable; -import android.net.Uri; -import android.os.AsyncTask; -import android.os.Build; -import android.os.Environment; -import android.os.ParcelFileDescriptor; -import android.provider.MediaStore; -import android.text.TextUtils; -import android.util.Base64; -import android.util.Log; -import android.webkit.URLUtil; -import android.widget.ImageView; -import android.widget.Toast; - -import androidx.annotation.IntDef; -import androidx.annotation.NonNull; -import androidx.annotation.Nullable; -import androidx.appcompat.widget.PopupMenu; -import androidx.core.content.FileProvider; - -import com.bumptech.glide.Glide; -import com.bumptech.glide.RequestBuilder; -import com.bumptech.glide.load.resource.drawable.DrawableTransitionOptions; -import com.bumptech.glide.request.FutureTarget; -import com.bumptech.glide.request.RequestOptions; -import com.bumptech.glide.request.target.Target; -import com.huanchengfly.tieba.post.App; -import com.huanchengfly.tieba.post.ExtensionsKt; -import com.huanchengfly.tieba.post.R; -import com.huanchengfly.tieba.post.activities.PhotoViewActivity; -import com.huanchengfly.tieba.post.components.transformations.RadiusTransformation; -import com.huanchengfly.tieba.post.models.PhotoViewBean; -import com.zhihu.matisse.MimeType; - -import java.io.ByteArrayOutputStream; -import java.io.File; -import java.io.FileInputStream; -import java.io.FileNotFoundException; -import java.io.FileOutputStream; -import java.io.IOException; -import java.io.InputStream; -import java.lang.annotation.Retention; -import java.lang.annotation.RetentionPolicy; -import java.lang.ref.WeakReference; -import java.nio.channels.FileChannel; -import java.util.ArrayList; -import java.util.Arrays; -import java.util.Collections; -import java.util.List; - -public class ImageUtil { - /** - * 智能省流 - */ - public static final int SETTINGS_SMART_ORIGIN = 0; - /** - * 智能无图 - */ - public static final int SETTINGS_SMART_LOAD = 1; - /** - * 始终高质量 - */ - public static final int SETTINGS_ALL_ORIGIN = 2; - /** - * 始终无图 - */ - public static final int SETTINGS_ALL_NO = 3; - - public static final int LOAD_TYPE_SMALL_PIC = 0; - public static final int LOAD_TYPE_AVATAR = 1; - public static final int LOAD_TYPE_NO_RADIUS = 2; - public static final int LOAD_TYPE_ALWAYS_ROUND = 3; - public static final String TAG = "ImageUtil"; - - private static boolean isGifFile(File file) { - if (file == null) return false; - try { - return isGifFile(new FileInputStream(file)); - } catch (FileNotFoundException e) { - e.printStackTrace(); - } - return false; - } - - //判断是否为GIF文件 - private static boolean isGifFile(InputStream inputStream) { - if (inputStream == null) return false; - byte[] bytes = new byte[4]; - try { - inputStream.read(bytes); - inputStream.close(); - String str = new String(bytes); - return str.equalsIgnoreCase("GIF8"); - } catch (IOException e) { - e.printStackTrace(); - } - return false; - } - - public static File compressImage(Bitmap bitmap, File output, int maxSize) { - ByteArrayOutputStream baos = new ByteArrayOutputStream(); - int quality = 100; - bitmap.compress(Bitmap.CompressFormat.JPEG, quality, baos);//质量压缩方法,这里100表示不压缩,把压缩后的数据存放到baos中 - while (baos.toByteArray().length / 1024 > maxSize && quality > 0) { //循环判断如果压缩后图片是否大于20kb,大于继续压缩 友盟缩略图要求不大于18kb - baos.reset();//重置baos即清空baos - quality -= 5;//每次都减少5 - bitmap.compress(Bitmap.CompressFormat.JPEG, quality, baos);//这里压缩options%,把压缩后的数据存放到baos中 - } - try { - FileOutputStream fos = new FileOutputStream(output); - try { - fos.write(baos.toByteArray()); - fos.flush(); - fos.close(); - } catch (IOException e) { - e.printStackTrace(); - } - } catch (FileNotFoundException e) { - e.printStackTrace(); - } - return output; - } - - public static File bitmapToFile(Bitmap bitmap, File output) { - return bitmapToFile(bitmap, output, Bitmap.CompressFormat.JPEG); - } - - public static File bitmapToFile(Bitmap bitmap, File output, Bitmap.CompressFormat format) { - ByteArrayOutputStream baos = new ByteArrayOutputStream(); - bitmap.compress(format, 100, baos); - try { - FileOutputStream fos = new FileOutputStream(output); - try { - fos.write(baos.toByteArray()); - fos.flush(); - fos.close(); - } catch (IOException e) { - e.printStackTrace(); - } - } catch (FileNotFoundException e) { - e.printStackTrace(); - } - return output; - } - - public static File compressImage(Bitmap bitmap, File output) { - return compressImage(bitmap, output, 100); - } - - public static Bitmap drawableToBitmap(Drawable drawable) { - Bitmap bitmap = Bitmap.createBitmap(drawable.getIntrinsicWidth(), drawable.getIntrinsicHeight(), - drawable.getOpacity() != PixelFormat.OPAQUE ? Bitmap.Config.ARGB_8888 : Bitmap.Config.RGB_565); - Canvas canvas = new Canvas(bitmap); - drawable.setBounds(0, 0, drawable.getIntrinsicWidth(), drawable.getIntrinsicHeight()); - drawable.draw(canvas); - return bitmap; - } - - public static boolean copyFile(FileInputStream src, FileOutputStream dest) { - boolean result = false; - if ((src == null) || (dest == null)) { - return result; - } - - FileChannel srcChannel = null; - FileChannel dstChannel = null; - - try { - srcChannel = src.getChannel(); - dstChannel = dest.getChannel(); - srcChannel.transferTo(0, srcChannel.size(), dstChannel); - result = true; - } catch (IOException e) { - e.printStackTrace(); - return result; - } - try { - srcChannel.close(); - dstChannel.close(); - } catch (IOException e) { - e.printStackTrace(); - } - return result; - } - - public static boolean copyFile(File src, File dest) { - boolean result = false; - if ((src == null) || (dest == null)) { - return result; - } - if (dest.exists()) { - dest.delete(); - } - try { - dest.createNewFile(); - } catch (IOException e) { - e.printStackTrace(); - } - - FileChannel srcChannel = null; - FileChannel dstChannel = null; - - try { - srcChannel = new FileInputStream(src).getChannel(); - dstChannel = new FileOutputStream(dest).getChannel(); - srcChannel.transferTo(0, srcChannel.size(), dstChannel); - result = true; - } catch (IOException e) { - e.printStackTrace(); - return result; - } - try { - srcChannel.close(); - dstChannel.close(); - } catch (IOException e) { - e.printStackTrace(); - } - return result; - } - - private static void changeBrightness(ImageView imageView, int brightness) { - ColorMatrix cMatrix = new ColorMatrix(); - cMatrix.set(new float[]{1, 0, 0, 0, brightness, 0, 1, 0, 0, brightness, // 改变亮度 - 0, 0, 1, 0, brightness, 0, 0, 0, 1, 0}); - imageView.setColorFilter(new ColorMatrixColorFilter(cMatrix)); - } - - @SuppressLint("StaticFieldLeak") - public static void download(Context context, String url) { - download(context, url, false, null); - } - - private static void downloadForShare(Context context, String url, @NonNull ShareTaskCallback taskCallback) { - new DownloadAsyncTask(context, url, file -> { - File pictureFolder = new File(context.getCacheDir(), ".shareTemp"); - if (pictureFolder.exists() || pictureFolder.mkdirs()) { - String fileName = "share_" + System.currentTimeMillis(); - File destFile = new File(pictureFolder, fileName); - if (!destFile.exists()) { - copyFile(file, destFile); - } - Uri shareUri; - if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.N) { - shareUri = FileProvider.getUriForFile( - context, - context.getPackageName() + ".share.FileProvider", - destFile - ); - } else { - shareUri = Uri.fromFile(destFile); - } - taskCallback.onGetUri(shareUri); - } - }).execute(); - } - - @SuppressLint("StaticFieldLeak") - public static void download(Context context, String url, boolean forShare, @Nullable ShareTaskCallback taskCallback) { - if (forShare) { - if (taskCallback != null) downloadForShare(context, url, taskCallback); - return; - } - if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.Q) { - downloadAboveQ(context, url); - return; - } - PermissionUtils.INSTANCE.askPermission( - context, - new PermissionUtils.PermissionData( - Arrays.asList(PermissionUtils.READ_EXTERNAL_STORAGE, PermissionUtils.WRITE_EXTERNAL_STORAGE), - context.getString(R.string.tip_permission_storage) - ), - R.string.toast_no_permission_save_photo, - () -> { - downloadBelowQ(context, url); - return null; - }); - } - - private static void downloadAboveQ(Context context, String url) { - if (Build.VERSION.SDK_INT < Build.VERSION_CODES.Q) { - return; - } - new DownloadAsyncTask(context, url, file -> { - String mimeType = MimeType.JPEG.toString(); - String fileName = URLUtil.guessFileName(url, null, MimeType.JPEG.toString()); - if (isGifFile(file)) { - fileName = changeFileExtension(fileName, ".gif"); - mimeType = MimeType.GIF.toString(); - } - Log.i(TAG, "download: fileName = " + fileName); - String relativePath = Environment.DIRECTORY_PICTURES + File.separator + FILE_FOLDER; - ContentValues values = new ContentValues(); - values.put(MediaStore.Images.Media.RELATIVE_PATH, relativePath); - values.put(MediaStore.Images.Media.DISPLAY_NAME, fileName); - values.put(MediaStore.Images.Media.MIME_TYPE, mimeType); - values.put(MediaStore.Images.Media.DESCRIPTION, fileName); - Uri uri = null; - ContentResolver cr = context.getContentResolver(); - try { - uri = cr.insert(MediaStore.Images.Media.EXTERNAL_CONTENT_URI, values); - if (uri == null) { - return; - } - ParcelFileDescriptor descriptor = cr.openFileDescriptor(uri, "w"); - FileOutputStream outputStream = new FileOutputStream(descriptor.getFileDescriptor()); - FileInputStream inputStream = new FileInputStream(file); - copyFile(inputStream, outputStream); - } catch (Exception e) { - e.printStackTrace(); - if (uri != null) { - cr.delete(uri, null, null); - } - return; - } - Toast.makeText(context, context.getString(R.string.toast_photo_saved, relativePath), Toast.LENGTH_SHORT).show(); - }).execute(); - } - - private static void downloadBelowQ(Context context, String url) { - new DownloadAsyncTask(context, url, file -> { - File pictureFolder = Environment.getExternalStoragePublicDirectory(Environment.DIRECTORY_PICTURES).getAbsoluteFile(); - File appDir; - appDir = new File(pictureFolder, FILE_FOLDER); - if (appDir.exists() || appDir.mkdirs()) { - String fileName = URLUtil.guessFileName(url, null, MimeType.JPEG.toString()); - if (isGifFile(file)) { - fileName = changeFileExtension(fileName, ".gif"); - } - File destFile = new File(appDir, fileName); - if (destFile.exists()) { - return; - } - copyFile(file, destFile); - checkGifFile(destFile); - context.sendBroadcast(new Intent(Intent.ACTION_MEDIA_SCANNER_SCAN_FILE, Uri.fromFile(new File(destFile.getPath())))); - Toast.makeText(context, context.getString(R.string.toast_photo_saved, destFile.getPath()), Toast.LENGTH_SHORT).show(); - } - }).execute(); - } - - private static void checkGifFile(File file) { - if (isGifFile(file)) { - File gifFile = new File(file.getParentFile(), FileUtil.changeFileExtension(file.getName(), ".gif")); - if (gifFile.exists()) { - file.delete(); - } else { - file.renameTo(gifFile); - } - } - } - - @NonNull - public static String getPicId(String picUrl) { - String fileName = URLUtil.guessFileName(picUrl, null, MimeType.JPEG.toString()); - return fileName.replace(".jpg", ""); - } - - public static void initImageView(@NonNull ImageView view, List photoViewBeans, int position, String forumName, String forumId, String threadId, boolean seeLz, String objType) { - view.setOnClickListener(v -> { - Object tag = view.getTag(R.id.image_load_tag); - if (tag != null) { - boolean loaded = (boolean) tag; - if (loaded) { - PhotoViewActivity.launch(v.getContext(), photoViewBeans.toArray(new PhotoViewBean[0]), position, forumName, forumId, threadId, seeLz, objType); - } else { - load(view, LOAD_TYPE_SMALL_PIC, photoViewBeans.get(position).getUrl(), true); - } - } - }); - view.setOnLongClickListener(v -> { - PopupMenu popupMenu = PopupUtil.create(view); - popupMenu.getMenuInflater().inflate(R.menu.menu_image_long_click, popupMenu.getMenu()); - popupMenu.setOnMenuItemClickListener(item -> { - if (item.getItemId() == R.id.menu_save_image) { - download(view.getContext(), photoViewBeans.get(position).getOriginUrl()); - return true; - } - return false; - }); - popupMenu.show(); - return true; - }); - } - - public static void initImageView(ImageView view, List photoViewBeans, int position) { - view.setOnClickListener(v -> { - Object tag = view.getTag(R.id.image_load_tag); - if (tag != null) { - boolean loaded = (boolean) tag; - if (loaded) { - PhotoViewActivity.launch(v.getContext(), photoViewBeans, position); - } else { - load(view, LOAD_TYPE_SMALL_PIC, photoViewBeans.get(position).getUrl(), true); - } - } - }); - view.setOnLongClickListener(v -> { - PopupMenu popupMenu = PopupUtil.create(view); - popupMenu.getMenuInflater().inflate(R.menu.menu_image_long_click, popupMenu.getMenu()); - popupMenu.setOnMenuItemClickListener(item -> { - if (item.getItemId() == R.id.menu_save_image) { - download(view.getContext(), photoViewBeans.get(position).getOriginUrl()); - return true; - } - return false; - }); - popupMenu.show(); - return true; - }); - } - - public static void initImageView(ImageView view, PhotoViewBean photoViewBean) { - List photoViewBeans = new ArrayList<>(); - photoViewBeans.add(photoViewBean); - initImageView(view, photoViewBeans, 0); - } - - public static String getNonNullString(String... strings) { - for (String url : strings) { - if (!TextUtils.isEmpty(url)) { - return url; - } - } - return null; - } - - public static int getRadiusPx(Context context) { - return DisplayUtil.dp2px(context, getRadiusDp(context)); - } - - public static int getRadiusDp(Context context) { - return AppPreferencesUtilsKt.getAppPreferences(context).getRadius(); - } - - public static void load(ImageView imageView, @LoadType int type, String url) { - load(imageView, type, url, false); - } - - public static void clear(ImageView imageView) { - Glide.with(imageView).clear(imageView); - } - - @NonNull - public static Drawable getPlaceHolder(Context context, int radius) { - GradientDrawable drawable = new GradientDrawable(); - int color = ThemeUtil.isNightMode() ? - context.getResources().getColor(R.color.color_place_holder_night) : - context.getResources().getColor(R.color.color_place_holder); - drawable.setColor(color); - drawable.setCornerRadius(DisplayUtil.dp2px(context, radius)); - return drawable; - } - - @SuppressLint("CheckResult") - public static void load( - ImageView imageView, - @LoadType int type, - String url, - boolean skipNetworkCheck, - boolean noTransition - ) { - if (!Util.canLoadGlide(imageView.getContext())) { - return; - } - int radius = getRadiusDp(imageView.getContext()); - RequestBuilder requestBuilder; - if (skipNetworkCheck || - type == LOAD_TYPE_AVATAR || - getImageLoadSettings() == SETTINGS_ALL_ORIGIN || - getImageLoadSettings() == SETTINGS_SMART_ORIGIN || - (getImageLoadSettings() == SETTINGS_SMART_LOAD && NetworkUtil.isWifiConnected(imageView.getContext()))) { - imageView.setTag(R.id.image_load_tag, true); - requestBuilder = Glide.with(imageView).load(url); - } else { - imageView.setTag(R.id.image_load_tag, false); - requestBuilder = Glide.with(imageView).load(getPlaceHolder(imageView.getContext(), type == LOAD_TYPE_SMALL_PIC ? radius : 0)); - } - if (ThemeUtil.isNightMode()) { - changeBrightness(imageView, -35); - } else { - imageView.clearColorFilter(); - } - switch (type) { - case LOAD_TYPE_SMALL_PIC: - requestBuilder.apply(RequestOptions.bitmapTransform(new RadiusTransformation(radius)) - .placeholder(getPlaceHolder(imageView.getContext(), radius)) - .skipMemoryCache(true)); - break; - case LOAD_TYPE_AVATAR: - requestBuilder.apply(RequestOptions.bitmapTransform(new RadiusTransformation(6)) - .placeholder(getPlaceHolder(imageView.getContext(), 6)) - .skipMemoryCache(true)); - break; - case LOAD_TYPE_NO_RADIUS: - requestBuilder.apply(new RequestOptions() - .placeholder(getPlaceHolder(imageView.getContext(), 0)) - .skipMemoryCache(true)); - break; - case LOAD_TYPE_ALWAYS_ROUND: - requestBuilder.apply(new RequestOptions() - .circleCrop() - .placeholder(getPlaceHolder(imageView.getContext(), ExtensionsKt.dpToPx(100))) - .skipMemoryCache(true)); - break; - } - if (!noTransition) { - requestBuilder.transition(DrawableTransitionOptions.withCrossFade()); - } - requestBuilder.into(imageView); - } - - public static void load( - ImageView imageView, - @LoadType int type, - String url, - boolean skipNetworkCheck - ) { - load(imageView, type, url, skipNetworkCheck, false); - } - - /** - * 获取要加载的图片 Url - * - * @param isSmallPic 加载的是否为缩略图 - * @param originUrl 原图 Url - * @param smallPicUrls 缩略图 Url,按照画质从好到差排序 - * @return 要加载的图片 Url - */ - public static String getUrl(Context context, boolean isSmallPic, @NonNull String originUrl, @NonNull String... smallPicUrls) { - List urls = Arrays.asList(smallPicUrls); - if (isSmallPic) { - if (needReverse(context)) { - Collections.reverse(urls); - } - for (String url : urls) { - if (!TextUtils.isEmpty(url)) { - return url; - } - } - } - return originUrl; - } - - private static boolean needReverse(Context context) { - if (getImageLoadSettings() == SETTINGS_SMART_ORIGIN && NetworkUtil.isWifiConnected(context)) { - return false; - } - return getImageLoadSettings() != SETTINGS_ALL_ORIGIN; - } - - @ImageLoadSettings - private static int getImageLoadSettings() { - return Integer.parseInt(AppPreferencesUtilsKt.getAppPreferences(App.getINSTANCE()).getImageLoadType()); - } - - public static String imageToBase64(InputStream is) { - if (is == null) { - return null; - } - byte[] data = null; - String result = null; - try { - //创建一个字符流大小的数组。 - data = new byte[is.available()]; - //写入数组 - is.read(data); - //用默认的编码格式进行编码 - result = Base64.encodeToString(data, Base64.DEFAULT); - } catch (IOException e) { - e.printStackTrace(); - } finally { - if (null != is) { - try { - is.close(); - } catch (IOException e) { - e.printStackTrace(); - } - } - - } - return result; - } - - public static String imageToBase64(File file) { - if (file == null) { - return null; - } - String result = null; - try { - InputStream is = new FileInputStream(file); - result = imageToBase64(is); - } catch (IOException e) { - e.printStackTrace(); - } - return result; - } - - public interface ShareTaskCallback { - void onGetUri(Uri uri); - } - - @IntDef({LOAD_TYPE_SMALL_PIC, LOAD_TYPE_AVATAR, LOAD_TYPE_NO_RADIUS, LOAD_TYPE_ALWAYS_ROUND}) - @Retention(RetentionPolicy.SOURCE) - public @interface LoadType { - } - - @IntDef({SETTINGS_SMART_ORIGIN, SETTINGS_SMART_LOAD, SETTINGS_ALL_ORIGIN, SETTINGS_ALL_NO}) - public @interface ImageLoadSettings { - } - - public static class DownloadAsyncTask extends AsyncTask { - private final WeakReference contextWeakReference; - private final TaskCallback callback; - private final String url; - - public DownloadAsyncTask(Context context, String url, TaskCallback callback) { - this.contextWeakReference = new WeakReference<>(context); - this.url = url; - this.callback = callback; - } - - public TaskCallback getCallback() { - return callback; - } - - public String getUrl() { - return url; - } - - private Context getContext() { - return contextWeakReference.get(); - } - - @Override - protected File doInBackground(Void... voids) { - File file = null; - try { - FutureTarget future = Glide.with(getContext()) - .asFile() - .load(getUrl()) - .submit(Target.SIZE_ORIGINAL, Target.SIZE_ORIGINAL); - file = future.get(); - return file; - } catch (Exception e) { - e.printStackTrace(); - } - return file; - } - - @Override - protected void onPostExecute(File file) { - super.onPostExecute(file); - getCallback().onPostExecute(file); - } - - public interface TaskCallback { - void onPostExecute(File file); - } - } -} diff --git a/app/src/main/java/com/huanchengfly/tieba/post/utils/ImageUtil.kt b/app/src/main/java/com/huanchengfly/tieba/post/utils/ImageUtil.kt new file mode 100644 index 00000000..8b8c68a9 --- /dev/null +++ b/app/src/main/java/com/huanchengfly/tieba/post/utils/ImageUtil.kt @@ -0,0 +1,701 @@ +package com.huanchengfly.tieba.post.utils + +import android.annotation.SuppressLint +import android.content.ContentValues +import android.content.Context +import android.content.Intent +import android.graphics.Bitmap +import android.graphics.Bitmap.CompressFormat +import android.graphics.Canvas +import android.graphics.ColorMatrix +import android.graphics.ColorMatrixColorFilter +import android.graphics.PixelFormat +import android.graphics.drawable.Drawable +import android.graphics.drawable.GradientDrawable +import android.net.Uri +import android.os.Build +import android.os.Environment +import android.provider.MediaStore +import android.text.TextUtils +import android.util.Base64 +import android.view.MenuItem +import android.view.View +import android.webkit.URLUtil +import android.widget.ImageView +import android.widget.Toast +import androidx.annotation.IntDef +import androidx.core.content.ContextCompat +import androidx.core.content.FileProvider +import com.bumptech.glide.Glide +import com.bumptech.glide.load.resource.drawable.DrawableTransitionOptions +import com.bumptech.glide.request.RequestOptions +import com.github.panpf.sketch.request.DownloadRequest +import com.github.panpf.sketch.request.DownloadResult +import com.huanchengfly.tieba.post.App.Companion.INSTANCE +import com.huanchengfly.tieba.post.R +import com.huanchengfly.tieba.post.activities.PhotoViewActivity.Companion.launch +import com.huanchengfly.tieba.post.components.transformations.RadiusTransformation +import com.huanchengfly.tieba.post.dpToPx +import com.huanchengfly.tieba.post.models.PhotoViewBean +import com.huanchengfly.tieba.post.utils.PermissionUtils.PermissionData +import com.huanchengfly.tieba.post.utils.PermissionUtils.askPermission +import com.huanchengfly.tieba.post.utils.ThemeUtil.isNightMode +import com.zhihu.matisse.MimeType +import kotlinx.coroutines.CoroutineScope +import kotlinx.coroutines.Dispatchers +import kotlinx.coroutines.launch +import kotlinx.coroutines.withContext +import java.io.ByteArrayOutputStream +import java.io.File +import java.io.FileInputStream +import java.io.FileNotFoundException +import java.io.FileOutputStream +import java.io.IOException +import java.io.InputStream +import java.nio.channels.FileChannel + +object ImageUtil { + /** + * 智能省流 + */ + const val SETTINGS_SMART_ORIGIN = 0 + + /** + * 智能无图 + */ + const val SETTINGS_SMART_LOAD = 1 + + /** + * 始终高质量 + */ + const val SETTINGS_ALL_ORIGIN = 2 + + /** + * 始终无图 + */ + const val SETTINGS_ALL_NO = 3 + const val LOAD_TYPE_SMALL_PIC = 0 + const val LOAD_TYPE_AVATAR = 1 + const val LOAD_TYPE_NO_RADIUS = 2 + const val LOAD_TYPE_ALWAYS_ROUND = 3 + const val TAG = "ImageUtil" + private fun isGifFile(file: File?): Boolean { + if (file == null) return false + try { + return FileInputStream(file).use { isGifFile(it) } + } catch (e: FileNotFoundException) { + e.printStackTrace() + } + return false + } + + //判断是否为GIF文件 + private fun isGifFile(inputStream: InputStream?): Boolean { + if (inputStream == null) return false + val bytes = ByteArray(4) + try { + inputStream.mark(0) + inputStream.read(bytes) + inputStream.reset() + val str = String(bytes) + return str.equals("GIF8", ignoreCase = true) + } catch (e: IOException) { + e.printStackTrace() + } + return false + } + + @JvmOverloads + fun compressImage(bitmap: Bitmap, output: File, maxSize: Int = 100): File { + val baos = ByteArrayOutputStream() + var quality = 100 + bitmap.compress(CompressFormat.JPEG, quality, baos) //质量压缩方法,这里100表示不压缩,把压缩后的数据存放到baos中 + while (baos.toByteArray().size / 1024 > maxSize && quality > 0) { //循环判断如果压缩后图片是否大于20kb,大于继续压缩 友盟缩略图要求不大于18kb + baos.reset() //重置baos即清空baos + quality -= 5 //每次都减少5 + bitmap.compress(CompressFormat.JPEG, quality, baos) //这里压缩options%,把压缩后的数据存放到baos中 + } + try { + val fos = FileOutputStream(output) + try { + fos.write(baos.toByteArray()) + fos.flush() + fos.close() + } catch (e: IOException) { + e.printStackTrace() + } + } catch (e: FileNotFoundException) { + e.printStackTrace() + } + return output + } + + @JvmOverloads + fun bitmapToFile( + bitmap: Bitmap, + output: File, + format: CompressFormat? = CompressFormat.JPEG + ): File { + val baos = ByteArrayOutputStream() + bitmap.compress(format, 100, baos) + try { + val fos = FileOutputStream(output) + try { + fos.write(baos.toByteArray()) + fos.flush() + fos.close() + } catch (e: IOException) { + e.printStackTrace() + } + } catch (e: FileNotFoundException) { + e.printStackTrace() + } + return output + } + + fun drawableToBitmap(drawable: Drawable): Bitmap { + val bitmap = Bitmap.createBitmap( + drawable.intrinsicWidth, drawable.intrinsicHeight, + if (drawable.opacity != PixelFormat.OPAQUE) Bitmap.Config.ARGB_8888 else Bitmap.Config.RGB_565 + ) + val canvas = Canvas(bitmap) + drawable.setBounds(0, 0, drawable.intrinsicWidth, drawable.intrinsicHeight) + drawable.draw(canvas) + return bitmap + } + + fun copyFile(src: FileInputStream?, dest: FileOutputStream?): Boolean { + if (src == null || dest == null) { + return false + } + val srcChannel: FileChannel? + val dstChannel: FileChannel? + try { + srcChannel = src.channel + dstChannel = dest.channel + srcChannel.transferTo(0, srcChannel.size(), dstChannel) + } catch (e: IOException) { + e.printStackTrace() + return false + } + try { + srcChannel.close() + dstChannel.close() + } catch (e: IOException) { + e.printStackTrace() + } + return true + } + + fun copyFile(src: File?, dest: File?): Boolean { + if (src == null || dest == null) { + return false + } + if (dest.exists()) { + dest.delete() + } + try { + dest.createNewFile() + } catch (e: IOException) { + e.printStackTrace() + } + val srcChannel: FileChannel? + val dstChannel: FileChannel? + try { + srcChannel = FileInputStream(src).channel + dstChannel = FileOutputStream(dest).channel + srcChannel.transferTo(0, srcChannel.size(), dstChannel) + } catch (e: IOException) { + e.printStackTrace() + return false + } + try { + srcChannel.close() + dstChannel.close() + } catch (e: IOException) { + e.printStackTrace() + } + return true + } + + private fun changeBrightness(imageView: ImageView, brightness: Int) { + val cMatrix = ColorMatrix() + cMatrix.set( + floatArrayOf( + 1f, 0f, 0f, 0f, brightness.toFloat(), 0f, 1f, 0f, 0f, brightness.toFloat(), // 改变亮度 + 0f, 0f, 1f, 0f, brightness.toFloat(), 0f, 0f, 0f, 1f, 0f + ) + ) + imageView.colorFilter = ColorMatrixColorFilter(cMatrix) + } + + @SuppressLint("StaticFieldLeak") + fun download(context: Context, url: String?) { + download(context, url, false, null) + } + + private fun downloadForShare(context: Context, url: String?, taskCallback: ShareTaskCallback) { + if (url == null) return + CoroutineScope(Dispatchers.IO).launch { + val downloadResult = DownloadRequest(context, url).execute() + if (downloadResult is DownloadResult.Success) { + val inputStream = downloadResult.data.data.newInputStream() + val pictureFolder = File(context.cacheDir, ".shareTemp") + if (pictureFolder.exists() || pictureFolder.mkdirs()) { + val fileName = "share_" + System.currentTimeMillis() + val destFile = File(pictureFolder, fileName) + if (!destFile.exists()) { + withContext(Dispatchers.IO) { + destFile.createNewFile() + } + } + inputStream.use { input -> + if (destFile.canWrite()) { + destFile.outputStream().use { output -> + input.copyTo(output) + } + } + } + val shareUri = if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.N) { + FileProvider.getUriForFile( + context, + context.packageName + ".share.FileProvider", + destFile + ) + } else { + Uri.fromFile(destFile) + } + withContext(Dispatchers.Main) { + taskCallback.onGetUri(shareUri) + } + } + } + } + } + + @SuppressLint("StaticFieldLeak") + fun download( + context: Context, + url: String?, + forShare: Boolean, + taskCallback: ShareTaskCallback? + ) { + if (forShare) { + if (taskCallback != null) downloadForShare(context, url, taskCallback) + return + } + if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.Q) { + downloadAboveQ(context, url) + return + } + askPermission( + context, + PermissionData( + listOf( + PermissionUtils.READ_EXTERNAL_STORAGE, + PermissionUtils.WRITE_EXTERNAL_STORAGE + ), + context.getString(R.string.tip_permission_storage) + ), + R.string.toast_no_permission_save_photo + ) { + downloadBelowQ(context, url) + } + } + + private fun downloadAboveQ(context: Context, url: String?) { + if (Build.VERSION.SDK_INT < Build.VERSION_CODES.Q) { + return + } + CoroutineScope(Dispatchers.IO).launch { + val downloadResult = DownloadRequest(context, url).execute() + if (downloadResult is DownloadResult.Success) { + var mimeType = MimeType.JPEG.toString() + var fileName = URLUtil.guessFileName(url, null, mimeType) + downloadResult.data.data.newInputStream().use { inputStream -> + if (isGifFile(inputStream)) { + mimeType = MimeType.GIF.toString() + fileName = FileUtil.changeFileExtension(fileName, ".gif") + } + } + downloadResult.data.data.newInputStream().use { inputStream -> + val relativePath = + Environment.DIRECTORY_PICTURES + File.separator + FileUtil.FILE_FOLDER + val values = ContentValues().apply { + put(MediaStore.Images.Media.RELATIVE_PATH, relativePath) + put(MediaStore.Images.Media.DISPLAY_NAME, fileName) + put(MediaStore.Images.Media.MIME_TYPE, mimeType) + put(MediaStore.Images.Media.DESCRIPTION, fileName) + } + val cr = context.contentResolver + val uri: Uri = runCatching { + cr.insert( + MediaStore.Images.Media.EXTERNAL_CONTENT_URI, + values + ) + }.getOrNull() ?: return@launch + try { + cr.openFileDescriptor(uri, "w")?.use { + FileOutputStream(it.fileDescriptor).use { outputStream -> + inputStream.copyTo(outputStream) + } + } + withContext(Dispatchers.Main) { + Toast.makeText( + context, + context.getString(R.string.toast_photo_saved, relativePath), + Toast.LENGTH_SHORT + ).show() + } + } catch (e: Exception) { + e.printStackTrace() + cr.delete(uri, null, null) + } + } + } + } + } + + private fun downloadBelowQ(context: Context, url: String?) { + CoroutineScope(Dispatchers.IO).launch { + val downloadResult = DownloadRequest(context, url).execute() + if (downloadResult is DownloadResult.Success) { + var fileName = URLUtil.guessFileName(url, null, MimeType.JPEG.toString()) + downloadResult.data.data.newInputStream().use { inputStream -> + if (isGifFile(inputStream)) { + fileName = FileUtil.changeFileExtension(fileName, ".gif") + } + } + downloadResult.data.data.newInputStream().use { inputStream -> + val pictureFolder = + Environment.getExternalStoragePublicDirectory(Environment.DIRECTORY_PICTURES) + val appDir = File(pictureFolder, FileUtil.FILE_FOLDER) + val dirExists = + withContext(Dispatchers.IO) { appDir.exists() || appDir.mkdirs() } + if (dirExists) { + val destFile = File(appDir, fileName) + if (!destFile.exists()) { + withContext(Dispatchers.IO) { + destFile.createNewFile() + } + } + destFile.outputStream().use { outputStream -> + inputStream.copyTo(outputStream) + } + context.sendBroadcast( + Intent( + Intent.ACTION_MEDIA_SCANNER_SCAN_FILE, + Uri.fromFile(File(destFile.path)) + ) + ) + withContext(Dispatchers.Main) { + Toast.makeText( + context, + context.getString(R.string.toast_photo_saved, destFile.path), + Toast.LENGTH_SHORT + ).show() + } + } + } + } + } + } + + private fun checkGifFile(file: File) { + if (isGifFile(file)) { + val gifFile = File(file.parentFile, FileUtil.changeFileExtension(file.name, ".gif")) + if (gifFile.exists()) { + file.delete() + } else { + file.renameTo(gifFile) + } + } + } + + fun getPicId(picUrl: String?): String { + val fileName = URLUtil.guessFileName(picUrl, null, MimeType.JPEG.toString()) + return fileName.replace(".jpg", "") + } + + @JvmStatic + fun initImageView( + view: ImageView, + photoViewBeans: List, + position: Int, + forumName: String?, + forumId: String?, + threadId: String?, + seeLz: Boolean, + objType: String? + ) { + view.setOnClickListener { v: View -> + val tag = view.getTag(R.id.image_load_tag) + if (tag != null) { + val loaded = tag as Boolean + if (loaded) { + launch( + v.context, + photoViewBeans.toTypedArray(), + position, + forumName, + forumId, + threadId, + seeLz, + objType + ) + } else { + load(view, LOAD_TYPE_SMALL_PIC, photoViewBeans[position]!!.url, true) + } + } + } + view.setOnLongClickListener { + val popupMenu = PopupUtil.create(view) + popupMenu.menuInflater.inflate(R.menu.menu_image_long_click, popupMenu.menu) + popupMenu.setOnMenuItemClickListener { item: MenuItem -> + if (item.itemId == R.id.menu_save_image) { + download(view.context, photoViewBeans[position]!!.originUrl) + return@setOnMenuItemClickListener true + } + false + } + popupMenu.show() + true + } + } + + @JvmStatic + fun initImageView(view: ImageView, photoViewBeans: List, position: Int) { + view.setOnClickListener { v: View -> + val tag = view.getTag(R.id.image_load_tag) + if (tag != null) { + val loaded = tag as Boolean + if (loaded) { + launch(v.context, photoViewBeans, position) + } else { + load(view, LOAD_TYPE_SMALL_PIC, photoViewBeans[position].url, true) + } + } + } + view.setOnLongClickListener { + val popupMenu = PopupUtil.create(view) + popupMenu.menuInflater.inflate(R.menu.menu_image_long_click, popupMenu.menu) + popupMenu.setOnMenuItemClickListener { item: MenuItem -> + if (item.itemId == R.id.menu_save_image) { + download(view.context, photoViewBeans[position].originUrl) + return@setOnMenuItemClickListener true + } + false + } + popupMenu.show() + true + } + } + + fun initImageView(view: ImageView, photoViewBean: PhotoViewBean) { + val photoViewBeans: MutableList = ArrayList() + photoViewBeans.add(photoViewBean) + initImageView(view, photoViewBeans, 0) + } + + @JvmStatic + fun getNonNullString(vararg strings: String?): String? { + for (url in strings) { + if (!TextUtils.isEmpty(url)) { + return url + } + } + return null + } + + @JvmStatic + fun getRadiusPx(context: Context): Int { + return DisplayUtil.dp2px(context, getRadiusDp(context).toFloat()) + } + + private fun getRadiusDp(context: Context): Int { + return context.appPreferences.radius + } + + fun clear(imageView: ImageView?) { + Glide.with(imageView!!).clear(imageView) + } + + fun getPlaceHolder(context: Context, radius: Int): Drawable { + val drawable = GradientDrawable() + val colorResId = + if (isNightMode()) R.color.color_place_holder_night else R.color.color_place_holder + val color = ContextCompat.getColor(context, colorResId) + drawable.setColor(color) + drawable.cornerRadius = + DisplayUtil.dp2px(context, radius.toFloat()).toFloat() + return drawable + } + + @SuppressLint("CheckResult") + fun load( + imageView: ImageView, + @LoadType type: Int, + url: String?, + skipNetworkCheck: Boolean, + noTransition: Boolean + ) { + if (!Util.canLoadGlide(imageView.context)) { + return + } + val radius = getRadiusDp(imageView.context) + val requestBuilder = + if (skipNetworkCheck || + type == LOAD_TYPE_AVATAR || + imageLoadSettings == SETTINGS_ALL_ORIGIN || + imageLoadSettings == SETTINGS_SMART_ORIGIN || + (imageLoadSettings == SETTINGS_SMART_LOAD && NetworkUtil.isWifiConnected(imageView.context)) + ) { + imageView.setTag(R.id.image_load_tag, true) + Glide.with(imageView).load(url) + } else { + imageView.setTag(R.id.image_load_tag, false) + Glide.with(imageView).load( + getPlaceHolder( + imageView.context, + if (type == LOAD_TYPE_SMALL_PIC) radius else 0 + ) + ) + } + if (isNightMode()) { + changeBrightness(imageView, -35) + } else { + imageView.clearColorFilter() + } + when (type) { + LOAD_TYPE_SMALL_PIC -> requestBuilder.apply( + RequestOptions.bitmapTransform(RadiusTransformation(radius)) + .placeholder(getPlaceHolder(imageView.context, radius)) + .skipMemoryCache(true) + ) + + LOAD_TYPE_AVATAR -> requestBuilder.apply( + RequestOptions.bitmapTransform(RadiusTransformation(6)) + .placeholder(getPlaceHolder(imageView.context, 6)) + .skipMemoryCache(true) + ) + + LOAD_TYPE_NO_RADIUS -> requestBuilder.apply( + RequestOptions() + .placeholder(getPlaceHolder(imageView.context, 0)) + .skipMemoryCache(true) + ) + + LOAD_TYPE_ALWAYS_ROUND -> requestBuilder.apply( + RequestOptions() + .circleCrop() + .placeholder(getPlaceHolder(imageView.context, 100.dpToPx())) + .skipMemoryCache(true) + ) + } + if (!noTransition) { + requestBuilder.transition(DrawableTransitionOptions.withCrossFade()) + } + requestBuilder.into(imageView) + } + + @JvmStatic + @JvmOverloads + fun load( + imageView: ImageView, + @LoadType type: Int, + url: String?, + skipNetworkCheck: Boolean = false + ) { + load(imageView, type, url, skipNetworkCheck, false) + } + + /** + * 获取要加载的图片 Url + * + * @param isSmallPic 加载的是否为缩略图 + * @param originUrl 原图 Url + * @param smallPicUrls 缩略图 Url,按照画质从好到差排序 + * @return 要加载的图片 Url + */ + @JvmStatic + fun getUrl( + context: Context, + isSmallPic: Boolean, + originUrl: String, + vararg smallPicUrls: String? + ): String { + val urls = mutableListOf(*smallPicUrls) + if (isSmallPic) { + if (needReverse(context)) { + urls.reverse() + } + for (url in urls) { + if (!url.isNullOrEmpty()) { + return url + } + } + } + return originUrl + } + + private fun needReverse(context: Context): Boolean { + return if (imageLoadSettings == SETTINGS_SMART_ORIGIN && + NetworkUtil.isWifiConnected(context) + ) false + else imageLoadSettings != SETTINGS_ALL_ORIGIN + } + + @get:ImageLoadSettings + private val imageLoadSettings: Int + get() = INSTANCE.appPreferences.imageLoadType!!.toInt() + + fun imageToBase64(inputStream: InputStream?): String? { + if (inputStream == null) { + return null + } + return runCatching { + inputStream.use { + Base64.encodeToString(inputStream.readBytes(), Base64.DEFAULT) + } + }.getOrNull() + } + + fun imageToBase64(file: File?): String? { + if (file == null) { + return null + } + var result: String? = null + try { + val `is`: InputStream = FileInputStream(file) + result = imageToBase64(`is`) + } catch (e: IOException) { + e.printStackTrace() + } + return result + } + + interface ShareTaskCallback { + fun onGetUri(uri: Uri) + } + + @IntDef(LOAD_TYPE_SMALL_PIC, LOAD_TYPE_AVATAR, LOAD_TYPE_NO_RADIUS, LOAD_TYPE_ALWAYS_ROUND) + @Retention(AnnotationRetention.SOURCE) + annotation class LoadType + + @IntDef(SETTINGS_SMART_ORIGIN, SETTINGS_SMART_LOAD, SETTINGS_ALL_ORIGIN, SETTINGS_ALL_NO) + annotation class ImageLoadSettings + +} + +fun ImageUtil.download( + context: Context, + url: String?, + forShare: Boolean, + taskCallback: (Uri) -> Unit +) { + download(context, url, forShare, object : ImageUtil.ShareTaskCallback { + override fun onGetUri(uri: Uri) { + taskCallback(uri) + } + }) +}