From 8c07ef51ed6e15ba7c3dce55c92ee9e6e693dbd2 Mon Sep 17 00:00:00 2001 From: HuanCheng65 <22636177+HuanCheng65@users.noreply.github.com> Date: Wed, 15 Jun 2022 12:30:00 +0800 Subject: [PATCH] =?UTF-8?q?pref:=20=E4=BC=98=E5=8C=96=E6=9D=83=E9=99=90?= =?UTF-8?q?=E7=94=B3=E8=AF=B7=E6=B5=81=E7=A8=8B?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- app/build.gradle | 5 +- app/src/main/AndroidManifest.xml | 3 + .../tieba/post/activities/MainActivity.kt | 20 -- .../tieba/post/activities/ReplyActivity.kt | 69 +++--- .../activities/TranslucentThemeActivity.kt | 34 ++- .../tieba/post/adapters/InsertPhotoAdapter.kt | 209 ++++++++++-------- .../FailureResponseInterceptor.kt | 15 +- .../dialogs/RequestPermissionTipDialog.kt | 43 ++++ .../tieba/post/fragments/WebViewFragment.java | 32 ++- .../fragments/intro/PermissionFragment.kt | 93 +++----- .../tieba/post/utils/FileUtil.java | 17 +- .../tieba/post/utils/ImageUtil.java | 17 +- .../tieba/post/utils/PermissionUtil.java | 153 ------------- .../tieba/post/utils/PermissionUtils.kt | 190 ++++++++++++++++ .../edittext/widget/UndoableEditText.java | 28 +++ .../layout/dialog_request_permission_tip.xml | 51 +++++ .../main/res/menu/menu_undoable_edit_text.xml | 9 + app/src/main/res/values/strings.xml | 15 ++ app/src/main/res/values/styles.xml | 7 + 19 files changed, 610 insertions(+), 400 deletions(-) create mode 100644 app/src/main/java/com/huanchengfly/tieba/post/components/dialogs/RequestPermissionTipDialog.kt delete mode 100644 app/src/main/java/com/huanchengfly/tieba/post/utils/PermissionUtil.java create mode 100644 app/src/main/java/com/huanchengfly/tieba/post/utils/PermissionUtils.kt create mode 100644 app/src/main/res/layout/dialog_request_permission_tip.xml create mode 100644 app/src/main/res/menu/menu_undoable_edit_text.xml diff --git a/app/build.gradle b/app/build.gradle index 564f97e6..3353b2eb 100644 --- a/app/build.gradle +++ b/app/build.gradle @@ -89,12 +89,11 @@ dependencies { //兼容 Glide implementation "com.android.support:support-annotations:28.0.0" - implementation "androidx.work:work-runtime:2.5.0" kapt "com.android.support:support-annotations:28.0.0" //AndroidX implementation "androidx.appcompat:appcompat:1.3.1" - implementation "androidx.core:core-ktx:1.6.0" + implementation "androidx.core:core-ktx:1.7.0" implementation "androidx.annotation:annotation:1.3.0" implementation "androidx.constraintlayout:constraintlayout:2.1.1" implementation "androidx.preference:preference-ktx:1.1.1" @@ -138,7 +137,7 @@ dependencies { implementation "com.scwang.smart:refresh-header-material:2.0.1" implementation 'com.github.lwj1994:Matisse:0.5.3-20220115' - implementation "com.yanzhenjie:permission:2.0.3" + implementation 'com.github.getActivity:XXPermissions:13.6' implementation "com.gyf.immersionbar:immersionbar:3.0.0" implementation "com.github.yalantis:ucrop:2.2.7" diff --git a/app/src/main/AndroidManifest.xml b/app/src/main/AndroidManifest.xml index c7111725..eb400685 100644 --- a/app/src/main/AndroidManifest.xml +++ b/app/src/main/AndroidManifest.xml @@ -92,6 +92,9 @@ + { - try { - if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.Q) { - String relativePath = Environment.DIRECTORY_PICTURES + File.separator + "Tieba Lite" + File.separator + "shareTemp"; - String where = MediaStore.Images.Media.RELATIVE_PATH + " like \"" + relativePath + "%" + "\""; - int i = getContentResolver().deleteAll(MediaStore.Images.Media.EXTERNAL_CONTENT_URI, where, null); - } else { - if (AndPermission.hasPermissions(this, Permission.Group.STORAGE)) { - File shareTemp = new File(Environment.getExternalStoragePublicDirectory(Environment.DIRECTORY_PICTURES).getAbsoluteFile(), "Tieba Lite" + File.separator + "shareTemp"); - if (shareTemp.exists() && shareTemp.deleteAll()) { - FileUtil.deleteAllFiles(shareTemp); - } - } - } - } catch (Exception e) { - e.printStackTrace(); - } - }, 100); - */ } override fun recreate() { diff --git a/app/src/main/java/com/huanchengfly/tieba/post/activities/ReplyActivity.kt b/app/src/main/java/com/huanchengfly/tieba/post/activities/ReplyActivity.kt index 367538b2..8ed37bb2 100644 --- a/app/src/main/java/com/huanchengfly/tieba/post/activities/ReplyActivity.kt +++ b/app/src/main/java/com/huanchengfly/tieba/post/activities/ReplyActivity.kt @@ -19,6 +19,7 @@ import android.widget.FrameLayout import android.widget.GridView import android.widget.RelativeLayout import android.widget.Toast +import androidx.activity.result.ActivityResultLauncher import androidx.appcompat.widget.Toolbar import androidx.recyclerview.widget.ItemTouchHelper import androidx.recyclerview.widget.LinearLayoutManager @@ -49,10 +50,10 @@ import com.huanchengfly.tieba.post.utils.* import com.huanchengfly.tieba.post.widgets.edittext.widget.UndoableEditText import com.huanchengfly.tieba.post.widgets.theme.TintConstraintLayout import com.huanchengfly.tieba.post.widgets.theme.TintImageView -import com.zhihu.matisse.Matisse import org.litepal.LitePal.where -class ReplyActivity : BaseActivity(), View.OnClickListener { +class ReplyActivity : BaseActivity(), View.OnClickListener, + InsertPhotoAdapter.MatisseLauncherProvider { @BindView(R.id.activity_reply_edit_text) lateinit var editText: UndoableEditText @@ -84,7 +85,9 @@ class ReplyActivity : BaseActivity(), View.OnClickListener { lateinit var toolbar: Toolbar private var replyInfoBean: ReplyInfoBean? = null private var loadingDialog: LoadingDialog? = null - private var insertPhotoAdapter: InsertPhotoAdapter? = null + private val insertPhotoAdapter: InsertPhotoAdapter by lazy { + InsertPhotoAdapter(this) + } private var sendItem: MenuItem? = null private var replySuccess = false private var content: String? = null @@ -92,6 +95,17 @@ class ReplyActivity : BaseActivity(), View.OnClickListener { override val isNeedImmersionBar: Boolean get() = false + @JvmField + val matisseLauncher = registerForActivityResult(InsertPhotoAdapter.MatisseResultContract()) { + val photoInfoBeans = insertPhotoAdapter.getFileList().toMutableList() + for (uri in it) { + photoInfoBeans.add(PhotoInfoBean(this, uri)) + } + insertPhotoAdapter.setFileList(photoInfoBeans) + } + + override fun getMatisseLauncher(): ActivityResultLauncher = matisseLauncher + override fun getLayoutId(): Int { return R.layout.activity_reply } @@ -217,11 +231,11 @@ class ReplyActivity : BaseActivity(), View.OnClickListener { ): Int { var dragFlags = 0 var swiped = 0 - if (viewHolder.adapterPosition < insertPhotoAdapter!!.itemCount - 1) { + if (viewHolder.adapterPosition < insertPhotoAdapter.itemCount - 1) { swiped = ItemTouchHelper.UP or ItemTouchHelper.DOWN - if (viewHolder.adapterPosition < insertPhotoAdapter!!.itemCount - 2 && viewHolder.adapterPosition > 0) { + if (viewHolder.adapterPosition < insertPhotoAdapter.itemCount - 2 && viewHolder.adapterPosition > 0) { dragFlags = ItemTouchHelper.RIGHT or ItemTouchHelper.LEFT - } else if (viewHolder.adapterPosition == insertPhotoAdapter!!.itemCount - 2) { + } else if (viewHolder.adapterPosition == insertPhotoAdapter.itemCount - 2) { dragFlags = ItemTouchHelper.LEFT } else if (viewHolder.adapterPosition == 0) { dragFlags = ItemTouchHelper.RIGHT @@ -237,17 +251,17 @@ class ReplyActivity : BaseActivity(), View.OnClickListener { ): Boolean { val oldPosition = viewHolder.adapterPosition val newPosition = target.adapterPosition - if (newPosition < insertPhotoAdapter!!.itemCount - 1) { + if (newPosition < insertPhotoAdapter.itemCount - 1) { if (oldPosition < newPosition) { for (i in oldPosition until newPosition) { - insertPhotoAdapter!!.swap(i, i + 1) + insertPhotoAdapter.swap(i, i + 1) } } else { for (i in oldPosition downTo newPosition + 1) { - insertPhotoAdapter!!.swap(i, i - 1) + insertPhotoAdapter.swap(i, i - 1) } } - insertPhotoAdapter!!.notifyItemMoved(oldPosition, newPosition) + insertPhotoAdapter.notifyItemMoved(oldPosition, newPosition) return true } return false @@ -255,7 +269,7 @@ class ReplyActivity : BaseActivity(), View.OnClickListener { override fun onSwiped(viewHolder: RecyclerView.ViewHolder, direction: Int) { val position = viewHolder.adapterPosition - insertPhotoAdapter!!.remove(position) + insertPhotoAdapter.remove(position) } }) mItemTouchHelper.attachToRecyclerView(insertView) @@ -267,7 +281,6 @@ class ReplyActivity : BaseActivity(), View.OnClickListener { } else { insertImageBtn.visibility = View.INVISIBLE } - insertPhotoAdapter = InsertPhotoAdapter(this) insertView.adapter = insertPhotoAdapter val layoutManager = LinearLayoutManager(this) layoutManager.orientation = RecyclerView.HORIZONTAL @@ -315,7 +328,7 @@ class ReplyActivity : BaseActivity(), View.OnClickListener { private fun canSend(): Boolean { return editText.text.toString().isNotEmpty() || - insertPhotoAdapter!!.fileList.size > 0 + insertPhotoAdapter.getFileList().isNotEmpty() } private fun needUpload(): Boolean { @@ -323,7 +336,7 @@ class ReplyActivity : BaseActivity(), View.OnClickListener { if (replyInfoBean!!.isSubFloor) { return false } - for (photoInfoBean in insertPhotoAdapter!!.fileList) { + for (photoInfoBean in insertPhotoAdapter.getFileList()) { if (photoInfoBean.webUploadPicBean == null) { needUpload = true break @@ -352,12 +365,13 @@ class ReplyActivity : BaseActivity(), View.OnClickListener { val builder = StringBuilder() if (hasPhoto()) { if (!needUpload()) { - for (photoInfoBean in insertPhotoAdapter!!.fileList) { + for (photoInfoBean in insertPhotoAdapter.getFileList()) { if (photoInfoBean.webUploadPicBean != null) { builder.append(photoInfoBean.webUploadPicBean.imageInfo) - if (insertPhotoAdapter!!.fileList.size - 1 > insertPhotoAdapter!!.fileList.indexOf( - photoInfoBean - ) + if (insertPhotoAdapter.getFileList().size - 1 > insertPhotoAdapter.getFileList() + .indexOf( + photoInfoBean + ) ) { builder.append("|") } @@ -367,7 +381,7 @@ class ReplyActivity : BaseActivity(), View.OnClickListener { return } UploadHelper.with(this) - .setFileList(insertPhotoAdapter!!.fileList) + .setFileList(insertPhotoAdapter.getFileList()) .setCallback(object : UploadCallback { override fun onSuccess(photoInfoBeans: List) { for (photoInfoBean in photoInfoBeans) { @@ -401,7 +415,8 @@ class ReplyActivity : BaseActivity(), View.OnClickListener { } private fun hasPhoto(): Boolean { - return insertPhotoAdapter!!.fileList != null && insertPhotoAdapter!!.fileList.size > 0 + return insertPhotoAdapter.getFileList() + .isNotEmpty() } private fun setEnabled(imageButton: TintImageView, enable: Boolean) { @@ -570,20 +585,6 @@ class ReplyActivity : BaseActivity(), View.OnClickListener { }) } - override fun onActivityResult(requestCode: Int, resultCode: Int, data: Intent?) { - super.onActivityResult(requestCode, resultCode, data) - if (requestCode == REQUEST_CODE_CHOOSE && resultCode == RESULT_OK) { - val uriList = Matisse.obtainResult(data) - val photoInfoBeans = insertPhotoAdapter!!.fileList - for (uri in uriList) { - val infoBean = PhotoInfoBean(this, uri) - photoInfoBeans.add(infoBean) - } - insertPhotoAdapter!!.fileList = photoInfoBeans - sendItem!!.isEnabled = true - } - } - override fun onClick(v: View) { when (v.id) { R.id.activity_reply_root -> finish() diff --git a/app/src/main/java/com/huanchengfly/tieba/post/activities/TranslucentThemeActivity.kt b/app/src/main/java/com/huanchengfly/tieba/post/activities/TranslucentThemeActivity.kt index 6101c8d3..9d9be18f 100644 --- a/app/src/main/java/com/huanchengfly/tieba/post/activities/TranslucentThemeActivity.kt +++ b/app/src/main/java/com/huanchengfly/tieba/post/activities/TranslucentThemeActivity.kt @@ -10,7 +10,10 @@ import android.graphics.drawable.Drawable import android.net.Uri import android.os.Build import android.os.Bundle -import android.view.* +import android.view.MenuItem +import android.view.MotionEvent +import android.view.View +import android.view.ViewGroup import android.widget.LinearLayout import android.widget.SeekBar import android.widget.SeekBar.OnSeekBarChangeListener @@ -47,8 +50,6 @@ import com.huanchengfly.tieba.post.widgets.theme.TintMaterialButton import com.jrummyapps.android.colorpicker.ColorPickerDialog import com.jrummyapps.android.colorpicker.ColorPickerDialogListener import com.yalantis.ucrop.UCrop -import com.yanzhenjie.permission.Action -import com.yanzhenjie.permission.runtime.Permission import com.zhihu.matisse.Matisse import com.zhihu.matisse.MimeType import com.zhihu.matisse.engine.impl.GlideEngine @@ -460,7 +461,7 @@ class TranslucentThemeActivity : BaseActivity(), View.OnClickListener, OnSeekBar .choose(MimeType.ofImage()) .theme(if (ThemeUtil.isNightMode(this)) R.style.Matisse_Dracula else R.style.Matisse_Zhihu) .imageEngine(GlideEngine()) - .forResult(REQUEST_CODE_CHOOSE) + .forResult(REQUEST_CODE_CHOOSE) } R.id.custom_color -> { val primaryColorPicker = ColorPickerDialog.newBuilder() @@ -485,14 +486,23 @@ class TranslucentThemeActivity : BaseActivity(), View.OnClickListener, OnSeekBar } } - private fun askPermission(granted: Action>) { - if (Build.VERSION.SDK_INT < Build.VERSION_CODES.Q) { - PermissionUtil.askPermission(this, granted, R.string.toast_no_permission_insert_photo, - PermissionUtil.Permission(Permission.Group.STORAGE, getString(R.string.tip_permission_storage))) - } else { - PermissionUtil.askPermission(this, granted, R.string.toast_no_permission_insert_photo, - PermissionUtil.Permission(Permission.READ_EXTERNAL_STORAGE, getString(R.string.tip_permission_storage))) - } + private fun askPermission(granted: () -> Unit) { + PermissionUtils.askPermission( + this, + PermissionUtils.Permission( + if (Build.VERSION.SDK_INT < Build.VERSION_CODES.Q) { + listOf( + PermissionUtils.READ_EXTERNAL_STORAGE, + PermissionUtils.WRITE_EXTERNAL_STORAGE + ) + } else { + listOf(PermissionUtils.READ_EXTERNAL_STORAGE) + }, + getString(R.string.tip_permission_storage) + ), + R.string.toast_no_permission_insert_photo, + granted + ) } interface SavePicCallback { diff --git a/app/src/main/java/com/huanchengfly/tieba/post/adapters/InsertPhotoAdapter.kt b/app/src/main/java/com/huanchengfly/tieba/post/adapters/InsertPhotoAdapter.kt index 4adf96b6..a1b4fb6c 100644 --- a/app/src/main/java/com/huanchengfly/tieba/post/adapters/InsertPhotoAdapter.kt +++ b/app/src/main/java/com/huanchengfly/tieba/post/adapters/InsertPhotoAdapter.kt @@ -1,128 +1,143 @@ -package com.huanchengfly.tieba.post.adapters; +package com.huanchengfly.tieba.post.adapters -import android.app.Activity; -import android.content.Context; -import android.os.Build; -import android.view.LayoutInflater; -import android.view.View; -import android.view.ViewGroup; -import android.widget.ImageView; -import android.widget.Toast; +import android.app.Activity.RESULT_OK +import android.content.Context +import android.content.Intent +import android.net.Uri +import android.os.Build +import android.view.LayoutInflater +import android.view.View +import android.view.ViewGroup +import android.widget.ImageView +import android.widget.Toast +import androidx.activity.result.ActivityResultLauncher +import androidx.activity.result.contract.ActivityResultContract +import androidx.appcompat.app.AppCompatActivity +import androidx.recyclerview.widget.RecyclerView +import com.bumptech.glide.Glide +import com.bumptech.glide.request.RequestOptions +import com.huanchengfly.tieba.post.R +import com.huanchengfly.tieba.post.components.MyViewHolder +import com.huanchengfly.tieba.post.components.transformations.RadiusTransformation +import com.huanchengfly.tieba.post.models.PhotoInfoBean +import com.huanchengfly.tieba.post.utils.PermissionUtils +import com.huanchengfly.tieba.post.utils.ThemeUtil +import com.zhihu.matisse.Matisse +import com.zhihu.matisse.MimeType +import com.zhihu.matisse.engine.impl.GlideEngine +import java.util.* -import androidx.annotation.NonNull; -import androidx.recyclerview.widget.RecyclerView; +class InsertPhotoAdapter(private val mContext: Context) : RecyclerView.Adapter() { + private var fileList: MutableList = ArrayList() -import com.bumptech.glide.Glide; -import com.bumptech.glide.request.RequestOptions; -import com.huanchengfly.tieba.post.R; -import com.huanchengfly.tieba.post.activities.ReplyActivity; -import com.huanchengfly.tieba.post.components.MyViewHolder; -import com.huanchengfly.tieba.post.components.transformations.RadiusTransformation; -import com.huanchengfly.tieba.post.models.PhotoInfoBean; -import com.huanchengfly.tieba.post.utils.PermissionUtil; -import com.huanchengfly.tieba.post.utils.ThemeUtil; -import com.yanzhenjie.permission.Action; -import com.yanzhenjie.permission.runtime.Permission; -import com.zhihu.matisse.Matisse; -import com.zhihu.matisse.MimeType; -import com.zhihu.matisse.engine.impl.GlideEngine; - -import java.util.ArrayList; -import java.util.Collections; -import java.util.List; - -public class InsertPhotoAdapter extends RecyclerView.Adapter { - public static final int TYPE_IMAGE = 0; - public static final int TYPE_INSERT = 1; - private static final String TAG = "InsertPhotoAdapter"; - private Context mContext; - private List fileList; - - public InsertPhotoAdapter(Context context) { - super(); - this.mContext = context; - this.fileList = new ArrayList<>(); + fun remove(position: Int) { + fileList.removeAt(position) + notifyItemRemoved(position) } - public void remove(int position) { - fileList.remove(position); - notifyItemRemoved(position); + fun swap(oldPosition: Int, newPosition: Int) { + Collections.swap(fileList, oldPosition, newPosition) } - public void swap(int oldPosition, int newPosition) { - Collections.swap(fileList, oldPosition, newPosition); + fun getFileList(): List { + return fileList } - public List getFileList() { - return fileList; + fun setFileList(fileList: MutableList) { + this.fileList = fileList + notifyDataSetChanged() } - public void setFileList(List fileList) { - this.fileList = fileList; - notifyDataSetChanged(); + override fun getItemViewType(position: Int): Int { + return if (position >= fileList.size) TYPE_INSERT else TYPE_IMAGE } - @Override - public int getItemViewType(int position) { - if (position >= fileList.size()) return TYPE_INSERT; - return TYPE_IMAGE; - } - - @NonNull - @Override - public MyViewHolder onCreateViewHolder(@NonNull ViewGroup parent, int viewType) { + override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): MyViewHolder { if (viewType == TYPE_INSERT) { - View view = LayoutInflater.from(parent.getContext()).inflate(R.layout.item_insert_more, parent, false); - return new MyViewHolder(view); + val view: View = LayoutInflater.from(parent.context) + .inflate(R.layout.item_insert_more, parent, false) + return MyViewHolder(view) } - View view = LayoutInflater.from(parent.getContext()).inflate(R.layout.item_insert_image, parent, false); - return new MyViewHolder(view); + val view: View = LayoutInflater.from(parent.context) + .inflate(R.layout.item_insert_image, parent, false) + return MyViewHolder(view) } - @Override - public void onBindViewHolder(@NonNull MyViewHolder holder, int position) { - if (holder.getItemViewType() == TYPE_IMAGE) { - PhotoInfoBean photoInfoBean = fileList.get(position); - ImageView imageView = holder.getView(R.id.image_preview); + override fun onBindViewHolder(holder: MyViewHolder, position: Int) { + if (holder.itemViewType == TYPE_IMAGE) { + val photoInfoBean: PhotoInfoBean = fileList[position] + val imageView: ImageView = holder.getView(R.id.image_preview) Glide.with(mContext) - .load(photoInfoBean.getFileUri()) - .apply(RequestOptions.bitmapTransform(new RadiusTransformation())) - .into(imageView); - } else if (holder.getItemViewType() == TYPE_INSERT) { - if (fileList.size() < 10) { - holder.setItemOnClickListener((View view) -> { - if (mContext instanceof Activity) { - askPermission(data -> Matisse.from((Activity) mContext) + .load(photoInfoBean.fileUri) + .apply(RequestOptions.bitmapTransform(RadiusTransformation())) + .into(imageView) + } else if (holder.itemViewType == TYPE_INSERT) { + if (fileList.size < 10) { + holder.setItemOnClickListener { + if (mContext is AppCompatActivity && mContext is MatisseLauncherProvider) { + askPermission { + Matisse.from(mContext) .choose(MimeType.ofImage()) .countable(true) - .maxSelectable(10 - fileList.size()) - .theme(ThemeUtil.isNightMode(mContext) ? R.style.Matisse_Dracula : R.style.Matisse_Zhihu) - .imageEngine(new GlideEngine()) - .forResult(ReplyActivity.REQUEST_CODE_CHOOSE)); + .maxSelectable(10 - fileList.size) + .theme(if (ThemeUtil.isNightMode(mContext)) R.style.Matisse_Dracula else R.style.Matisse_Zhihu) + .imageEngine(GlideEngine()) + .forResult(mContext.getMatisseLauncher()) + } } - }); + } } else { - Toast.makeText(mContext, R.string.toast_max_selectable, Toast.LENGTH_SHORT).show(); + Toast.makeText(mContext, R.string.toast_max_selectable, Toast.LENGTH_SHORT).show() } } } - private void askPermission(Action> granted) { - if (Build.VERSION.SDK_INT < Build.VERSION_CODES.Q) { - PermissionUtil.askPermission(mContext, granted, R.string.toast_no_permission_insert_photo, - new PermissionUtil.Permission(Permission.Group.STORAGE, mContext.getString(R.string.tip_permission_storage))); - } else { - PermissionUtil.askPermission(mContext, granted, R.string.toast_no_permission_insert_photo, - new PermissionUtil.Permission(Permission.READ_EXTERNAL_STORAGE, mContext.getString(R.string.tip_permission_storage))); + private fun askPermission(granted: () -> Unit) { + PermissionUtils.askPermission( + mContext, + PermissionUtils.Permission( + if (Build.VERSION.SDK_INT < Build.VERSION_CODES.Q) { + listOf( + PermissionUtils.READ_EXTERNAL_STORAGE, + PermissionUtils.WRITE_EXTERNAL_STORAGE + ) + } else { + listOf(PermissionUtils.READ_EXTERNAL_STORAGE) + }, + mContext.getString(R.string.tip_permission_storage) + ), + R.string.toast_no_permission_insert_photo, + granted + ) + } + + override fun getItemCount(): Int { + return fileList.size + 1 + } + + fun getItem(position: Int): PhotoInfoBean { + return fileList[position] + } + + interface MatisseLauncherProvider { + fun getMatisseLauncher(): ActivityResultLauncher + } + + class MatisseResultContract : ActivityResultContract>() { + override fun createIntent(context: Context, input: Intent): Intent { + return input + } + + override fun parseResult(resultCode: Int, intent: Intent?): List { + if (resultCode != RESULT_OK) { + return emptyList() + } + return Matisse.obtainResult(intent) } } - @Override - public int getItemCount() { - return fileList.size() + 1; - } - - public PhotoInfoBean getItem(int position) { - return fileList.get(position); + companion object { + const val TYPE_IMAGE = 0 + const val TYPE_INSERT = 1 } } \ No newline at end of file diff --git a/app/src/main/java/com/huanchengfly/tieba/post/api/retrofit/interceptors/FailureResponseInterceptor.kt b/app/src/main/java/com/huanchengfly/tieba/post/api/retrofit/interceptors/FailureResponseInterceptor.kt index be6713b2..1342c123 100644 --- a/app/src/main/java/com/huanchengfly/tieba/post/api/retrofit/interceptors/FailureResponseInterceptor.kt +++ b/app/src/main/java/com/huanchengfly/tieba/post/api/retrofit/interceptors/FailureResponseInterceptor.kt @@ -1,10 +1,12 @@ package com.huanchengfly.tieba.post.api.retrofit.interceptors +import android.util.Log import com.google.gson.Gson import com.huanchengfly.tieba.post.api.models.CommonResponse import com.huanchengfly.tieba.post.api.retrofit.exception.TiebaApiException import okhttp3.Interceptor import okhttp3.Response +import java.nio.charset.StandardCharsets object FailureResponseInterceptor : Interceptor { private val gson = Gson() @@ -22,9 +24,18 @@ object FailureResponseInterceptor : Interceptor { contentType.charset(Charsets.UTF_8)!! } - val inputStreamReader = body.source().also { + val inputStream = body.source().also { it.request(Long.MAX_VALUE) - }.buffer.clone().inputStream().reader(charset) + }.buffer.clone().inputStream() + + val length: Int = inputStream.available() + val buffer = ByteArray(length) + inputStream.read(buffer) + val bodyString = String(buffer, StandardCharsets.UTF_8) + + Log.i("ResponseI", bodyString) + + val inputStreamReader = inputStream.reader(charset) val jsonObject = try { gson.fromJson( diff --git a/app/src/main/java/com/huanchengfly/tieba/post/components/dialogs/RequestPermissionTipDialog.kt b/app/src/main/java/com/huanchengfly/tieba/post/components/dialogs/RequestPermissionTipDialog.kt new file mode 100644 index 00000000..04eaffcb --- /dev/null +++ b/app/src/main/java/com/huanchengfly/tieba/post/components/dialogs/RequestPermissionTipDialog.kt @@ -0,0 +1,43 @@ +package com.huanchengfly.tieba.post.components.dialogs + +import android.app.AlertDialog +import android.content.Context +import android.view.Gravity +import android.view.View +import android.view.WindowManager +import android.widget.TextView +import androidx.core.view.setPadding +import com.huanchengfly.tieba.post.R +import com.huanchengfly.tieba.post.dpToPx +import com.huanchengfly.tieba.post.utils.PermissionUtils + + +class RequestPermissionTipDialog(context: Context, permission: PermissionUtils.Permission) : + AlertDialog(context, R.style.Dialog_RequestPermissionTip) { + val title: TextView + val message: TextView + + init { + setCancelable(false) + setView(View.inflate(context, R.layout.dialog_request_permission_tip, null).also { + title = it.findViewById(R.id.request_permission_tip_dialog_title) + message = it.findViewById(R.id.request_permission_tip_dialog_message) + }) + val permissionName = PermissionUtils.transformText(context, permission.permissions).first() + title.text = context.getString(R.string.title_request_permission_tip_dialog, permissionName) + message.text = + context.getString(R.string.message_request_permission_tip_dialog, permission.desc) + } + + override fun show() { + super.show() + window?.let { + it.attributes = it.attributes.apply { + width = WindowManager.LayoutParams.MATCH_PARENT + height = WindowManager.LayoutParams.WRAP_CONTENT + it.decorView.setPadding(16f.dpToPx()) + } + it.setGravity(Gravity.TOP or Gravity.CENTER_HORIZONTAL) + } + } +} \ No newline at end of file diff --git a/app/src/main/java/com/huanchengfly/tieba/post/fragments/WebViewFragment.java b/app/src/main/java/com/huanchengfly/tieba/post/fragments/WebViewFragment.java index 7dddfe17..4cf2774a 100644 --- a/app/src/main/java/com/huanchengfly/tieba/post/fragments/WebViewFragment.java +++ b/app/src/main/java/com/huanchengfly/tieba/post/fragments/WebViewFragment.java @@ -1,5 +1,7 @@ package com.huanchengfly.tieba.post.fragments; +import static com.huanchengfly.tieba.post.utils.FileUtil.FILE_TYPE_DOWNLOAD; + import android.annotation.SuppressLint; import android.annotation.TargetApi; import android.app.Activity; @@ -33,6 +35,7 @@ import androidx.annotation.Nullable; import androidx.swiperefreshlayout.widget.SwipeRefreshLayout; import com.google.android.material.snackbar.Snackbar; +import com.hjq.permissions.Permission; import com.huanchengfly.tieba.post.R; import com.huanchengfly.tieba.post.components.dialogs.PermissionDialog; import com.huanchengfly.tieba.post.interfaces.OnReceivedTitleListener; @@ -43,17 +46,14 @@ import com.huanchengfly.tieba.post.utils.AssetUtil; import com.huanchengfly.tieba.post.utils.DialogUtil; import com.huanchengfly.tieba.post.utils.FileUtil; import com.huanchengfly.tieba.post.utils.NavigationHelper; +import com.huanchengfly.tieba.post.utils.PermissionUtils; import com.huanchengfly.tieba.post.utils.ThemeUtil; import com.huanchengfly.tieba.post.utils.TiebaLiteJavaScript; import com.huanchengfly.tieba.post.utils.Util; -import com.yanzhenjie.permission.AndPermission; -import com.yanzhenjie.permission.runtime.Permission; import org.jetbrains.annotations.NotNull; -import java.util.List; - -import static com.huanchengfly.tieba.post.utils.FileUtil.FILE_TYPE_DOWNLOAD; +import java.util.Arrays; //TODO: 代码太烂,需要重写 public class WebViewFragment extends BaseFragment implements DownloadListener { @@ -380,18 +380,26 @@ public class WebViewFragment extends BaseFragment implements DownloadListener { getAttachContext().getString(R.string.title_ask_permission, uri.getHost(), getAttachContext().getString(R.string.permission_name_location)), R.drawable.ic_round_location_on)) .setOnGrantedCallback(isForever -> { - AndPermission.with(getAttachContext()) - .runtime() - .permission(Permission.ACCESS_COARSE_LOCATION, Permission.ACCESS_FINE_LOCATION) - .onGranted((List permissions) -> { + PermissionUtils.INSTANCE.askPermission( + getAttachContext(), + new PermissionUtils.Permission( + Arrays.asList(Permission.ACCESS_COARSE_LOCATION, Permission.ACCESS_FINE_LOCATION), + getAttachContext().getString(R.string.usage_webview_location_permission) + ), + R.string.tip_no_permission, + () -> { if (isEnabledLocationFunction()) { callback.invoke(origin, true, isForever); } else { callback.invoke(origin, false, false); } - }) - .onDenied((List permissions) -> callback.invoke(origin, false, false)) - .start(); + return null; + }, + () -> { + callback.invoke(origin, false, false); + return null; + } + ); }) .setOnDeniedCallback(isForever -> callback.invoke(origin, false, false)) .show(); diff --git a/app/src/main/java/com/huanchengfly/tieba/post/fragments/intro/PermissionFragment.kt b/app/src/main/java/com/huanchengfly/tieba/post/fragments/intro/PermissionFragment.kt index f261fa1e..0eae1aed 100644 --- a/app/src/main/java/com/huanchengfly/tieba/post/fragments/intro/PermissionFragment.kt +++ b/app/src/main/java/com/huanchengfly/tieba/post/fragments/intro/PermissionFragment.kt @@ -1,67 +1,44 @@ -package com.huanchengfly.tieba.post.fragments.intro; +package com.huanchengfly.tieba.post.fragments.intro -import android.os.Build; -import android.view.ViewGroup; +import android.os.Build +import android.view.ViewGroup +import com.huanchengfly.tieba.post.R +import com.huanchengfly.tieba.post.ui.intro.fragments.BaseIntroFragment +import com.huanchengfly.tieba.post.ui.theme.utils.ThemeUtils +import com.huanchengfly.tieba.post.utils.PermissionUtils -import androidx.annotation.Nullable; +class PermissionFragment : BaseIntroFragment() { + override fun getIconRes(): Int = R.drawable.ic_round_warning + override fun getTitle(): CharSequence = + attachContext.getString(R.string.title_fragment_permission) -import com.huanchengfly.tieba.post.R; -import com.huanchengfly.tieba.post.ui.intro.fragments.BaseIntroFragment; -import com.huanchengfly.tieba.post.ui.theme.utils.ThemeUtils; -import com.huanchengfly.tieba.post.utils.PermissionUtil; -import com.yanzhenjie.permission.runtime.Permission; + override fun getSubtitle(): CharSequence = + attachContext.getString(R.string.subtitle_fragment_permission) -public class PermissionFragment extends BaseIntroFragment { - @Override - public int getIconRes() { - return R.drawable.ic_round_warning; + override fun getIconColor(): Int = ThemeUtils.getColorByAttr(attachContext, R.attr.colorAccent) + override fun getTitleTextColor(): Int = + ThemeUtils.getColorByAttr(attachContext, R.attr.colorText) + + override fun getSubtitleTextColor(): Int = + ThemeUtils.getColorByAttr(attachContext, R.attr.colorTextSecondary) + + override fun getCustomLayoutResId(): Int = R.layout.layout_fragment_permission + + override fun initCustomLayout(container: ViewGroup) { + super.initCustomLayout(container) } - @Nullable - @Override - protected CharSequence getTitle() { - return getAttachContext().getString(R.string.title_fragment_permission); - } - - @Nullable - @Override - protected CharSequence getSubtitle() { - return getAttachContext().getString(R.string.subtitle_fragment_permission); - } - - @Override - protected int getIconColor() { - return ThemeUtils.getColorByAttr(getAttachContext(), R.attr.colorAccent); - } - - @Override - protected int getTitleTextColor() { - return ThemeUtils.getColorByAttr(getAttachContext(), R.attr.colorText); - } - - @Override - protected int getSubtitleTextColor() { - return ThemeUtils.getColorByAttr(getAttachContext(), R.attr.colorTextSecondary); - } - - @Override - protected int getCustomLayoutResId() { - return R.layout.layout_fragment_permission; - } - - @Override - protected void initCustomLayout(ViewGroup container) { - super.initCustomLayout(container); - } - - @Override - public boolean onNext() { + override fun onNext(): Boolean { if (Build.VERSION.SDK_INT < Build.VERSION_CODES.Q) { - PermissionUtil.askPermission(getAttachContext(), data -> next(), - new PermissionUtil.Permission(Permission.READ_PHONE_STATE, getString(R.string.tip_permission_phone)), - new PermissionUtil.Permission(Permission.Group.STORAGE, getString(R.string.tip_permission_storage))); - return true; + PermissionUtils.askPermission( + attachContext, + PermissionUtils.Permission( + listOf(PermissionUtils.READ_PHONE_STATE), + getString(R.string.tip_permission_phone) + ) + ) { next() } + return true } - return false; + return false } -} +} \ No newline at end of file diff --git a/app/src/main/java/com/huanchengfly/tieba/post/utils/FileUtil.java b/app/src/main/java/com/huanchengfly/tieba/post/utils/FileUtil.java index e624d497..f3233429 100644 --- a/app/src/main/java/com/huanchengfly/tieba/post/utils/FileUtil.java +++ b/app/src/main/java/com/huanchengfly/tieba/post/utils/FileUtil.java @@ -16,7 +16,6 @@ import android.text.TextUtils; import android.webkit.URLUtil; import com.huanchengfly.tieba.post.R; -import com.yanzhenjie.permission.runtime.Permission; import java.io.File; import java.io.FileInputStream; @@ -24,6 +23,7 @@ import java.io.FileOutputStream; import java.io.IOException; import java.io.InputStream; import java.nio.charset.StandardCharsets; +import java.util.Arrays; public class FileUtil { public static final int FILE_TYPE_DOWNLOAD = 0; @@ -197,10 +197,17 @@ public class FileUtil { downloadBySystemWithPermission(context, fileType, url, fileName); return; } - PermissionUtil.askPermission(context, - data -> downloadBySystemWithPermission(context, fileType, url, fileName), - R.string.toast_without_permission_download, - new PermissionUtil.Permission(Permission.WRITE_EXTERNAL_STORAGE, context.getString(R.string.tip_permission_storage_download))); + PermissionUtils.INSTANCE.askPermission( + context, + new PermissionUtils.Permission( + Arrays.asList(PermissionUtils.READ_EXTERNAL_STORAGE, PermissionUtils.WRITE_EXTERNAL_STORAGE), + context.getString(R.string.tip_permission_storage_download) + ), + () -> { + downloadBySystemWithPermission(context, fileType, url, fileName); + return null; + } + ); } public static String readFile(File file) { 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 index 913b4460..8700fa6a 100644 --- a/app/src/main/java/com/huanchengfly/tieba/post/utils/ImageUtil.java +++ b/app/src/main/java/com/huanchengfly/tieba/post/utils/ImageUtil.java @@ -45,7 +45,6 @@ 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.yanzhenjie.permission.runtime.Permission; import com.zhihu.matisse.MimeType; import java.io.ByteArrayOutputStream; @@ -254,10 +253,17 @@ public class ImageUtil { downloadAboveQ(context, url, forShare, taskCallback); return; } - PermissionUtil.askPermission(context, - data -> downloadBelowQ(context, url, forShare, taskCallback), + PermissionUtils.INSTANCE.askPermission( + context, + new PermissionUtils.Permission( + Arrays.asList(PermissionUtils.READ_EXTERNAL_STORAGE, PermissionUtils.WRITE_EXTERNAL_STORAGE), + context.getString(R.string.tip_permission_storage) + ), R.string.toast_no_permission_save_photo, - new PermissionUtil.Permission(Permission.Group.STORAGE, context.getString(R.string.tip_permission_storage))); + () -> { + downloadBelowQ(context, url, forShare, taskCallback); + return null; + }); } private static void downloadAboveQ(Context context, String url, boolean forShare, @Nullable ShareTaskCallback taskCallback) { @@ -328,6 +334,9 @@ public class ImageUtil { } } 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; diff --git a/app/src/main/java/com/huanchengfly/tieba/post/utils/PermissionUtil.java b/app/src/main/java/com/huanchengfly/tieba/post/utils/PermissionUtil.java deleted file mode 100644 index 50b4e98f..00000000 --- a/app/src/main/java/com/huanchengfly/tieba/post/utils/PermissionUtil.java +++ /dev/null @@ -1,153 +0,0 @@ -package com.huanchengfly.tieba.post.utils; - -import android.annotation.SuppressLint; -import android.content.Context; -import android.text.TextUtils; -import android.widget.Toast; - -import androidx.annotation.Nullable; -import androidx.annotation.StringRes; - -import com.huanchengfly.tieba.post.R; -import com.yanzhenjie.permission.Action; -import com.yanzhenjie.permission.AndPermission; - -import java.util.ArrayList; -import java.util.Arrays; -import java.util.List; - -public class PermissionUtil { - public static void askPermission(Context context, Permission... permissions) { - askPermission(context, null, permissions); - } - - public static void askPermission(Context context, Action> action, Permission... permissions) { - askPermission(context, action, context.getString(R.string.tip_no_permission), permissions); - } - - public static void askPermission(Context context, Action> action, @StringRes int deniedTip, Permission... permissions) { - askPermission(context, action, context.getString(deniedTip), permissions); - } - - private static String getMessage(Context context, Permission... permissions) { - StringBuilder stringBuilder = new StringBuilder(context.getString(R.string.message_need_permission)); - for (Permission permission : permissions) { - if (permission.isGroup()) { - if (!AndPermission.hasPermissions(context, permission.getPermissionGroup())) { - stringBuilder.append(context.getString(R.string.message_permission_desc, com.yanzhenjie.permission.runtime.Permission.transformText(context, permission.getPermissionGroup()).get(0), permission.getDesc())); - } - } else { - if (!AndPermission.hasPermissions(context, permission.getPermission())) { - stringBuilder.append(context.getString(R.string.message_permission_desc, com.yanzhenjie.permission.runtime.Permission.transformText(context, permission.getPermission()).get(0), permission.getDesc())); - } - } - } - stringBuilder.append(context.getString(R.string.tip_regrant_permission, context.getString(R.string.button_sure_default))); - return stringBuilder.toString(); - } - - @SuppressLint("WrongConstant") - public static void askPermission(Context context, @Nullable Action> action, @Nullable String deniedTip, Permission... permissions) { - List permissionList = new ArrayList<>(); - for (Permission permission : permissions) { - if (permission.isGroup()) { - permissionList.addAll(Arrays.asList(permission.getPermissionGroup())); - } else { - permissionList.add(permission.getPermission()); - } - } - String[] permissionArray = permissionList.toArray(new String[0]); - if (AndPermission.hasPermissions(context, permissionArray) && action != null) { - action.onAction(permissionList); - return; - } - AndPermission.with(context) - .runtime() - .permission(permissionArray) - .rationale((c, data, executor) -> { - DialogUtil.build(c) - .setCancelable(false) - .setTitle(R.string.title_dialog_permission) - .setMessage(getMessage(c, permissions)) - .setPositiveButton(R.string.button_sure_default, (dialog, which) -> { - executor.execute(); - }) - .setNegativeButton(R.string.button_cancel, (dialog, which) -> { - executor.cancel(); - }) - .create() - .show(); - }) - .onGranted(action) - .onDenied(data -> { - if (!TextUtils.isEmpty(deniedTip)) - Toast.makeText(context, deniedTip, Toast.LENGTH_SHORT).show(); - boolean hasAlwaysDeniedPermission = AndPermission.hasAlwaysDeniedPermission(context, data); - if (hasAlwaysDeniedPermission) { - AndPermission.with(context) - .runtime() - .setting() - .start(0); - } - /* - DialogUtil.build(context) - .setCancelable(false) - .setTitle(R.string.title_dialog_permission) - .setMessage(getMessage(context, permissions)) - .setPositiveButton(R.string.button_sure_default, (dialog, which) -> { - if (hasAlwaysDeniedPermission) { - AndPermission.with(context) - .runtime() - .setting() - .onComeback(() -> askPermission(context, action, deniedTip, permissions)) - .start(); - } else { - askPermission(context, action, deniedTip, permissions); - } - }) - .setNegativeButton(R.string.button_cancel, (dialog, which) -> { - if (!TextUtils.isEmpty(deniedTip)) - Toast.makeText(context, deniedTip, Toast.LENGTH_SHORT).show(); - }) - .create() - .show(); - */ - }) - .start(); - } - - public static class Permission { - private boolean isGroup; - private String permission; - private String[] permissionGroup; - private String desc; - - public Permission(String permission, String desc) { - this.isGroup = false; - this.permission = permission; - this.desc = desc; - } - - public Permission(String[] permissionGroup, String desc) { - this.isGroup = true; - this.permissionGroup = permissionGroup; - this.desc = desc; - } - - public String getDesc() { - return desc; - } - - public boolean isGroup() { - return isGroup; - } - - public String getPermission() { - return permission; - } - - public String[] getPermissionGroup() { - return permissionGroup; - } - } -} \ No newline at end of file diff --git a/app/src/main/java/com/huanchengfly/tieba/post/utils/PermissionUtils.kt b/app/src/main/java/com/huanchengfly/tieba/post/utils/PermissionUtils.kt new file mode 100644 index 00000000..6b4d2385 --- /dev/null +++ b/app/src/main/java/com/huanchengfly/tieba/post/utils/PermissionUtils.kt @@ -0,0 +1,190 @@ +package com.huanchengfly.tieba.post.utils + +import android.content.Context +import android.os.Build +import com.hjq.permissions.OnPermissionCallback +import com.hjq.permissions.XXPermissions +import com.huanchengfly.tieba.post.R +import com.huanchengfly.tieba.post.components.dialogs.RequestPermissionTipDialog +import com.huanchengfly.tieba.post.toastShort + + +object PermissionUtils { + const val READ_CALENDAR = "android.permission.READ_CALENDAR" + const val WRITE_CALENDAR = "android.permission.WRITE_CALENDAR" + + const val CAMERA = "android.permission.CAMERA" + + const val READ_CONTACTS = "android.permission.READ_CONTACTS" + const val WRITE_CONTACTS = "android.permission.WRITE_CONTACTS" + const val GET_ACCOUNTS = "android.permission.GET_ACCOUNTS" + + const val ACCESS_FINE_LOCATION = "android.permission.ACCESS_FINE_LOCATION" + const val ACCESS_COARSE_LOCATION = "android.permission.ACCESS_COARSE_LOCATION" + const val ACCESS_BACKGROUND_LOCATION = "android.permission.ACCESS_BACKGROUND_LOCATION" + + const val RECORD_AUDIO = "android.permission.RECORD_AUDIO" + + const val READ_PHONE_STATE = "android.permission.READ_PHONE_STATE" + const val CALL_PHONE = "android.permission.CALL_PHONE" + const val USE_SIP = "android.permission.USE_SIP" + const val READ_PHONE_NUMBERS = "android.permission.READ_PHONE_NUMBERS" + const val ANSWER_PHONE_CALLS = "android.permission.ANSWER_PHONE_CALLS" + const val ADD_VOICEMAIL = "com.android.voicemail.permission.ADD_VOICEMAIL" + + const val READ_CALL_LOG = "android.permission.READ_CALL_LOG" + const val WRITE_CALL_LOG = "android.permission.WRITE_CALL_LOG" + const val PROCESS_OUTGOING_CALLS = "android.permission.PROCESS_OUTGOING_CALLS" + + const val BODY_SENSORS = "android.permission.BODY_SENSORS" + const val ACTIVITY_RECOGNITION = "android.permission.ACTIVITY_RECOGNITION" + + const val SEND_SMS = "android.permission.SEND_SMS" + const val RECEIVE_SMS = "android.permission.RECEIVE_SMS" + const val READ_SMS = "android.permission.READ_SMS" + const val RECEIVE_WAP_PUSH = "android.permission.RECEIVE_WAP_PUSH" + const val RECEIVE_MMS = "android.permission.RECEIVE_MMS" + + const val READ_EXTERNAL_STORAGE = "android.permission.READ_EXTERNAL_STORAGE" + const val WRITE_EXTERNAL_STORAGE = "android.permission.WRITE_EXTERNAL_STORAGE" + + /** + * Turn permissions into text. + */ + fun transformText(context: Context, permissions: List): List { + val textList: MutableList = ArrayList() + for (permission in permissions) { + when (permission) { + READ_CALENDAR, WRITE_CALENDAR -> { + val message = context.getString(R.string.permission_name_calendar) + if (!textList.contains(message)) { + textList.add(message) + } + } + CAMERA -> { + val message = context.getString(R.string.permission_name_camera) + if (!textList.contains(message)) { + textList.add(message) + } + } + GET_ACCOUNTS, READ_CONTACTS, WRITE_CONTACTS -> { + val message = context.getString(R.string.permission_name_contacts) + if (!textList.contains(message)) { + textList.add(message) + } + } + ACCESS_FINE_LOCATION, ACCESS_COARSE_LOCATION -> { + val message = context.getString(R.string.permission_name_location) + if (!textList.contains(message)) { + textList.add(message) + } + } + RECORD_AUDIO -> { + val message = context.getString(R.string.permission_name_microphone) + if (!textList.contains(message)) { + textList.add(message) + } + } + READ_PHONE_STATE, CALL_PHONE, ADD_VOICEMAIL, USE_SIP, READ_PHONE_NUMBERS, ANSWER_PHONE_CALLS -> { + val message = context.getString(R.string.permission_name_phone) + if (!textList.contains(message)) { + textList.add(message) + } + } + READ_CALL_LOG, WRITE_CALL_LOG, PROCESS_OUTGOING_CALLS -> { + val messageId: Int = + if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.Q) R.string.permission_name_call_log else R.string.permission_name_phone + val message = context.getString(messageId) + if (!textList.contains(message)) { + textList.add(message) + } + } + BODY_SENSORS -> { + val message = context.getString(R.string.permission_name_sensors) + if (!textList.contains(message)) { + textList.add(message) + } + } + ACTIVITY_RECOGNITION -> { + val message = context.getString(R.string.permission_name_activity_recognition) + if (!textList.contains(message)) { + textList.add(message) + } + } + SEND_SMS, RECEIVE_SMS, READ_SMS, RECEIVE_WAP_PUSH, RECEIVE_MMS -> { + val message = context.getString(R.string.permission_name_sms) + if (!textList.contains(message)) { + textList.add(message) + } + } + READ_EXTERNAL_STORAGE, WRITE_EXTERNAL_STORAGE -> { + val message = context.getString(R.string.permission_name_storage) + if (!textList.contains(message)) { + textList.add(message) + } + } + } + } + return textList + } + + fun askPermission(context: Context, permission: Permission, onGranted: () -> Unit) { + askPermission(context, permission, R.string.tip_no_permission, onGranted) + } + + fun askPermission( + context: Context, + permission: Permission, + deniedToast: Int, + onGranted: () -> Unit + ) { + askPermission(context, permission, context.getString(deniedToast), onGranted, null) + } + + fun askPermission( + context: Context, + permission: Permission, + deniedToast: Int, + onGranted: () -> Unit, + onDenied: (() -> Unit)? + ) { + askPermission(context, permission, context.getString(deniedToast), onGranted, onDenied) + } + + @JvmOverloads + fun askPermission( + context: Context, + permission: Permission, + deniedToast: String, + onGranted: () -> Unit, + onDenied: (() -> Unit)? = null + ) { + if (XXPermissions.isGranted(context, permission.permissions)) { + onGranted() + } else { + val dialog = RequestPermissionTipDialog(context, permission) + XXPermissions.with(context) + .permission(permission.permissions) + .request(object : OnPermissionCallback { + override fun onGranted(permissions: List, all: Boolean) { + if (all) { + onGranted() + } else { + context.toastShort(deniedToast) + onDenied?.invoke() + } + dialog.dismiss() + } + + override fun onDenied(permissions: List, never: Boolean) { + context.toastShort(deniedToast) + onDenied?.invoke() + dialog.dismiss() + } + }) + dialog.show() + } + } + + data class Permission(val permissions: List, val desc: String) +} diff --git a/app/src/main/java/com/huanchengfly/tieba/post/widgets/edittext/widget/UndoableEditText.java b/app/src/main/java/com/huanchengfly/tieba/post/widgets/edittext/widget/UndoableEditText.java index 2ccee903..bca53808 100644 --- a/app/src/main/java/com/huanchengfly/tieba/post/widgets/edittext/widget/UndoableEditText.java +++ b/app/src/main/java/com/huanchengfly/tieba/post/widgets/edittext/widget/UndoableEditText.java @@ -4,9 +4,15 @@ import android.content.Context; import android.os.Bundle; import android.os.Parcelable; import android.util.AttributeSet; +import android.view.ActionMode; +import android.view.Menu; +import android.view.MenuInflater; +import android.view.MenuItem; import androidx.appcompat.widget.AppCompatEditText; +import androidx.core.widget.TextViewCompat; +import com.huanchengfly.tieba.post.R; import com.huanchengfly.tieba.post.widgets.edittext.OperationManager; public class UndoableEditText extends AppCompatEditText { @@ -50,6 +56,28 @@ public class UndoableEditText extends AppCompatEditText { } private void init() { + TextViewCompat.setCustomSelectionActionModeCallback(this, new ActionMode.Callback() { + @Override + public boolean onCreateActionMode(ActionMode mode, Menu menu) { + MenuInflater menuInflater = mode.getMenuInflater(); + menuInflater.inflate(R.menu.menu_undoable_edit_text, menu); + return true; + } + + @Override + public boolean onPrepareActionMode(ActionMode mode, Menu menu) { + return false; + } + + @Override + public boolean onActionItemClicked(ActionMode mode, MenuItem item) { + return false; + } + + @Override + public void onDestroyActionMode(ActionMode mode) { + } + }); addTextChangedListener(mgr); } diff --git a/app/src/main/res/layout/dialog_request_permission_tip.xml b/app/src/main/res/layout/dialog_request_permission_tip.xml new file mode 100644 index 00000000..11cdf29c --- /dev/null +++ b/app/src/main/res/layout/dialog_request_permission_tip.xml @@ -0,0 +1,51 @@ + + + + + + + + + + \ No newline at end of file diff --git a/app/src/main/res/menu/menu_undoable_edit_text.xml b/app/src/main/res/menu/menu_undoable_edit_text.xml new file mode 100644 index 00000000..bef1c3b6 --- /dev/null +++ b/app/src/main/res/menu/menu_undoable_edit_text.xml @@ -0,0 +1,9 @@ + + + + + \ No newline at end of file diff --git a/app/src/main/res/values/strings.xml b/app/src/main/res/values/strings.xml index a567b8ce..b803c483 100644 --- a/app/src/main/res/values/strings.xml +++ b/app/src/main/res/values/strings.xml @@ -504,4 +504,19 @@ “电池优化”已忽略 已忽略电池优化 复制崩溃报告 + 重做 + 日历 + 相机 + 手机账号 / 通讯录 + 位置信息 + 麦克风 + 电话 + 通话记录 + 身体传感器 + 健身运动 + 短信 + 存储空间 + 贴吧 Lite 正在请求“%1$s”权限 + 授权后,将%1$s + 用于向当前访问的网页提供你的位置信息,以便该网页向你提供相关服务。 diff --git a/app/src/main/res/values/styles.xml b/app/src/main/res/values/styles.xml index 78981f46..7c39006e 100644 --- a/app/src/main/res/values/styles.xml +++ b/app/src/main/res/values/styles.xml @@ -143,6 +143,13 @@ @style/Animation.Bottom + +