pref: 优化权限申请流程

This commit is contained in:
HuanCheng65 2022-06-15 12:30:00 +08:00
parent db03c543e1
commit 8c07ef51ed
No known key found for this signature in database
GPG Key ID: E9031EF91A805148
19 changed files with 610 additions and 400 deletions

View File

@ -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"

View File

@ -92,6 +92,9 @@
<meta-data
android:name="use_miui_font"
android:value="true" />
<meta-data
android:name="ScopedStorage"
android:value="true" />
<activity
android:name=".activities.MainActivity"

View File

@ -253,26 +253,6 @@ open class MainActivity : BaseActivity(), NavigationBarView.OnItemSelectedListen
} else if (!AccountUtil.isLoggedIn(this)) {
navigationHelper.navigationByData(NavigationHelper.ACTION_LOGIN)
}
/*
handler.postDelayed(() -> {
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() {

View File

@ -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<Intent> = 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<PhotoInfoBean>) {
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()

View File

@ -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<List<String?>>) {
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<T> {

View File

@ -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<MyViewHolder>() {
private var fileList: MutableList<PhotoInfoBean> = ArrayList<PhotoInfoBean>()
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<MyViewHolder> {
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<PhotoInfoBean> 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<PhotoInfoBean> {
return fileList
}
public List<PhotoInfoBean> getFileList() {
return fileList;
fun setFileList(fileList: MutableList<PhotoInfoBean>) {
this.fileList = fileList
notifyDataSetChanged()
}
public void setFileList(List<PhotoInfoBean> 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<List<String>> 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<Intent>
}
class MatisseResultContract : ActivityResultContract<Intent, List<Uri>>() {
override fun createIntent(context: Context, input: Intent): Intent {
return input
}
override fun parseResult(resultCode: Int, intent: Intent?): List<Uri> {
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
}
}

View File

@ -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<CommonResponse>(

View File

@ -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)
}
}
}

View File

@ -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<String> 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<String> permissions) -> callback.invoke(origin, false, false))
.start();
return null;
},
() -> {
callback.invoke(origin, false, false);
return null;
}
);
})
.setOnDeniedCallback(isForever -> callback.invoke(origin, false, false))
.show();

View File

@ -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
}
}
}

View File

@ -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) {

View File

@ -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;

View File

@ -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<List<String>> action, Permission... permissions) {
askPermission(context, action, context.getString(R.string.tip_no_permission), permissions);
}
public static void askPermission(Context context, Action<List<String>> 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<List<String>> action, @Nullable String deniedTip, Permission... permissions) {
List<String> 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;
}
}
}

View File

@ -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<String>): List<String> {
val textList: MutableList<String> = 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<String>, all: Boolean) {
if (all) {
onGranted()
} else {
context.toastShort(deniedToast)
onDenied?.invoke()
}
dialog.dismiss()
}
override fun onDenied(permissions: List<String>, never: Boolean) {
context.toastShort(deniedToast)
onDenied?.invoke()
dialog.dismiss()
}
})
dialog.show()
}
}
data class Permission(val permissions: List<String>, val desc: String)
}

View File

@ -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);
}

View File

@ -0,0 +1,51 @@
<?xml version="1.0" encoding="utf-8"?>
<com.huanchengfly.tieba.post.widgets.theme.TintConstraintLayout xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:app="http://schemas.android.com/apk/res-auto"
xmlns:tools="http://schemas.android.com/tools"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_margin="16dp"
android:background="@drawable/bg_radius_16dp"
android:padding="16dp"
app:backgroundTint="@color/default_color_background">
<ImageView
android:id="@+id/request_permission_tip_dialog_icon"
android:layout_width="@dimen/size_avatar_small"
android:layout_height="@dimen/size_avatar_small"
app:layout_constraintBottom_toBottomOf="parent"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toTopOf="parent"
app:layout_constraintVertical_bias="0.0"
app:srcCompat="@mipmap/ic_launcher_round" />
<com.huanchengfly.tieba.post.widgets.theme.TintTextView
android:id="@+id/request_permission_tip_dialog_title"
android:layout_width="0dp"
android:layout_height="wrap_content"
android:layout_marginStart="8dp"
android:textStyle="bold"
app:layout_constraintBottom_toTopOf="@+id/request_permission_tip_dialog_message"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintHorizontal_bias="0.0"
app:layout_constraintStart_toEndOf="@+id/request_permission_tip_dialog_icon"
app:layout_constraintTop_toTopOf="@+id/request_permission_tip_dialog_icon"
app:layout_constraintVertical_chainStyle="packed"
app:tint="@color/default_color_text"
tools:text="贴吧 Lite 正在请求“存储空间”权限" />
<com.huanchengfly.tieba.post.widgets.theme.TintTextView
android:id="@+id/request_permission_tip_dialog_message"
android:layout_width="0dp"
android:layout_height="wrap_content"
android:layout_marginTop="4dp"
android:textSize="12sp"
app:layout_constraintBottom_toBottomOf="@+id/request_permission_tip_dialog_icon"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintHorizontal_bias="0.0"
app:layout_constraintStart_toStartOf="@+id/request_permission_tip_dialog_title"
app:layout_constraintTop_toBottomOf="@+id/request_permission_tip_dialog_title"
app:tint="@color/default_color_text_secondary"
tools:text="授权后,将用于保存图片" />
</com.huanchengfly.tieba.post.widgets.theme.TintConstraintLayout>

View File

@ -0,0 +1,9 @@
<?xml version="1.0" encoding="utf-8"?>
<menu xmlns:android="http://schemas.android.com/apk/res/android">
<item
android:id="@+id/action_undo"
android:title="@string/button_undo" />
<item
android:id="@+id/action_redo"
android:title="@string/button_redo" />
</menu>

View File

@ -504,4 +504,19 @@
<string name="summary_battery_optimization_ignored">“电池优化”已忽略</string>
<string name="toast_ignore_battery_optimization_already">已忽略电池优化</string>
<string name="button_copy_crash">复制崩溃报告</string>
<string name="button_redo">重做</string>
<string name="permission_name_calendar">日历</string>
<string name="permission_name_camera">相机</string>
<string name="permission_name_contacts">手机账号 / 通讯录</string>
<string name="permission_name_location">位置信息</string>
<string name="permission_name_microphone">麦克风</string>
<string name="permission_name_phone">电话</string>
<string name="permission_name_call_log">通话记录</string>
<string name="permission_name_sensors">身体传感器</string>
<string name="permission_name_activity_recognition">健身运动</string>
<string name="permission_name_sms">短信</string>
<string name="permission_name_storage">存储空间</string>
<string name="title_request_permission_tip_dialog">贴吧 Lite 正在请求“%1$s”权限</string>
<string name="message_request_permission_tip_dialog">授权后,将%1$s</string>
<string name="usage_webview_location_permission">用于向当前访问的网页提供你的位置信息,以便该网页向你提供相关服务。</string>
</resources>

View File

@ -143,6 +143,13 @@
<item name="android:windowAnimationStyle">@style/Animation.Bottom</item>
</style>
<style name="Dialog.RequestPermissionTip" parent="Theme.MaterialComponents.DayNight.Dialog">
<item name="android:windowFrame">@null</item>
<item name="android:windowNoTitle">true</item>
<item name="android:windowBackground">@android:color/transparent</item>
<item name="android:windowAnimationStyle">@style/Animation.Fade</item>
</style>
<style name="Widget.ScrollCollapsingToolbar" parent="android:Widget">
<item name="expandedTitleMargin">32dp</item>
<item name="statusBarScrim">?attr/colorPrimaryDark</item>