私聊改造:输入体验优化

This commit is contained in:
huangjian
2023-01-11 19:09:36 +08:00
parent 42aea570b3
commit 69f0d76330
34 changed files with 1455 additions and 1021 deletions

View File

@@ -69,8 +69,6 @@ dependencies {
annotationProcessor "com.github.bumptech.glide:compiler:${glideVersion}"
implementation project(':library')
}
repositories {
mavenCentral()

View File

@@ -1,5 +1,5 @@
<?xml version="1.0" encoding="utf-8"?>
<shape xmlns:android="http://schemas.android.com/apk/res/android">
<solid android:color="#F4F4FA" />
<corners android:radius="15dp" />
<solid android:color="#F2F2F7" />
<corners android:radius="25dp" />
</shape>

View File

@@ -6,13 +6,6 @@
android:orientation="vertical"
android:visibility="visible">
<View
android:id="@+id/top_divider_line"
android:layout_height="1px"
android:layout_width="match_parent"
android:background="#353548"
android:visibility="gone"/>
<androidx.viewpager.widget.ViewPager
android:id="@+id/scrPlugin"
android:layout_width="fill_parent"
@@ -30,8 +23,6 @@
android:gravity="center"
android:orientation="horizontal"/>
<View style="@style/horizontal_light_thin_divider"/>
<LinearLayout
android:layout_width="fill_parent"
android:layout_height="wrap_content"
@@ -50,6 +41,7 @@
android:layout_height="44dp"
android:orientation="horizontal">
</LinearLayout>
</HorizontalScrollView>
</LinearLayout>

View File

@@ -1,8 +1,8 @@
<?xml version="1.0" encoding="UTF-8"?>
<resources>
<color name="nim_app_color">#FFBC52</color>
<color name="nim_app_color_press">#FFBC52</color>
<color name="nim_app_color">#5BC8F8</color>
<color name="nim_app_color_press">#5BC8F8</color>
<color name="transparent">#00000000</color>
<color name="white">#FFFFFF</color>

View File

@@ -1,61 +0,0 @@
package com.netease.nim.uikit;
import android.app.Activity;
import android.graphics.Rect;
import android.view.View;
import android.view.ViewTreeObserver;
import android.widget.FrameLayout;
/**
* Created by huangmeng1 on 2018/3/9.
*/
public class AndroidBug5497Workaround {
// For more information, see https://code.google.com/p/android/issues/detail?id=5497
// To use this class, simply invoke assistActivity() on an Activity that already has its content view set.
public static void assistActivity (Activity activity) {
new AndroidBug5497Workaround(activity);
}
private View mChildOfContent;
private int usableHeightPrevious;
private FrameLayout.LayoutParams frameLayoutParams;
private AndroidBug5497Workaround(final Activity activity) {
FrameLayout content = (FrameLayout) activity.findViewById(android.R.id.content);
mChildOfContent = content.getChildAt(0);
mChildOfContent.getViewTreeObserver().addOnGlobalLayoutListener(new ViewTreeObserver.OnGlobalLayoutListener() {
public void onGlobalLayout() {
possiblyResizeChildOfContent(activity);
}
});
frameLayoutParams = (FrameLayout.LayoutParams) mChildOfContent.getLayoutParams();
}
private void possiblyResizeChildOfContent(Activity activity) {
int usableHeightNow = computeUsableHeight();
if (usableHeightNow != usableHeightPrevious) {
int usableHeightSansKeyboard = activity.getWindowManager().getDefaultDisplay().getHeight();
int heightDifference = usableHeightSansKeyboard - usableHeightNow;
if (heightDifference > (usableHeightSansKeyboard/4)) {
// keyboard probably just became visible
frameLayoutParams.height = usableHeightSansKeyboard - heightDifference;
} else {
// keyboard probably just became hidden
frameLayoutParams.height = usableHeightSansKeyboard;
}
mChildOfContent.requestLayout();
usableHeightPrevious = usableHeightNow;
}
}
private int computeUsableHeight() {
Rect r = new Rect();
mChildOfContent.getWindowVisibleDisplayFrame(r);
return (r.bottom - r.top);// 全屏模式下: return r.bottom
}
}

View File

@@ -307,16 +307,6 @@ public class NimUIKit {
NimUIKitImpl.startP2PSession(context, account);
}
/**
* 同 {@link NimUIKitImpl#startP2PSession(Context, String)},同时聊天界面打开后列表跳转至anchor位置
*
* @param context 上下文
* @param account 目标账号
* @param anchor 跳转到指定消息的位置不需要跳转填null
*/
public static void startP2PSession(Context context, String account, IMMessage anchor) {
NimUIKitImpl.startP2PSession(context, account, anchor);
}
/**
* 打开群聊界面,若开发者未设置 {@link NimUIKitImpl#setCommonTeamSessionCustomization(SessionCustomization)},

View File

@@ -1,82 +0,0 @@
package com.netease.nim.uikit.api.wrapper;
import android.content.Context;
import android.graphics.Bitmap;
import android.graphics.drawable.BitmapDrawable;
import android.graphics.drawable.Drawable;
import android.text.TextUtils;
import com.netease.nim.uikit.R;
import com.netease.nim.uikit.api.NimUIKit;
import com.netease.nim.uikit.business.team.helper.TeamHelper;
import com.netease.nimlib.sdk.msg.constant.SessionTypeEnum;
import com.netease.nimlib.sdk.team.model.Team;
import com.netease.nimlib.sdk.uinfo.UserInfoProvider;
import com.netease.nimlib.sdk.uinfo.model.UserInfo;
/**
* 初始化sdk 需要的用户信息提供者,现主要用于内置通知提醒获取昵称和头像
* <p>
* 注意不要与 IUserInfoProvider 混淆,后者是 UIKit 与 demo 之间的数据共享接口
* <p>
*/
public class NimUserInfoProvider implements UserInfoProvider {
private Context context;
public NimUserInfoProvider(Context context) {
this.context = context;
}
@Override
public UserInfo getUserInfo(String account) {
return NimUIKit.getUserInfoProvider().getUserInfo(account);
}
@Override
public Bitmap getAvatarForMessageNotifier(SessionTypeEnum sessionType, String sessionId) {
/*
* 注意:这里最好从缓存里拿,如果加载时间过长会导致通知栏延迟弹出!该函数在后台线程执行!
*/
Bitmap bm = null;
int defResId = R.drawable.nim_avatar_default;
if (SessionTypeEnum.P2P == sessionType) {
UserInfo user = getUserInfo(sessionId);
bm = (user != null) ? NimUIKit.getImageLoaderKit().getNotificationBitmapFromCache(user.getAvatar()) : null;
} else if (SessionTypeEnum.Team == sessionType) {
Team team = NimUIKit.getTeamProvider().getTeamById(sessionId);
bm = (team != null) ? NimUIKit.getImageLoaderKit().getNotificationBitmapFromCache(team.getIcon()) : null;
defResId = R.drawable.nim_avatar_group;
}
if (bm == null) {
Drawable drawable = context.getResources().getDrawable(defResId);
if (drawable instanceof BitmapDrawable) {
bm = ((BitmapDrawable) drawable).getBitmap();
}
}
return bm;
}
@Override
public String getDisplayNameForMessageNotifier(String account, String sessionId, SessionTypeEnum sessionType) {
String nick = null;
if (sessionType == SessionTypeEnum.P2P) {
nick = NimUIKit.getContactProvider().getAlias(account);
} else if (sessionType == SessionTypeEnum.Team) {
nick = NimUIKit.getContactProvider().getAlias(account);
if (TextUtils.isEmpty(nick)) {
nick = TeamHelper.getTeamNick(sessionId, account);
}
}
if (TextUtils.isEmpty(nick)) {
return null; // 返回null交给sdk处理。如果对方有设置nicksdk会显示nick
}
return nick;
}
}

View File

@@ -0,0 +1,44 @@
package com.netease.nim.uikit.business.session.actions;
import com.netease.nim.uikit.R;
import com.netease.nim.uikit.business.session.constant.RequestCode;
import com.netease.nim.uikit.business.session.event.ActiveEvent;
import com.netease.nim.uikit.common.media.picker.PickImageHelper;
import com.netease.nimlib.sdk.chatroom.ChatRoomMessageBuilder;
import com.netease.nimlib.sdk.msg.MessageBuilder;
import com.netease.nimlib.sdk.msg.constant.SessionTypeEnum;
import com.netease.nimlib.sdk.msg.model.IMMessage;
import org.greenrobot.eventbus.EventBus;
import java.io.File;
/**
* Created by hzxuwen on 2015/6/12.
*/
public class CameraAction extends PickImageAction {
public CameraAction() {
super(R.drawable.chat_icon_photo, R.string.input_panel_photo, true);
}
@Override
public void onClick() {
PickImageHelper.pickCamera(getActivity(), makeRequestCode(RequestCode.PICK_IMAGE), buildOption());
}
@Override
protected void onPicked(File file) {
IMMessage message;
if (getContainer() != null && getContainer().sessionType == SessionTypeEnum.ChatRoom) {
message = ChatRoomMessageBuilder.createChatRoomImageMessage(getAccount(), file, file.getName());
} else {
message = MessageBuilder.createImageMessage(getAccount(), getSessionType(), file, file.getName());
}
sendMessage(message);
EventBus.getDefault().post(new ActiveEvent());
}
}

View File

@@ -0,0 +1,43 @@
package com.netease.nim.uikit.business.session.actions;
import com.netease.nim.uikit.R;
import com.netease.nim.uikit.business.session.constant.RequestCode;
import com.netease.nim.uikit.business.session.event.ActiveEvent;
import com.netease.nim.uikit.common.media.picker.PickImageHelper;
import com.netease.nimlib.sdk.chatroom.ChatRoomMessageBuilder;
import com.netease.nimlib.sdk.msg.MessageBuilder;
import com.netease.nimlib.sdk.msg.constant.SessionTypeEnum;
import com.netease.nimlib.sdk.msg.model.IMMessage;
import org.greenrobot.eventbus.EventBus;
import java.io.File;
/**
* Created by hzxuwen on 2015/6/12.
*/
public class PhotoAction extends PickImageAction {
public PhotoAction() {
super(R.drawable.chat_icon_photo, R.string.input_panel_photo, true);
}
@Override
public void onClick() {
PickImageHelper.pickPhoto(getActivity(), makeRequestCode(RequestCode.PICK_IMAGE), buildOption());
}
@Override
protected void onPicked(File file) {
IMMessage message;
if (getContainer() != null && getContainer().sessionType == SessionTypeEnum.ChatRoom) {
message = ChatRoomMessageBuilder.createChatRoomImageMessage(getAccount(), file, file.getName());
} else {
message = MessageBuilder.createImageMessage(getAccount(), getSessionType(), file, file.getName());
}
sendMessage(message);
EventBus.getDefault().post(new ActiveEvent());
}
}

View File

@@ -25,26 +25,23 @@ import java.io.File;
*/
public abstract class PickImageAction extends BaseAction {
private static final int PICK_IMAGE_COUNT = 9;
private static final int PORTRAIT_IMAGE_WIDTH = 720;
public static final String MIME_JPEG = "image/jpeg";
public static final String JPG = ".jpg";
private static final int PICK_IMAGE_COUNT = 9;
private static final int PORTRAIT_IMAGE_WIDTH = 720;
private boolean multiSelect;
private boolean crop = false;
protected abstract void onPicked(File file);
protected PickImageAction(int iconResId, int titleId, boolean multiSelect) {
super(iconResId, titleId);
this.multiSelect = multiSelect;
}
protected abstract void onPicked(File file);
@Override
public void onClick() {
int requestCode = makeRequestCode(RequestCode.PICK_IMAGE);
showSelector(getTitleId(), requestCode, multiSelect, tempFile());
PickImageHelper.pickImage(getActivity(), makeRequestCode(RequestCode.PICK_IMAGE), buildOption());
}
private String tempFile() {
@@ -52,20 +49,16 @@ public abstract class PickImageAction extends BaseAction {
return StorageUtil.getWritePath(filename, StorageType.TYPE_TEMP);
}
/**
* 打开图片选择器
*/
private void showSelector(int titleId, final int requestCode, final boolean multiSelect, final String outPath) {
public PickImageHelper.PickImageOption buildOption() {
PickImageHelper.PickImageOption option = new PickImageHelper.PickImageOption();
option.titleResId = titleId;
option.titleResId = getTitleId();
option.multiSelect = multiSelect;
option.multiSelectMaxCount = PICK_IMAGE_COUNT;
option.crop = crop;
option.cropOutputImageWidth = PORTRAIT_IMAGE_WIDTH;
option.cropOutputImageHeight = PORTRAIT_IMAGE_WIDTH;
option.outputPath = outPath;
PickImageHelper.pickImage(getActivity(), requestCode, option);
option.outputPath = tempFile();
return option;
}
@Override

View File

@@ -1,11 +1,8 @@
package com.netease.nim.uikit.business.session.emoji;
import android.annotation.TargetApi;
import android.content.Context;
import android.graphics.Bitmap;
import android.os.Build;
import android.os.Handler;
import androidx.viewpager.widget.ViewPager;
import android.util.AttributeSet;
import android.view.LayoutInflater;
import android.view.View;
@@ -15,6 +12,8 @@ import android.widget.HorizontalScrollView;
import android.widget.ImageView;
import android.widget.LinearLayout;
import androidx.viewpager.widget.ViewPager;
import com.netease.nim.uikit.R;
import com.netease.nim.uikit.common.ui.imageview.CheckedImageButton;
import com.netease.nim.uikit.common.util.log.LogUtil;
@@ -47,9 +46,14 @@ public class EmoticonPickerView extends LinearLayout implements IEmoticonCategor
private HorizontalScrollView scrollView;
private LinearLayout tabView;
// 添加各个tab按钮
OnClickListener tabCheckListener = new OnClickListener() {
@Override
public void onClick(View v) {
onEmoticonBtnChecked(v.getId());
}
};
private int categoryIndex;
private Handler uiHandler;
public EmoticonPickerView(Context context) {
@@ -64,17 +68,14 @@ public class EmoticonPickerView extends LinearLayout implements IEmoticonCategor
init(context);
}
@TargetApi(Build.VERSION_CODES.HONEYCOMB)
public EmoticonPickerView(Context context, AttributeSet attrs, int defStyle) {
super(context, attrs, defStyle);
init(context);
}
private void init(Context context) {
this.context = context;
this.uiHandler = new Handler(context.getMainLooper());
LayoutInflater inflater = (LayoutInflater) context.getSystemService(Context.LAYOUT_INFLATER_SERVICE);
inflater.inflate(R.layout.nim_emoji_layout, this);
}
@@ -91,41 +92,26 @@ public class EmoticonPickerView extends LinearLayout implements IEmoticonCategor
}
public void show(IEmoticonSelectedListener listener) {
setListener(listener);
this.listener = listener;
if (loaded)
return;
loadStickers();
loaded = true;
show();
}
public void setListener(IEmoticonSelectedListener listener) {
if (listener != null) {
this.listener = listener;
if (!withSticker) {
showEmojiView();
} else {
LogUtil.i("sticker", "listener is null");
onEmoticonBtnChecked(0);
setSelectedVisible(0);
}
}
protected void setupEmojView() {
currentEmojiPage = (ViewPager) findViewById(R.id.scrPlugin);
pageNumberLayout = (LinearLayout) findViewById(R.id.layout_scr_bottom);
tabView = (LinearLayout) findViewById(R.id.emoj_tab_view);
scrollView = (HorizontalScrollView) findViewById(R.id.emoj_tab_view_container);
findViewById(R.id.top_divider_line).setVisibility(View.VISIBLE);
currentEmojiPage = findViewById(R.id.scrPlugin);
pageNumberLayout = findViewById(R.id.layout_scr_bottom);
tabView = findViewById(R.id.emoj_tab_view);
scrollView = findViewById(R.id.emoj_tab_view_container);
}
// 添加各个tab按钮
OnClickListener tabCheckListener = new OnClickListener() {
@Override
public void onClick(View v) {
onEmoticonBtnChecked(v.getId());
}
};
private void loadStickers() {
if (!withSticker) {
scrollView.setVisibility(View.GONE);
@@ -151,7 +137,6 @@ public class EmoticonPickerView extends LinearLayout implements IEmoticonCategor
}
}
private CheckedImageButton addEmoticonTabBtn(int index, OnClickListener listener) {
CheckedImageButton emotBtn = new CheckedImageButton(context);
emotBtn.setNormalBkResId(R.drawable.nim_sticker_button_background_normal_layer_list);
@@ -232,19 +217,6 @@ public class EmoticonPickerView extends LinearLayout implements IEmoticonCategor
gifView.showEmojis();
}
private void show() {
if (listener == null) {
LogUtil.i("sticker", "show picker view when listener is null");
}
if (!withSticker) {
showEmojiView();
} else {
onEmoticonBtnChecked(0);
setSelectedVisible(0);
}
}
private void setSelectedVisible(final int index) {
final Runnable runnable = new Runnable() {
@Override

View File

@@ -18,6 +18,60 @@ import org.greenrobot.eventbus.EventBus;
*/
public class PickImageHelper {
/**
* 打开图片选择器
*/
public static void pickImage(final Context context, final int requestCode, final PickImageOption option) {
if (context == null) {
return;
}
CustomAlertDialog dialog = new CustomAlertDialog(context);
dialog.setTitle(option.titleResId);
dialog.addItem(context.getString(R.string.input_panel_take), () -> pickCamera(context, requestCode, option));
dialog.addItem(context.getString(R.string.choose_from_photo_album), () -> pickPhoto(context, requestCode, option));
dialog.show();
}
/**
* 打开图片选择器
*/
public static void pickPhoto(final Context context, final int requestCode, final PickImageOption option) {
NimImageActionEvent event = new NimImageActionEvent();
event.setSuccess(o -> {
int from = PickImageActivity.FROM_LOCAL;
if (!option.crop) {
PickImageActivity.start((Activity) context, requestCode, from, option.outputPath, option.multiSelect,
option.multiSelectMaxCount, true, false, 0, 0);
} else {
PickImageActivity.start((Activity) context, requestCode, from, option.outputPath, false, 1,
false, true, option.cropOutputImageWidth, option.cropOutputImageHeight);
}
});
EventBus.getDefault().post(event);
}
/**
* 打开相机发送图片
*/
public static void pickCamera(final Context context, final int requestCode, final PickImageOption option) {
NimImageActionEvent event = new NimImageActionEvent();
event.setSuccess(o -> {
int from = PickImageActivity.FROM_CAMERA;
if (!option.crop) {
PickImageActivity.start((Activity) context, requestCode, from, option.outputPath, option.multiSelect, 1,
true, false, 0, 0);
} else {
PickImageActivity.start((Activity) context, requestCode, from, option.outputPath, false, 1,
false, true, option.cropOutputImageWidth, option.cropOutputImageHeight);
}
});
EventBus.getDefault().post(event);
}
public static class PickImageOption {
/**
* 图片选择器标题
@@ -55,53 +109,5 @@ public class PickImageHelper {
public String outputPath = StorageUtil.getWritePath(StringUtil.get32UUID() + ".jpg", StorageType.TYPE_TEMP);
}
/**
* 打开图片选择器
*/
public static void pickImage(final Context context, final int requestCode, final PickImageOption option) {
if (context == null) {
return;
}
CustomAlertDialog dialog = new CustomAlertDialog(context);
dialog.setTitle(option.titleResId);
dialog.addItem(context.getString(R.string.input_panel_take), new CustomAlertDialog.onSeparateItemClickListener() {
@Override
public void onClick() {
NimImageActionEvent event = new NimImageActionEvent();
event.setSuccess(o -> {
int from = PickImageActivity.FROM_CAMERA;
if (!option.crop) {
PickImageActivity.start((Activity) context, requestCode, from, option.outputPath, option.multiSelect, 1,
true, false, 0, 0);
} else {
PickImageActivity.start((Activity) context, requestCode, from, option.outputPath, false, 1,
false, true, option.cropOutputImageWidth, option.cropOutputImageHeight);
}
});
EventBus.getDefault().post(event);
}
});
dialog.addItem(context.getString(R.string.choose_from_photo_album), new CustomAlertDialog.onSeparateItemClickListener() {
@Override
public void onClick() {
NimImageActionEvent event = new NimImageActionEvent();
event.setSuccess(o -> {
int from = PickImageActivity.FROM_LOCAL;
if (!option.crop) {
PickImageActivity.start((Activity) context, requestCode, from, option.outputPath, option.multiSelect,
option.multiSelectMaxCount, true, false, 0, 0);
} else {
PickImageActivity.start((Activity) context, requestCode, from, option.outputPath, false, 1,
false, true, option.cropOutputImageWidth, option.cropOutputImageHeight);
}
});
EventBus.getDefault().post(event);
}
});
dialog.show();
}
}

View File

@@ -1,36 +0,0 @@
package com.netease.nim.uikit.impl.preference;
import android.content.Context;
import android.content.SharedPreferences;
import com.netease.nim.uikit.api.NimUIKit;
/**
* Created by hzxuwen on 2015/10/21.
*/
public class UserPreferences {
private final static String KEY_EARPHONE_MODE = "KEY_EARPHONE_MODE";
public static void setEarPhoneModeEnable(boolean on) {
saveBoolean(KEY_EARPHONE_MODE, on);
}
public static boolean isEarPhoneModeEnable() {
return getBoolean(KEY_EARPHONE_MODE, true);
}
private static boolean getBoolean(String key, boolean value) {
return getSharedPreferences().getBoolean(key, value);
}
private static void saveBoolean(String key, boolean value) {
SharedPreferences.Editor editor = getSharedPreferences().edit();
editor.putBoolean(key, value);
editor.apply();
}
private static SharedPreferences getSharedPreferences() {
return NimUIKit.getContext().getSharedPreferences("UIKit." + NimUIKit.getAccount(), Context.MODE_PRIVATE);
}
}

View File

@@ -1,204 +0,0 @@
package com.netease.nim.uikit.support.permission;
import android.annotation.TargetApi;
import android.app.Activity;
import android.content.pm.PackageManager;
import android.os.Build;
import androidx.fragment.app.Fragment;
import android.util.Log;
import java.util.ArrayList;
import java.util.List;
public class BaseMPermission {
private static final String TAG = "MPermission";
public enum MPermissionResultEnum {
GRANTED, DENIED, DENIED_NEVER_ASK_AGAIN
}
static boolean isOverMarshmallow() {
return Build.VERSION.SDK_INT >= Build.VERSION_CODES.M;
}
static Activity getActivity(Object object) {
if (object instanceof Fragment) {
return ((Fragment) object).getActivity();
} else if (object instanceof Activity) {
return (Activity) object;
}
return null;
}
/**
* 获取权限请求结果
*/
public static List<MPermissionResultEnum> getPermissionResult(Activity activity, String[] permissions) {
return findPermissionResult(activity, permissions);
}
public static List<MPermissionResultEnum> getPermissionResult(Fragment fragment, String[] permissions) {
return findPermissionResult(fragment.getActivity(), permissions);
}
@TargetApi(value = Build.VERSION_CODES.M)
private static List<MPermissionResultEnum> findPermissionResult(Activity activity, String... permissions) {
boolean overM = isOverMarshmallow();
List<MPermissionResultEnum> result = new ArrayList<>();
for (String p : permissions) {
if (overM) {
if (activity.checkSelfPermission(p) == PackageManager.PERMISSION_GRANTED) {
result.add(MPermissionResultEnum.GRANTED);
} else {
if (!activity.shouldShowRequestPermissionRationale(p)) {
result.add(MPermissionResultEnum.DENIED_NEVER_ASK_AGAIN);
} else {
result.add(MPermissionResultEnum.DENIED);
}
}
} else {
result.add(MPermissionResultEnum.GRANTED);
}
}
return result;
}
/**
* 获取所有被未被授权的权限
*/
public static List<String> getDeniedPermissions(Activity activity, String[] permissions) {
return findDeniedPermissions(activity, permissions);
}
public static List<String> getDeniedPermissions(Fragment fragment, String[] permissions) {
return findDeniedPermissions(fragment.getActivity(), permissions);
}
@TargetApi(value = Build.VERSION_CODES.M)
static List<String> findDeniedPermissions(Activity activity, String... permissions) {
if (!isOverMarshmallow()) {
return null;
}
List<String> denyPermissions = new ArrayList<>();
for (String value : permissions) {
if (activity.checkSelfPermission(value) != PackageManager.PERMISSION_GRANTED) {
denyPermissions.add(value);
}
}
return denyPermissions;
}
/**
* 获取被拒绝且勾选不再询问的权限
* 请在请求权限结果回调中使用,因为从未请求过的权限也会被认为是该结果集
*/
public static List<String> getNeverAskAgainPermissions(Activity activity, String[] permissions) {
return findNeverAskAgainPermissions(activity, permissions);
}
public static List<String> getNeverAskAgainPermissions(Fragment fragment, String[] permissions) {
return findNeverAskAgainPermissions(fragment.getActivity(), permissions);
}
@TargetApi(value = Build.VERSION_CODES.M)
private static List<String> findNeverAskAgainPermissions(Activity activity, String... permissions) {
if (!isOverMarshmallow()) {
return null;
}
List<String> neverAskAgainPermission = new ArrayList<>();
for (String value : permissions) {
if (activity.checkSelfPermission(value) != PackageManager.PERMISSION_GRANTED &&
!activity.shouldShowRequestPermissionRationale(value)) {
// 拒绝&不要需要解释了(用户勾选了不再询问)
// 坑爹第一次不做任何设置返回值也是false。建议在权限授权结果里判断
neverAskAgainPermission.add(value);
}
}
return neverAskAgainPermission;
}
@TargetApi(value = Build.VERSION_CODES.M)
static boolean hasNeverAskAgainPermission(Activity activity, List<String> permission) {
if (!isOverMarshmallow()) {
return false;
}
for (String value : permission) {
if (activity.checkSelfPermission(value) != PackageManager.PERMISSION_GRANTED &&
!activity.shouldShowRequestPermissionRationale(value)) {
return true;
}
}
return false;
}
/**
* 获取被拒绝但没有勾选不再询问的权限(可以继续申请,会继续弹框)
*/
public static List<String> getDeniedPermissionsWithoutNeverAskAgain(Activity activity, String[] permissions) {
return findDeniedPermissionWithoutNeverAskAgain(activity, permissions);
}
public static List<String> getDeniedPermissionsWithoutNeverAskAgain(Fragment fragment, String[] permissions) {
return findDeniedPermissionWithoutNeverAskAgain(fragment.getActivity(), permissions);
}
@TargetApi(value = Build.VERSION_CODES.M)
private static List<String> findDeniedPermissionWithoutNeverAskAgain(Activity activity, String... permission) {
if (!isOverMarshmallow()) {
return null;
}
List<String> denyPermissions = new ArrayList<>();
for (String value : permission) {
if (activity.checkSelfPermission(value) != PackageManager.PERMISSION_GRANTED &&
activity.shouldShowRequestPermissionRationale(value)) {
denyPermissions.add(value); // 上次申请被用户拒绝了
}
}
return denyPermissions;
}
/**
* Log专用
*/
public static void printMPermissionResult(boolean preRequest, Activity activity, String[] permissions) {
Log.i(TAG, "----- MPermission result " + (preRequest ? "before" : "after") + " request");
List<BaseMPermission.MPermissionResultEnum> result = getPermissionResult(activity, permissions);
int i = 0;
for (BaseMPermission.MPermissionResultEnum p : result) {
Log.i(TAG, "* MPermission=" + permissions[i++] + ", result=" + p);
}
}
static String toString(List<String> permission) {
if (permission == null || permission.isEmpty()) {
return "";
}
return toString(permission.toArray(new String[permission.size()]));
}
private static String toString(String[] permission) {
if (permission == null || permission.length <= 0) {
return "";
}
StringBuilder sb = new StringBuilder();
for (String p : permission) {
sb.append(p.replaceFirst("android.permission.", ""));
sb.append(",");
}
sb.deleteCharAt(sb.length() - 1);
return sb.toString();
}
}

View File

@@ -1,170 +0,0 @@
package com.netease.nim.uikit.support.permission;
import android.annotation.TargetApi;
import android.app.Activity;
import android.content.pm.PackageManager;
import android.os.Build;
import androidx.annotation.NonNull;
import androidx.fragment.app.Fragment;
import com.netease.nim.uikit.support.permission.annotation.OnMPermissionDenied;
import com.netease.nim.uikit.support.permission.annotation.OnMPermissionGranted;
import com.netease.nim.uikit.support.permission.annotation.OnMPermissionNeverAskAgain;
import java.lang.annotation.Annotation;
import java.lang.reflect.InvocationTargetException;
import java.lang.reflect.Method;
import java.util.ArrayList;
import java.util.List;
public class MPermission extends BaseMPermission {
private int requestCode;
private String[] permissions;
private Object object; // activity or fragment
private MPermission(Object object) {
this.object = object;
}
public static MPermission with(Activity activity) {
return new MPermission(activity);
}
public static MPermission with(Fragment fragment) {
return new MPermission(fragment);
}
public MPermission setRequestCode(int requestCode) {
this.requestCode = requestCode;
return this;
}
public MPermission permissions(String... permissions) {
this.permissions = permissions;
return this;
}
/**
* ********************* request *********************
*/
@TargetApi(value = Build.VERSION_CODES.M)
public void request() {
doRequestPermissions(object, requestCode, permissions);
}
@TargetApi(value = Build.VERSION_CODES.M)
private static void doRequestPermissions(Object object, int requestCode, String[] permissions) {
if (!isOverMarshmallow()) {
doExecuteSuccess(object, requestCode);
return;
}
List<String> deniedPermissions = findDeniedPermissions(getActivity(object), permissions);
if (deniedPermissions != null && deniedPermissions.size() > 0) {
if (object instanceof Activity) {
((Activity) object).requestPermissions(deniedPermissions.toArray(new String[deniedPermissions.size()]), requestCode);
} else if (object instanceof Fragment) {
((Fragment) object).requestPermissions(deniedPermissions.toArray(new String[deniedPermissions.size()]), requestCode);
} else {
throw new IllegalArgumentException(object.getClass().getName() + " is not supported");
}
} else {
doExecuteSuccess(object, requestCode);
}
}
/**
* ********************* on result *********************
*/
public static void onRequestPermissionsResult(Activity activity, int requestCode, @NonNull String[] permissions, @NonNull int[] grantResults) {
dispatchResult(activity, requestCode, permissions, grantResults);
}
public static void onRequestPermissionsResult(Fragment fragment, int requestCode, @NonNull String[] permissions, @NonNull int[] grantResults) {
dispatchResult(fragment, requestCode, permissions, grantResults);
}
private static void dispatchResult(Object obj, int requestCode, @NonNull String[] permissions, @NonNull int[] grantResults) {
List<String> deniedPermissions = new ArrayList<>();
for (int i = 0; i < grantResults.length; i++) {
if (grantResults[i] != PackageManager.PERMISSION_GRANTED) {
deniedPermissions.add(permissions[i]);
}
}
if (deniedPermissions.size() > 0) {
if (hasNeverAskAgainPermission(getActivity(obj), deniedPermissions)) {
doExecuteFailAsNeverAskAgain(obj, requestCode);
} else {
doExecuteFail(obj, requestCode);
}
} else {
doExecuteSuccess(obj, requestCode);
}
}
/**
* ********************* reflect execute result *********************
*/
private static void doExecuteSuccess(Object activity, int requestCode) {
executeMethod(activity, findMethodWithRequestCode(activity.getClass(), OnMPermissionGranted.class, requestCode));
}
private static void doExecuteFail(Object activity, int requestCode) {
executeMethod(activity, findMethodWithRequestCode(activity.getClass(), OnMPermissionDenied.class, requestCode));
}
private static void doExecuteFailAsNeverAskAgain(Object activity, int requestCode) {
executeMethod(activity, findMethodWithRequestCode(activity.getClass(), OnMPermissionNeverAskAgain.class, requestCode));
}
private static <A extends Annotation> Method findMethodWithRequestCode(Class clazz, Class<A> annotation, int
requestCode) {
for (Method method : clazz.getDeclaredMethods()) {
if (method.getAnnotation(annotation) != null &&
isEqualRequestCodeFromAnnotation(method, annotation, requestCode)) {
return method;
}
}
return null;
}
private static boolean isEqualRequestCodeFromAnnotation(Method m, Class clazz, int requestCode) {
if (clazz.equals(OnMPermissionDenied.class)) {
return requestCode == m.getAnnotation(OnMPermissionDenied.class).value();
} else if (clazz.equals(OnMPermissionGranted.class)) {
return requestCode == m.getAnnotation(OnMPermissionGranted.class).value();
} else if (clazz.equals(OnMPermissionNeverAskAgain.class)) {
return requestCode == m.getAnnotation(OnMPermissionNeverAskAgain.class).value();
} else {
return false;
}
}
/**
* ********************* reflect execute method *********************
*/
private static void executeMethod(Object activity, Method executeMethod) {
executeMethodWithParam(activity, executeMethod, new Object[]{});
}
private static void executeMethodWithParam(Object activity, Method executeMethod, Object... args) {
if (executeMethod != null) {
try {
if (!executeMethod.isAccessible()) {
executeMethod.setAccessible(true);
}
executeMethod.invoke(activity, args);
} catch (InvocationTargetException e) {
e.printStackTrace();
} catch (IllegalAccessException e) {
e.printStackTrace();
}
}
}
}

View File

@@ -1,15 +0,0 @@
package com.netease.nim.uikit.support.permission.annotation;
import java.lang.annotation.ElementType;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.lang.annotation.Target;
/**
* register a method invoked when permission requests are denied without check never ask again.
*/
@Retention(RetentionPolicy.RUNTIME)
@Target(ElementType.METHOD)
public @interface OnMPermissionDenied {
int value();
}

View File

@@ -1,15 +0,0 @@
package com.netease.nim.uikit.support.permission.annotation;
import java.lang.annotation.ElementType;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.lang.annotation.Target;
/**
* register a method invoked when permission requests are succeeded.
*/
@Retention(RetentionPolicy.RUNTIME)
@Target(ElementType.METHOD)
public @interface OnMPermissionGranted {
int value();
}

View File

@@ -1,15 +0,0 @@
package com.netease.nim.uikit.support.permission.annotation;
import java.lang.annotation.ElementType;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.lang.annotation.Target;
/**
* register some methods handling the user's choice to permanently deny permissions checking never ask again.
*/
@Retention(RetentionPolicy.RUNTIME)
@Target(ElementType.METHOD)
public @interface OnMPermissionNeverAskAgain {
int value();
}