feat:删除无关紧要的代码
This commit is contained in:
@@ -3,19 +3,16 @@ apply plugin: 'kotlin-android'
|
|||||||
apply plugin: 'com.tencent.vasdolly'
|
apply plugin: 'com.tencent.vasdolly'
|
||||||
apply from: '../project.gradle'
|
apply from: '../project.gradle'
|
||||||
|
|
||||||
def onlyArm64 = Boolean.parseBoolean(only_arm64)
|
|
||||||
|
|
||||||
android {
|
android {
|
||||||
compileSdkVersion COMPILE_SDK_VERSION.toInteger()
|
compileSdkVersion COMPILE_SDK_VERSION.toInteger()
|
||||||
|
|
||||||
defaultConfig {
|
defaultConfig {
|
||||||
applicationId isolationMode ? 'com.aaa.bbb' : 'com.example.live'
|
applicationId isolationMode ? 'com.example.gogo' : 'com.example.live'
|
||||||
minSdkVersion MIN_SDK_VERSION.toInteger()
|
minSdkVersion MIN_SDK_VERSION.toInteger()
|
||||||
targetSdkVersion TARGET_SDK_VERSION.toInteger()
|
targetSdkVersion TARGET_SDK_VERSION.toInteger()
|
||||||
versionCode Integer.valueOf(version_code)
|
versionCode Integer.valueOf(version_code)
|
||||||
versionName version_name
|
versionName version_name
|
||||||
|
|
||||||
testInstrumentationRunner "androidx.test.runner.AndroidJUnitRunner"
|
|
||||||
multiDexEnabled true
|
multiDexEnabled true
|
||||||
|
|
||||||
flavorDimensions 'default'
|
flavorDimensions 'default'
|
||||||
@@ -214,8 +211,6 @@ dependencies {
|
|||||||
implementation 'androidx.legacy:legacy-support-v4:1.0.0'
|
implementation 'androidx.legacy:legacy-support-v4:1.0.0'
|
||||||
implementation 'androidx.lifecycle:lifecycle-livedata-ktx:2.6.2'
|
implementation 'androidx.lifecycle:lifecycle-livedata-ktx:2.6.2'
|
||||||
implementation 'androidx.lifecycle:lifecycle-viewmodel-ktx:2.6.2'
|
implementation 'androidx.lifecycle:lifecycle-viewmodel-ktx:2.6.2'
|
||||||
testImplementation 'junit:junit:4.13.2'
|
|
||||||
androidTestImplementation 'androidx.test.espresso:espresso-core:3.4.0'
|
|
||||||
|
|
||||||
api 'androidx.multidex:multidex:2.0.1'
|
api 'androidx.multidex:multidex:2.0.1'
|
||||||
|
|
||||||
@@ -310,8 +305,7 @@ channel {
|
|||||||
//多渠道包的输出目录,默认为new File(project.buildDir,"channel")
|
//多渠道包的输出目录,默认为new File(project.buildDir,"channel")
|
||||||
outputDir = new File(project.buildDir, "channelapk")
|
outputDir = new File(project.buildDir, "channelapk")
|
||||||
//多渠道包的命名规则,默认为:${appName}-${versionName}-${versionCode}-${flavorName}-${buildType}-${buildTime}
|
//多渠道包的命名规则,默认为:${appName}-${versionName}-${versionCode}-${flavorName}-${buildType}-${buildTime}
|
||||||
def only64 = onlyArm64 ? "-only64" : ""
|
apkNameFormat = 'habu-${buildType}-${flavorName}-v${versionName}-${buildTime}'
|
||||||
apkNameFormat = 'habu-${buildType}only64-${flavorName}-v${versionName}-${buildTime}'.replace("only64", only64)
|
|
||||||
//快速模式:生成渠道包时不进行校验(速度可以提升10倍以上,默认为false)
|
//快速模式:生成渠道包时不进行校验(速度可以提升10倍以上,默认为false)
|
||||||
fastMode = false
|
fastMode = false
|
||||||
//buildTime的时间格式,默认格式:yyyyMMdd-HHmmss
|
//buildTime的时间格式,默认格式:yyyyMMdd-HHmmss
|
||||||
|
@@ -1,28 +0,0 @@
|
|||||||
package com.chwl.app;
|
|
||||||
|
|
||||||
import static org.junit.Assert.assertEquals;
|
|
||||||
|
|
||||||
import android.content.Context;
|
|
||||||
|
|
||||||
import androidx.test.ext.junit.runners.AndroidJUnit4;
|
|
||||||
import androidx.test.platform.app.InstrumentationRegistry;
|
|
||||||
|
|
||||||
import org.junit.Test;
|
|
||||||
import org.junit.runner.RunWith;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Instrumented test, which will execute on an Android device.
|
|
||||||
*
|
|
||||||
* @see <a href="http://d.android.com/tools/testing">Testing documentation</a>
|
|
||||||
*/
|
|
||||||
@RunWith(AndroidJUnit4.class)
|
|
||||||
public class ExampleInstrumentedTest {
|
|
||||||
@Test
|
|
||||||
public void useAppContext() throws Exception {
|
|
||||||
// Context of the app under test.
|
|
||||||
Context appContext = InstrumentationRegistry.getTargetContext();
|
|
||||||
|
|
||||||
assertEquals("com.chwl.app_android_client", appContext.getPackageName());
|
|
||||||
}
|
|
||||||
|
|
||||||
}
|
|
@@ -1,253 +0,0 @@
|
|||||||
package com.chwl.app;
|
|
||||||
|
|
||||||
import static org.junit.Assert.assertEquals;
|
|
||||||
|
|
||||||
import com.chwl.library.utils.ResUtil;
|
|
||||||
|
|
||||||
import org.junit.Test;
|
|
||||||
|
|
||||||
import java.io.UnsupportedEncodingException;
|
|
||||||
import java.util.Arrays;
|
|
||||||
import java.util.HashSet;
|
|
||||||
import java.util.Iterator;
|
|
||||||
import java.util.List;
|
|
||||||
import java.util.Set;
|
|
||||||
import java.util.regex.Matcher;
|
|
||||||
import java.util.regex.Pattern;
|
|
||||||
|
|
||||||
import io.reactivex.Single;
|
|
||||||
import io.reactivex.SingleEmitter;
|
|
||||||
import io.reactivex.SingleObserver;
|
|
||||||
import io.reactivex.SingleOnSubscribe;
|
|
||||||
import io.reactivex.disposables.Disposable;
|
|
||||||
import io.reactivex.functions.Action;
|
|
||||||
import io.reactivex.functions.Consumer;
|
|
||||||
import io.reactivex.schedulers.Schedulers;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Example local unit test, which will execute on the development machine (host).
|
|
||||||
*
|
|
||||||
* @see <a href="http://d.android.com/tools/testing">Testing documentation</a>
|
|
||||||
*/
|
|
||||||
public class ExampleUnitTest {
|
|
||||||
|
|
||||||
@Test
|
|
||||||
public void addition_isCorrect() throws Exception {
|
|
||||||
assertEquals(4, 2 + 2);
|
|
||||||
}
|
|
||||||
|
|
||||||
@Test
|
|
||||||
public void test_do_on() throws Exception {
|
|
||||||
Single.create(new SingleOnSubscribe<String>() {
|
|
||||||
@Override
|
|
||||||
public void subscribe(SingleEmitter<String> e) throws Exception {
|
|
||||||
|
|
||||||
System.out.println(ResUtil.getString(R.string.yizhuan_erban_exampleunittest_01) + " thread:" +Thread.currentThread().getName());
|
|
||||||
e.onSuccess(ResUtil.getString(R.string.yizhuan_erban_exampleunittest_02));
|
|
||||||
}
|
|
||||||
})
|
|
||||||
.subscribeOn(Schedulers.io())
|
|
||||||
.observeOn(Schedulers.newThread())
|
|
||||||
.doOnSubscribe(new Consumer<Disposable>() {
|
|
||||||
@Override
|
|
||||||
public void accept(Disposable disposable) throws Exception {
|
|
||||||
System.out.println("doOnSubscribe:"+ " thread:" +Thread.currentThread().getName());
|
|
||||||
}
|
|
||||||
})
|
|
||||||
.doAfterTerminate(new Action() {
|
|
||||||
@Override
|
|
||||||
public void run() throws Exception {
|
|
||||||
System.out.println("doAfterTerminate:"+ " thread:" +Thread.currentThread().getName());
|
|
||||||
}
|
|
||||||
})
|
|
||||||
.subscribe(new SingleObserver<String>() {
|
|
||||||
@Override
|
|
||||||
public void onSubscribe(Disposable d) {
|
|
||||||
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public void onSuccess(String s) {
|
|
||||||
System.out.println(ResUtil.getString(R.string.yizhuan_erban_exampleunittest_03)+s + " thread:" +Thread.currentThread().getName());
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public void onError(Throwable e) {
|
|
||||||
|
|
||||||
}
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
@Test
|
|
||||||
public void test_list_to_string() throws Exception {
|
|
||||||
String[] str = new String[]{"1","2","3"};
|
|
||||||
List<String> stringList = Arrays.asList(str);
|
|
||||||
System.out.println(stringList.toString());
|
|
||||||
|
|
||||||
|
|
||||||
System.out.println(String.format("%1$02d:%2$02d", 3, 5));
|
|
||||||
}
|
|
||||||
|
|
||||||
@Test
|
|
||||||
public void test_set() {
|
|
||||||
Set<String> stringSet = new HashSet<>();
|
|
||||||
stringSet.add("1");
|
|
||||||
stringSet.add("2");
|
|
||||||
stringSet.add("1");
|
|
||||||
stringSet.add("2");
|
|
||||||
stringSet.add("3");
|
|
||||||
stringSet.add("4");
|
|
||||||
|
|
||||||
System.out.println("stringSet.size():" + stringSet.size());
|
|
||||||
|
|
||||||
|
|
||||||
if (stringSet.size() > 3) {
|
|
||||||
Iterator<String> iterator = stringSet.iterator();
|
|
||||||
if (iterator.hasNext()) {
|
|
||||||
stringSet.remove(iterator.next());
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
System.out.println("stringSet.size():" + stringSet.size());
|
|
||||||
|
|
||||||
Iterator<String> iterator = stringSet.iterator();
|
|
||||||
while (iterator.hasNext()) {
|
|
||||||
System.out.println("iterator.next():" + iterator.next());
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
@Test
|
|
||||||
public void test_emoji() {
|
|
||||||
print(ResUtil.getString(R.string.yizhuan_erban_exampleunittest_04));
|
|
||||||
|
|
||||||
// for (int i = 0; i < emoji.length(); i++) {
|
|
||||||
// print(i + "");
|
|
||||||
// char indexC = emoji.charAt(i);
|
|
||||||
// print("" + indexC);
|
|
||||||
// int indexI = (int) indexC;
|
|
||||||
// print("" + indexI);
|
|
||||||
// String indexS = Integer.toHexString(indexI);
|
|
||||||
// print(indexS);
|
|
||||||
// }
|
|
||||||
|
|
||||||
}
|
|
||||||
|
|
||||||
public boolean isTutuAppEmojiPwd(String emojiText) {
|
|
||||||
String oneEmojiP = "([\ud83c\udc00-\ud83c\udfff]|[\ud83d\udc00-\ud83d\udfff]|[\ud83e\udc00-\ud83e\udfff]" +
|
|
||||||
"|[\u2100-\u32ff]|[\u0030-\u007f][\u20d0-\u20ff]|[\u0080-\u00ff])";
|
|
||||||
String regx = oneEmojiP + "{3}";
|
|
||||||
Pattern p = Pattern.compile(regx);
|
|
||||||
|
|
||||||
Matcher matcher = p.matcher(emojiText);
|
|
||||||
print(ResUtil.getString(R.string.yizhuan_erban_exampleunittest_05));
|
|
||||||
try {
|
|
||||||
boolean flag = matcher.find();
|
|
||||||
int i = 0;
|
|
||||||
while (flag) {
|
|
||||||
String emoji = matcher.group();
|
|
||||||
flag = matcher.find();
|
|
||||||
print(i + ResUtil.getString(R.string.yizhuan_erban_exampleunittest_06) + emoji);
|
|
||||||
i++;
|
|
||||||
}
|
|
||||||
} catch (Exception ex) {
|
|
||||||
ex.printStackTrace();
|
|
||||||
print(ResUtil.getString(R.string.yizhuan_erban_exampleunittest_07));
|
|
||||||
}
|
|
||||||
print(ResUtil.getString(R.string.yizhuan_erban_exampleunittest_08));
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
|
|
||||||
private void unicodeToUtf16Fun2() {
|
|
||||||
print(ResUtil.getString(R.string.yizhuan_erban_exampleunittest_09));
|
|
||||||
String emojiUnico = "1F601ResUtil.getString(R.string.yizhuan_erban_exampleunittest_010)\uD83D\uDE01"
|
|
||||||
int hexVaild = 0x1f601;
|
|
||||||
printBinaryString(Integer.toBinaryString(hexVaild));
|
|
||||||
hexVaild = hexVaild - 0x10000;
|
|
||||||
print(hexVaild + "");
|
|
||||||
String binaryString = Integer.toBinaryString(hexVaild);
|
|
||||||
|
|
||||||
printBinaryString(binaryString);
|
|
||||||
|
|
||||||
print(ResUtil.getString(R.string.yizhuan_erban_exampleunittest_011));
|
|
||||||
printBinaryString(Integer.toBinaryString(0xd800 - 0xd7c0));
|
|
||||||
|
|
||||||
String highString = binaryString.substring(0, binaryString.length() - 10);
|
|
||||||
String lowString = binaryString.substring(binaryString.length() - 10, binaryString.length());
|
|
||||||
print(ResUtil.getString(R.string.yizhuan_erban_exampleunittest_012));
|
|
||||||
printBinaryString(highString);
|
|
||||||
print(ResUtil.getString(R.string.yizhuan_erban_exampleunittest_013));
|
|
||||||
printBinaryString(lowString);
|
|
||||||
int highOffset = 0xd800;
|
|
||||||
int lowOffset = 0xdc00;
|
|
||||||
print(Integer.toHexString(Integer.valueOf(highString, 2) + highOffset));
|
|
||||||
print(Integer.toHexString(Integer.valueOf(lowString, 2) + lowOffset));
|
|
||||||
}
|
|
||||||
|
|
||||||
private void printBinaryString(String binaryString) {
|
|
||||||
if (binaryString == null || binaryString.length() == 0) {
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
int start = binaryString.length() - 4;
|
|
||||||
int end = binaryString.length();
|
|
||||||
String result = "";
|
|
||||||
while (true) {
|
|
||||||
result = binaryString.substring(Math.max(0, start), end) + " " + result;
|
|
||||||
if (start < 0) {
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
start -= 4;
|
|
||||||
end -= 4;
|
|
||||||
}
|
|
||||||
print(result);
|
|
||||||
}
|
|
||||||
|
|
||||||
private void unicodeToUtf16Fun1() {
|
|
||||||
String emoji = "\uD83D\uDE01";
|
|
||||||
// high offset
|
|
||||||
final int HighOffset = Integer.valueOf("D7C0", 16);
|
|
||||||
// low offset
|
|
||||||
final int LowOffset = Integer.valueOf("DC00", 16);
|
|
||||||
String emojiUnico = "1F601ResUtil.getString(R.string.yizhuan_erban_exampleunittest_014)\uD83D\uDE01"
|
|
||||||
int value = Integer.valueOf(emojiUnico, 16);
|
|
||||||
print(ResUtil.getString(R.string.yizhuan_erban_exampleunittest_015) + value);
|
|
||||||
String binaryString = Integer.toBinaryString(value);
|
|
||||||
print(ResUtil.getString(R.string.yizhuan_erban_exampleunittest_016) + binaryString);
|
|
||||||
|
|
||||||
StringBuilder builder = new StringBuilder();
|
|
||||||
|
|
||||||
|
|
||||||
String high10 = binaryString.substring(0, binaryString.length() - 10);
|
|
||||||
String low10 = binaryString.substring(binaryString.length() - 10, binaryString.length());
|
|
||||||
print(ResUtil.getString(R.string.yizhuan_erban_exampleunittest_017) + high10);
|
|
||||||
print(ResUtil.getString(R.string.yizhuan_erban_exampleunittest_018) + low10);
|
|
||||||
//高10位+高位偏移量,转成十六进制,得到高位
|
|
||||||
int highI = Integer.valueOf(high10, 2) + HighOffset;
|
|
||||||
String highHex = Integer.toHexString(highI);
|
|
||||||
print(ResUtil.getString(R.string.yizhuan_erban_exampleunittest_019) + highHex);//d83d
|
|
||||||
//低字节同理
|
|
||||||
int lowI = Integer.valueOf(low10, 2) + LowOffset;
|
|
||||||
String lowHex = Integer.toHexString(lowI);
|
|
||||||
print(ResUtil.getString(R.string.yizhuan_erban_exampleunittest_020) + lowHex);//de01
|
|
||||||
|
|
||||||
try {
|
|
||||||
byte[] uft8Bytes = emoji.getBytes("UTF-8");
|
|
||||||
for (int i = 0; i < uft8Bytes.length; i++) {
|
|
||||||
int index = uft8Bytes[i];
|
|
||||||
String index2String = Integer.toBinaryString(index);
|
|
||||||
print(index2String);
|
|
||||||
//取低8位
|
|
||||||
String low8 = index2String.substring(index2String.length() - 8,
|
|
||||||
index2String.length());
|
|
||||||
String valueHex = Integer.toHexString(Integer.valueOf(low8, 2));
|
|
||||||
print(valueHex);
|
|
||||||
}
|
|
||||||
} catch (UnsupportedEncodingException e) {
|
|
||||||
e.printStackTrace();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
private void print(String msg) {
|
|
||||||
System.out.println(msg);
|
|
||||||
}
|
|
||||||
|
|
||||||
}
|
|
@@ -1,2 +1 @@
|
|||||||
official
|
official
|
||||||
fir
|
|
@@ -8,7 +8,6 @@ android {
|
|||||||
defaultConfig {
|
defaultConfig {
|
||||||
minSdkVersion MIN_SDK_VERSION.toInteger()
|
minSdkVersion MIN_SDK_VERSION.toInteger()
|
||||||
targetSdkVersion TARGET_SDK_VERSION.toInteger()
|
targetSdkVersion TARGET_SDK_VERSION.toInteger()
|
||||||
testInstrumentationRunner "androidx.test.runner.AndroidJUnitRunner"
|
|
||||||
}
|
}
|
||||||
|
|
||||||
buildTypes {
|
buildTypes {
|
||||||
@@ -61,9 +60,6 @@ def Lombok = "1.18.24"
|
|||||||
|
|
||||||
dependencies {
|
dependencies {
|
||||||
api fileTree(dir: 'libs', include: ['*.jar'])
|
api fileTree(dir: 'libs', include: ['*.jar'])
|
||||||
testImplementation 'junit:junit:4.13.2'
|
|
||||||
androidTestImplementation 'androidx.test.ext:junit:1.1.3'
|
|
||||||
androidTestImplementation 'androidx.test.espresso:espresso-core:3.4.0'
|
|
||||||
|
|
||||||
api "com.orhanobut:logger:${loggerVersion}"
|
api "com.orhanobut:logger:${loggerVersion}"
|
||||||
|
|
||||||
|
@@ -1,27 +0,0 @@
|
|||||||
package com.chwl.core;
|
|
||||||
|
|
||||||
import static org.junit.Assert.assertEquals;
|
|
||||||
|
|
||||||
import android.content.Context;
|
|
||||||
|
|
||||||
import androidx.test.ext.junit.runners.AndroidJUnit4;
|
|
||||||
import androidx.test.platform.app.InstrumentationRegistry;
|
|
||||||
|
|
||||||
import org.junit.Test;
|
|
||||||
import org.junit.runner.RunWith;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Instrumented test, which will execute on an Android device.
|
|
||||||
*
|
|
||||||
* @see <a href="http://d.android.com/tools/testing">Testing documentation</a>
|
|
||||||
*/
|
|
||||||
@RunWith(AndroidJUnit4.class)
|
|
||||||
public class ExampleInstrumentedTest {
|
|
||||||
@Test
|
|
||||||
public void useAppContext() throws Exception {
|
|
||||||
// Context of the app under test.
|
|
||||||
Context appContext = InstrumentationRegistry.getTargetContext();
|
|
||||||
|
|
||||||
assertEquals("com.chwl.core.test", appContext.getPackageName());
|
|
||||||
}
|
|
||||||
}
|
|
@@ -1,41 +0,0 @@
|
|||||||
package com.chwl.core;
|
|
||||||
|
|
||||||
import static org.junit.Assert.assertEquals;
|
|
||||||
|
|
||||||
import org.junit.Test;
|
|
||||||
|
|
||||||
import io.reactivex.Single;
|
|
||||||
import io.reactivex.SingleEmitter;
|
|
||||||
import io.reactivex.SingleOnSubscribe;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Example local unit test, which will execute on the development machine (host).
|
|
||||||
*
|
|
||||||
* @see <a href="http://d.android.com/tools/testing">Testing documentation</a>
|
|
||||||
*/
|
|
||||||
public class ExampleUnitTest {
|
|
||||||
@Test
|
|
||||||
public void addition_isCorrect() throws Exception {
|
|
||||||
assertEquals(4, 2 + 2);
|
|
||||||
}
|
|
||||||
|
|
||||||
@Test
|
|
||||||
public void test_blockingGet() throws Exception{
|
|
||||||
|
|
||||||
try {
|
|
||||||
String str = Single.create(new SingleOnSubscribe<String>() {
|
|
||||||
@Override
|
|
||||||
public void subscribe(SingleEmitter<String> e) throws Exception {
|
|
||||||
// e.onError(new Throwable("error!"));
|
|
||||||
e.onSuccess("hahaha!");
|
|
||||||
}
|
|
||||||
}).blockingGet();
|
|
||||||
System.out.println(str);
|
|
||||||
} catch (Exception e) {
|
|
||||||
e.printStackTrace();
|
|
||||||
System.out.println(e.getMessage());
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
}
|
|
@@ -20,7 +20,6 @@ android.injected.testOnly=false
|
|||||||
# mob
|
# mob
|
||||||
MobSDK.spEdition=FP
|
MobSDK.spEdition=FP
|
||||||
|
|
||||||
only_arm64=false
|
|
||||||
minify_enabled=false
|
minify_enabled=false
|
||||||
|
|
||||||
# 是否隔离模式:换包名、换签名、隐藏部分SDK-KEY信息、去掉部分SDK(Google相关)
|
# 是否隔离模式:换包名、换签名、隐藏部分SDK-KEY信息、去掉部分SDK(Google相关)
|
||||||
|
@@ -6,7 +6,6 @@ android {
|
|||||||
defaultConfig {
|
defaultConfig {
|
||||||
minSdkVersion MIN_SDK_VERSION.toInteger()
|
minSdkVersion MIN_SDK_VERSION.toInteger()
|
||||||
targetSdkVersion TARGET_SDK_VERSION.toInteger()
|
targetSdkVersion TARGET_SDK_VERSION.toInteger()
|
||||||
testInstrumentationRunner "androidx.test.runner.AndroidJUnitRunner"
|
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -80,9 +79,6 @@ dependencies {
|
|||||||
def GlideTransformationsVersion = "3.0.1"
|
def GlideTransformationsVersion = "3.0.1"
|
||||||
|
|
||||||
implementation fileTree(dir: 'libs', include: ['*.jar'])
|
implementation fileTree(dir: 'libs', include: ['*.jar'])
|
||||||
testImplementation 'junit:junit:4.13.2'
|
|
||||||
androidTestImplementation 'androidx.test.ext:junit:1.1.3'
|
|
||||||
androidTestImplementation 'androidx.test.espresso:espresso-core:3.4.0'
|
|
||||||
|
|
||||||
api 'androidx.cardview:cardview:1.0.0'
|
api 'androidx.cardview:cardview:1.0.0'
|
||||||
api 'androidx.gridlayout:gridlayout:1.0.0'
|
api 'androidx.gridlayout:gridlayout:1.0.0'
|
||||||
|
@@ -1,27 +0,0 @@
|
|||||||
package com.chwl.library;
|
|
||||||
|
|
||||||
import static org.junit.Assert.assertEquals;
|
|
||||||
|
|
||||||
import android.content.Context;
|
|
||||||
|
|
||||||
import androidx.test.ext.junit.runners.AndroidJUnit4;
|
|
||||||
import androidx.test.platform.app.InstrumentationRegistry;
|
|
||||||
|
|
||||||
import org.junit.Test;
|
|
||||||
import org.junit.runner.RunWith;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Instrumented test, which will execute on an Android device.
|
|
||||||
*
|
|
||||||
* @see <a href="http://d.android.com/tools/testing">Testing documentation</a>
|
|
||||||
*/
|
|
||||||
@RunWith(AndroidJUnit4.class)
|
|
||||||
public class ExampleInstrumentedTest {
|
|
||||||
@Test
|
|
||||||
public void useAppContext() throws Exception {
|
|
||||||
// Context of the app under test.
|
|
||||||
Context appContext = InstrumentationRegistry.getTargetContext();
|
|
||||||
|
|
||||||
assertEquals("com.chwl.library", appContext.getPackageName());
|
|
||||||
}
|
|
||||||
}
|
|
@@ -1,17 +0,0 @@
|
|||||||
package com.chwl.library;
|
|
||||||
|
|
||||||
import static org.junit.Assert.assertEquals;
|
|
||||||
|
|
||||||
import org.junit.Test;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Example local unit test, which will execute on the development machine (host).
|
|
||||||
*
|
|
||||||
* @see <a href="http://d.android.com/tools/testing">Testing documentation</a>
|
|
||||||
*/
|
|
||||||
public class ExampleUnitTest {
|
|
||||||
@Test
|
|
||||||
public void addition_isCorrect() throws Exception {
|
|
||||||
assertEquals(4, 2 + 2);
|
|
||||||
}
|
|
||||||
}
|
|
@@ -1,24 +0,0 @@
|
|||||||
package com.example.lib_core
|
|
||||||
|
|
||||||
import androidx.test.platform.app.InstrumentationRegistry
|
|
||||||
import androidx.test.ext.junit.runners.AndroidJUnit4
|
|
||||||
|
|
||||||
import org.junit.Test
|
|
||||||
import org.junit.runner.RunWith
|
|
||||||
|
|
||||||
import org.junit.Assert.*
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Instrumented test, which will execute on an Android device.
|
|
||||||
*
|
|
||||||
* See [testing documentation](http://d.android.com/tools/testing).
|
|
||||||
*/
|
|
||||||
@RunWith(AndroidJUnit4::class)
|
|
||||||
class ExampleInstrumentedTest {
|
|
||||||
@Test
|
|
||||||
fun useAppContext() {
|
|
||||||
// Context of the app under test.
|
|
||||||
val appContext = InstrumentationRegistry.getInstrumentation().targetContext
|
|
||||||
assertEquals("com.example.lib_core.test", appContext.packageName)
|
|
||||||
}
|
|
||||||
}
|
|
@@ -1,17 +0,0 @@
|
|||||||
package com.example.lib_core
|
|
||||||
|
|
||||||
import org.junit.Test
|
|
||||||
|
|
||||||
import org.junit.Assert.*
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Example local unit test, which will execute on the development machine (host).
|
|
||||||
*
|
|
||||||
* See [testing documentation](http://d.android.com/tools/testing).
|
|
||||||
*/
|
|
||||||
class ExampleUnitTest {
|
|
||||||
@Test
|
|
||||||
fun addition_isCorrect() {
|
|
||||||
assertEquals(4, 2 + 2)
|
|
||||||
}
|
|
||||||
}
|
|
@@ -10,7 +10,6 @@ android {
|
|||||||
targetSdkVersion TARGET_SDK_VERSION.toInteger()
|
targetSdkVersion TARGET_SDK_VERSION.toInteger()
|
||||||
versionCode 1
|
versionCode 1
|
||||||
versionName "1.0.0"
|
versionName "1.0.0"
|
||||||
testInstrumentationRunner "androidx.test.runner.AndroidJUnitRunner"
|
|
||||||
consumerProguardFiles 'proguard-rules.pro'
|
consumerProguardFiles 'proguard-rules.pro'
|
||||||
}
|
}
|
||||||
buildTypes {
|
buildTypes {
|
||||||
|
@@ -1,24 +0,0 @@
|
|||||||
package com.example.lib_utils
|
|
||||||
|
|
||||||
import androidx.test.platform.app.InstrumentationRegistry
|
|
||||||
import androidx.test.ext.junit.runners.AndroidJUnit4
|
|
||||||
|
|
||||||
import org.junit.Test
|
|
||||||
import org.junit.runner.RunWith
|
|
||||||
|
|
||||||
import org.junit.Assert.*
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Instrumented test, which will execute on an Android device.
|
|
||||||
*
|
|
||||||
* See [testing documentation](http://d.android.com/tools/testing).
|
|
||||||
*/
|
|
||||||
@RunWith(AndroidJUnit4::class)
|
|
||||||
class ExampleInstrumentedTest {
|
|
||||||
@Test
|
|
||||||
fun useAppContext() {
|
|
||||||
// Context of the app under test.
|
|
||||||
val appContext = InstrumentationRegistry.getInstrumentation().targetContext
|
|
||||||
assertEquals("com.example.lib_utils.test", appContext.packageName)
|
|
||||||
}
|
|
||||||
}
|
|
@@ -1,17 +0,0 @@
|
|||||||
package com.example.lib_utils
|
|
||||||
|
|
||||||
import org.junit.Test
|
|
||||||
|
|
||||||
import org.junit.Assert.*
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Example local unit test, which will execute on the development machine (host).
|
|
||||||
*
|
|
||||||
* See [testing documentation](http://d.android.com/tools/testing).
|
|
||||||
*/
|
|
||||||
class ExampleUnitTest {
|
|
||||||
@Test
|
|
||||||
fun addition_isCorrect() {
|
|
||||||
assertEquals(4, 2 + 2)
|
|
||||||
}
|
|
||||||
}
|
|
@@ -14,7 +14,6 @@ android {
|
|||||||
targetSdkVersion TARGET_SDK_VERSION.toInteger()
|
targetSdkVersion TARGET_SDK_VERSION.toInteger()
|
||||||
versionCode 1
|
versionCode 1
|
||||||
versionName "1.0.0"
|
versionName "1.0.0"
|
||||||
testInstrumentationRunner "androidx.test.runner.AndroidJUnitRunner"
|
|
||||||
consumerProguardFiles 'proguard-rules.pro'
|
consumerProguardFiles 'proguard-rules.pro'
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@@ -1,221 +0,0 @@
|
|||||||
# NimUIKit 接口
|
|
||||||
|
|
||||||
`NimUIKit` 是 `UIKit` 组件的能力输出类,以静态方法的形式提供,下面分别介绍各个方法的作用以及调用时机。
|
|
||||||
|
|
||||||
## 初始化
|
|
||||||
|
|
||||||
`UIKit` 在 SDK 完成初始化之后调用,注意判断一下进程,在主进程中调用初始化。一般地,直接调用 `NimUIKit` 下面的方法即可初始化 `UIKit`。
|
|
||||||
|
|
||||||
```java
|
|
||||||
public static void init(Context context);
|
|
||||||
```
|
|
||||||
|
|
||||||
此外,有如下重载方法,可以传递全局配置项 `UIKitOptions`,注意这个参数不要传null。
|
|
||||||
|
|
||||||
```java
|
|
||||||
public static void init(Context context, UIKitOptions option);
|
|
||||||
```
|
|
||||||
|
|
||||||
或者如下方法,可以设置用户信息提供者和联系人信息提供者,以及全局配置项 `UIKitOptions`。如果设置了 `userInfoProvider` 和 `contactProvider`,那么 `UIKit` 将会替代对应的默认数据提供者。
|
|
||||||
|
|
||||||
```java
|
|
||||||
public static void init(Context context, IUserInfoProvider userInfoProvider, ContactProvider contactProvider);
|
|
||||||
|
|
||||||
public static void init(Context context, UIKitOptions option, IUserInfoProvider userInfoProvider, ContactProvider contactProvider);
|
|
||||||
```
|
|
||||||
|
|
||||||
> 全局配置项 UIKitOptions 的参考文档:[Uikit全局配置项介绍](Uikit全局配置项介绍.md)
|
|
||||||
|
|
||||||
## 登陆IM、聊天室
|
|
||||||
|
|
||||||
- 登陆
|
|
||||||
|
|
||||||
`UIKit` 封装了云信 SDK 的登录接口,原因是 `UIKit` 在登陆成功后模块本身要做一些处理,例如构建数据缓存等等。开发者可以调用如下方法发起登陆即可。
|
|
||||||
|
|
||||||
```java
|
|
||||||
public static AbortableFuture<LoginInfo> login(LoginInfo loginInfo, final RequestCallback<LoginInfo> callback);
|
|
||||||
```
|
|
||||||
|
|
||||||
若 `APP` 直接调用 SDK 登陆方法,需在登陆成功之后调用 `NimUIKit` 下面方法通知 `UIKit` 。
|
|
||||||
```java
|
|
||||||
public static void loginSuccess(String account);
|
|
||||||
```
|
|
||||||
|
|
||||||
- 登出
|
|
||||||
|
|
||||||
`APP`调用 SDK 登出方法之后,需调用 `NimUIKit` 如下方法通知 `UIKit`:
|
|
||||||
```java
|
|
||||||
public static void logout(String account);
|
|
||||||
```
|
|
||||||
|
|
||||||
- 进入聊天室
|
|
||||||
|
|
||||||
进入聊天室成功之后,需调用 `NimUIKit` 如下方法通知 `UIKit`:
|
|
||||||
```java
|
|
||||||
public static void enterChatRoomSuccess(EnterChatRoomResultData data, boolean independent);
|
|
||||||
```
|
|
||||||
|
|
||||||
- 退出聊天室
|
|
||||||
|
|
||||||
退出聊天室之后,需调用 `NimUIKit` 如下方法通知 `UIKit`:
|
|
||||||
```java
|
|
||||||
public static void exitedChatRoom(String roomId);
|
|
||||||
```
|
|
||||||
|
|
||||||
## 数据请求接口 Provider
|
|
||||||
|
|
||||||
`UIKit` 提供了相对独立的功能模块,其与 `APP` 之间必然会有相同的请求业务数据需求,例如在 `UIKit` 与 `APP` 之间都会请求用户信息 `UserInfo`。`UIKit` 定义了一套数据接口,以 xxxProvider 的方式命名,这样双方通过同一套接口来访问,接口的实现细节只需要做一次。
|
|
||||||
|
|
||||||
`UIKit` 内部已经默认实现了部分接口,并且为了提升体验,构建了对应的数据缓存,例如用户资料,对于大部分开发者,使用了云信托管用户资料,则直接通过这套约定接口获取数据,避免了繁杂的 SDK 接口调用,降低了开发者接入的难度。此外,`UIKit` 也支持开发者自行管理数据,例如部分开发者用户数据或者联系人数据并没有使用云信托管,这就要求开发者必须实现对应的数据接口,并设置到 `UIKit` ,使 `UIKit` 内部也能够通过接口访问到数据。
|
|
||||||
|
|
||||||
- 各个数据接口的名称以及作用
|
|
||||||
|
|
||||||
|名称|作用| UIKit 默认实现|
|
|
||||||
|:---|:---|:---|
|
|
||||||
|IUserProvider|用户信息|DefaultUserInfoProvider|
|
|
||||||
|ContactProvider|联系人信息(好友关系)|DefaultContactProvider|
|
|
||||||
|TeamProvider|群、群成员信息|DefaultTeamProvider|
|
|
||||||
|RobotProvider|智能机器人信息|DefaultRobotProvider|
|
|
||||||
|ChatRoomProvider|聊天室成员信息|DefaultChatRoomProvider|
|
|
||||||
|LocationProvider|地理位置信息|无,由`App` 实现|
|
|
||||||
|OnlineStateContentProvider|在线状态事件|无,由`App` 实现|
|
|
||||||
|
|
||||||
- 数据接口调用:
|
|
||||||
|
|
||||||
开发者 `APP` 中统一通过 NimUIKit#getXXXProvider 获取 provider,再调用 provider 接口获取数据。
|
|
||||||
例如,获取用户信息 `UserInfo` API :
|
|
||||||
|
|
||||||
```java
|
|
||||||
public static IUserInfoProvider getUserInfoProvider();
|
|
||||||
```
|
|
||||||
|
|
||||||
示例:
|
|
||||||
|
|
||||||
```java
|
|
||||||
UserInfo userInfo = NimUIKit.getUserInfoProvider().getUserInfo(account);
|
|
||||||
```
|
|
||||||
|
|
||||||
- 实现数据接口并设置到 `UIkit`
|
|
||||||
|
|
||||||
一般情况下,如上表所示,对于 `UIKit` 已经实现的数据接口,开发者无须再关注。其他情况下,如果需要自行管理数据,则应该首先实现对应数据的 provider 接口,其次设置到 `UIKit` 使其生效,对于`IUserProvider` 和 `ContactProvider` 是在 `UIKit` 初始化时传递,其他通过 NimUIKit#setXXXProvider 设置,例如
|
|
||||||
|
|
||||||
```java
|
|
||||||
public static void setTeamProvider(TeamProvider teamProvider);
|
|
||||||
```
|
|
||||||
|
|
||||||
## 数据变化通知接口
|
|
||||||
|
|
||||||
在一些业务场景中,例如好友列表,不仅要展示当前的好友,而且如果期间变更,则需要及时自动更新页面,因此,当前界面需要监听好友关系变化。`UIKit` 组件定义了一组数据变化监听接口,以 xxxObserver 命名,如下所示:
|
|
||||||
|
|
||||||
|名称|作用|
|
|
||||||
|:---|:---|
|
|
||||||
|UserInfoObserver|用户资料变更监听接口|
|
|
||||||
|ContactChangedObserver|联系人信息变更监听接口|
|
|
||||||
|TeamDataChangedObserver|群信息变更监听接口|
|
|
||||||
|TeamMemberDataChangedObserver|群成员信息变更监听接口|
|
|
||||||
|RoomMemberChangedObserver|聊天室成员信息变更监听接口|
|
|
||||||
|OnlineStateChangeObserver|在线状态事件变更监听接口|
|
|
||||||
|
|
||||||
- 注册、取消注册监听
|
|
||||||
|
|
||||||
示例:
|
|
||||||
|
|
||||||
```java
|
|
||||||
|
|
||||||
/**
|
|
||||||
* 注册聊天室成员信息变更监听接口
|
|
||||||
*/
|
|
||||||
NimUIKit.getChatRoomMemberChangedObservable().registerObserver(observer, true);
|
|
||||||
|
|
||||||
/**
|
|
||||||
* 注册群成员信息变更监听接口
|
|
||||||
*/
|
|
||||||
NimUIKit.getTeamChangedObservable().registerTeamMemberDataChangedObserver(observer, true);
|
|
||||||
|
|
||||||
/**
|
|
||||||
* 注册群信息信息变更监听接口
|
|
||||||
*/
|
|
||||||
NimUIKit.getTeamChangedObservable().registerTeamDataChangedObserver(observer, true);
|
|
||||||
|
|
||||||
/**
|
|
||||||
* 注册聊天室成员信息变更监听接口
|
|
||||||
*/
|
|
||||||
NimUIKit.getChatRoomMemberChangedObservable().registerObserver(observer, true);
|
|
||||||
|
|
||||||
/**
|
|
||||||
* 注册在线状态事件变更监听接口
|
|
||||||
*/
|
|
||||||
NimUIKit.getOnlineStateChangeObservable().registerOnlineStateChangeListeners(observer, true)
|
|
||||||
|
|
||||||
```
|
|
||||||
|
|
||||||
- 通知数据变更
|
|
||||||
|
|
||||||
在上一节数据请求接口 Provider 中,如果开发者没有自定义 provider,则可以跳过此节。相反,如果开发者自定义实现 provider 接口,当对应数据发生变更,则应该主动通知观察者。
|
|
||||||
|
|
||||||
例如,在线状态事件,`UIKit` 没有实现 `OnlineStateContentProvider`,因此,如果开发者使用了改功能,则应该自行管理对应的数据,并在监听的 SDK 的事件变更通知之后,通知 `UIKit`:
|
|
||||||
|
|
||||||
```java
|
|
||||||
NimUIKit.getOnlineStateChangeObservable().notifyOnlineStateChange(accounts)
|
|
||||||
```
|
|
||||||
|
|
||||||
或者,如果开发者不使用云信托管用户资料和好友关系,当用户信息发生变更之后,通知
|
|
||||||
|
|
||||||
```java
|
|
||||||
NimUIKit.getUserInfoObservable().notifyUserInfoChanged(accounts)
|
|
||||||
```
|
|
||||||
|
|
||||||
当好友关系变更之后,通知给 `ContactChangedObserver`,如
|
|
||||||
|
|
||||||
```java
|
|
||||||
NimUIKit.getContactChangedObservable().notifyAddedOrUpdated(friendAccounts)
|
|
||||||
```
|
|
||||||
|
|
||||||
其他类似。
|
|
||||||
|
|
||||||
## 定制化 Customization
|
|
||||||
|
|
||||||
UIKit 组件提供了丰富的定制化接口,以 xxxCustomization 结尾,主要与最近会话列表、会话、联系人这三大 IM 主题业务相关。开发者在 `UIKit` 初始化完成之后设置。如果默认界面满足需求,开发者可以跳过此节。
|
|
||||||
|
|
||||||
|名称|作用|
|
|
||||||
|:---|:---|
|
|
||||||
|SessionCustomization|会话界面定制接口|
|
|
||||||
|RecentCustomization|最近会话列表定制接口|
|
|
||||||
|ContactsCustomization|联系人定制接口|
|
|
||||||
|ChatRoomSessionCustomization|聊天室会话界面定制接口|
|
|
||||||
|
|
||||||
- SessionCustomization
|
|
||||||
|
|
||||||
支持 IM 聊天界面三项定制,1. 聊天背景 2. 输入框加号展开后的按钮和动作 3. Toolbar右侧更多按钮。定制生效又分为 P2P聊天和群聊、全局生效和单个生效。
|
|
||||||
|
|
||||||
例如,调用 `NimUIKit` 设置全局群聊会话定制界面:
|
|
||||||
```java
|
|
||||||
public static void setCommonTeamSessionCustomization( commonTeamSessionCustomization);
|
|
||||||
```
|
|
||||||
|
|
||||||
调用 `NimUIKit` 设置全局点对点会话定制界面:
|
|
||||||
```java
|
|
||||||
public static void setCommonP2PSessionCustomization(commonP2PSessionCustomization);
|
|
||||||
```
|
|
||||||
|
|
||||||
以及在启动会话界面时,调用 `NimUIKit` 指定当前界面使用特殊定制:
|
|
||||||
```java
|
|
||||||
public static void startChatting(context, id, sessionType,
|
|
||||||
sessionCustomization, anchorMessage)
|
|
||||||
```
|
|
||||||
|
|
||||||
- RecentCustomization
|
|
||||||
|
|
||||||
支持定制会话项文案,即最近会话列表里面每一行的消息文案。
|
|
||||||
|
|
||||||
```java
|
|
||||||
public static void setRecentCustomization(recentCustomization)
|
|
||||||
```
|
|
||||||
|
|
||||||
- ChatRoomSessionCustomization
|
|
||||||
|
|
||||||
支持 Chat Room 聊天界面自定义“输入框加号展开后的按钮和动作”。
|
|
||||||
|
|
||||||
```java
|
|
||||||
public static void setCommonChatRoomSessionCustomization( commonChatRoomSessionCustomization)
|
|
||||||
```
|
|
@@ -1,31 +0,0 @@
|
|||||||
# 全局配置项 UIKitOptions
|
|
||||||
|
|
||||||
`UIKit` 组件提供了全局配置类 `UIKitOptions` ,初始化 `UIKit`时传入 `UIKitOptions` 对象,如果没有配置需求,则直接使用默认。
|
|
||||||
|
|
||||||
`UIKitOptions` 属性介绍:
|
|
||||||
|
|
||||||
|类型|UIKitOptions 属性|说明|默认
|
|
||||||
|:---|:---|:---|:---|
|
|
||||||
|String|appCacheDir|保存图片/语音/文件/log等数据缓存的目录|/sdcard/{packageName}/
|
|
||||||
|boolean|aitEnable|是否开启@功能|true
|
|
||||||
|boolean|aitTeamMember|是否支持@群成员|true
|
|
||||||
|boolean|aitIMRobot|是否在 IM 聊天中@机器人|true
|
|
||||||
|boolean|aitChatRoomRobot|是否在聊天室中@机器人|true
|
|
||||||
|boolean|initAsync|是否使用异步方式初始化UIKit|false
|
|
||||||
|boolean|buildNimUserCache|是否使用云信托管账号体系,构建缓存|true
|
|
||||||
|boolean|buildTeamCache|是否构建群缓存|true
|
|
||||||
|boolean|buildFriendCache|构建群好友关系缓存|true
|
|
||||||
|boolean|buildRobotInfoCache|构建智能机器人缓存|true
|
|
||||||
|boolean|buildChatRoomMemberCache|构建聊天室成员缓存|true
|
|
||||||
|long|displayMsgTimeWithInterval|消息列表每隔多久显示一条消息时间信息|5分钟
|
|
||||||
|int|messageCountLoadOnce|单次抓取消息条数配置|20
|
|
||||||
|int|messageLeftBackground|IM 接收到的消息时,内容区域背景的drawable id|R.drawable.nim_message_item_left_selector
|
|
||||||
|int|messageRightBackground|IM 发送出去消息时,内容区域背景的drawable id|R.drawable.nim_message_item_right_selector
|
|
||||||
|int|chatRoomMsgLeftBackground|聊天室接收到的消息时,内容区域背景的drawable id|0
|
|
||||||
|int|chatRoomMsgRightBackground|聊天室发送消息时,内容区域背景的drawableid|0
|
|
||||||
|boolean|shouldHandleReceipt|全局是否使用消息已读|true
|
|
||||||
|int|maxInputTextLength|消息文本输入框最大输入字符数目|5000
|
|
||||||
|RecordType|audioRecordType|录音类型|RecordType.AAC
|
|
||||||
|int|audioRecordMaxTime|录音时长限制,单位秒|120s
|
|
||||||
|boolean|disableAudioPlayedStatusIcon|不显示语音消息未读红点|false
|
|
||||||
|boolean|disableAutoPlayNextAudio|禁止音频轮播|false
|
|
@@ -1,192 +0,0 @@
|
|||||||
# 自定义消息
|
|
||||||
|
|
||||||
以掷骰子为例
|
|
||||||
|
|
||||||
首先,先定义一个自定义消息的类型
|
|
||||||
|
|
||||||
```java
|
|
||||||
public interface CustomAttachmentType {
|
|
||||||
// 多端统一
|
|
||||||
int Guess = 1;
|
|
||||||
int SnapChat = 2;
|
|
||||||
int Sticker = 3;
|
|
||||||
int RTS = 4;
|
|
||||||
int Crops=5;//骰子
|
|
||||||
}
|
|
||||||
```
|
|
||||||
|
|
||||||
第二步,定义一个自定义消息附件的基类,负责解析你的自定义消息的公用字段,比如类型等 。
|
|
||||||
|
|
||||||
> 注意: 实现 MsgAttachment 接口的成员都要实现 Serializable。
|
|
||||||
|
|
||||||
```java
|
|
||||||
public abstract class CustomAttachment implements MsgAttachment {
|
|
||||||
protected int type;
|
|
||||||
CustomAttachment(int type) {
|
|
||||||
this.type = type;
|
|
||||||
}
|
|
||||||
public void fromJson(JSONObject data) {
|
|
||||||
if (data != null) {
|
|
||||||
parseData(data);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@Override
|
|
||||||
public String toJson(boolean send) {
|
|
||||||
return CustomAttachParser.packData(type, packData());
|
|
||||||
}
|
|
||||||
public int getType() {
|
|
||||||
return type;
|
|
||||||
}
|
|
||||||
protected abstract void parseData(JSONObject data);
|
|
||||||
protected abstract JSONObject packData();
|
|
||||||
}
|
|
||||||
```
|
|
||||||
|
|
||||||
第三步,继承这个基类,实现“骰子”的附件类型。
|
|
||||||
|
|
||||||
> 注意,成员变量都要实现 Serializable。
|
|
||||||
|
|
||||||
```java
|
|
||||||
public class CrapsAttachment extends CustomAttachment{
|
|
||||||
public enum Craps {
|
|
||||||
one(1, "1"),
|
|
||||||
two(2, "2"),
|
|
||||||
three(3, "3"),
|
|
||||||
four(4,"4"),
|
|
||||||
five(5,"5"),
|
|
||||||
six(6,"6"),
|
|
||||||
;
|
|
||||||
private int value;
|
|
||||||
private String desc;
|
|
||||||
Craps(int value, String desc) {
|
|
||||||
this.value = value;
|
|
||||||
this.desc = desc;
|
|
||||||
}
|
|
||||||
static Craps enumOfValue(int value) {
|
|
||||||
for (Craps direction : values()){
|
|
||||||
if (direction.getValue() == value) {
|
|
||||||
return direction;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return one;
|
|
||||||
}
|
|
||||||
public int getValue() {
|
|
||||||
return value;
|
|
||||||
}
|
|
||||||
public String getDesc() {
|
|
||||||
return desc;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
private Craps value;
|
|
||||||
public CrapsAttachment() {
|
|
||||||
super(CustomAttachmentType.Guess);
|
|
||||||
random();
|
|
||||||
}
|
|
||||||
@Override
|
|
||||||
protected void parseData(JSONObject data) {
|
|
||||||
value = Craps.enumOfValue(data.getIntValue("value"));
|
|
||||||
}
|
|
||||||
@Override
|
|
||||||
protected JSONObject packData() {
|
|
||||||
JSONObject data = new JSONObject();
|
|
||||||
data.put("value", value.getValue());
|
|
||||||
return data;
|
|
||||||
}
|
|
||||||
private void random() {
|
|
||||||
int value = new Random().nextInt(6) + 1;
|
|
||||||
this.value = Craps.enumOfValue(value);
|
|
||||||
}
|
|
||||||
public Craps getValue() {
|
|
||||||
return value;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
```
|
|
||||||
|
|
||||||
第四步,实现自定义消息的附件解析器。
|
|
||||||
|
|
||||||
```java
|
|
||||||
public class CustomAttachParser implements MsgAttachmentParser {
|
|
||||||
private static final String KEY_TYPE = "type";
|
|
||||||
private static final String KEY_DATA = "data";
|
|
||||||
@Override
|
|
||||||
public MsgAttachment parse(String json) {
|
|
||||||
CustomAttachment attachment = null;
|
|
||||||
try {
|
|
||||||
JSONObject object = JSON.parseObject(json);
|
|
||||||
int type = object.getInteger(KEY_TYPE);
|
|
||||||
JSONObject data = object.getJSONObject(KEY_DATA);
|
|
||||||
switch (type) {
|
|
||||||
case CustomAttachmentType.Crops:
|
|
||||||
attachment = new CrapsAttachment();
|
|
||||||
default:
|
|
||||||
attachment = new DefaultCustomAttachment();
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
if (attachment != null) {
|
|
||||||
attachment.fromJson(data);
|
|
||||||
}
|
|
||||||
} catch (Exception e) {
|
|
||||||
|
|
||||||
}
|
|
||||||
return attachment;
|
|
||||||
}
|
|
||||||
public static String packData(int type, JSONObject data) {
|
|
||||||
JSONObject object = new JSONObject();
|
|
||||||
object.put(KEY_TYPE, type);
|
|
||||||
if (data != null) {
|
|
||||||
object.put(KEY_DATA, data);
|
|
||||||
}
|
|
||||||
return object.toJSONString();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
```
|
|
||||||
|
|
||||||
第五步,将自定义消息展示UI上(当然这里显示的文字,如果想要显示成图片,可以参考demo)
|
|
||||||
|
|
||||||
```java
|
|
||||||
public class MsgViewHolderCrops extends MsgViewHolderText{
|
|
||||||
@Override
|
|
||||||
protected String getDisplayText() {
|
|
||||||
//实例化一个attachment
|
|
||||||
CrapsAttachment attachment = (CrapsAttachment)message.getAttachment();
|
|
||||||
return attachment.getValue().getDesc() + "点!";
|
|
||||||
}
|
|
||||||
}
|
|
||||||
```
|
|
||||||
|
|
||||||
第六步,发送自定义消息
|
|
||||||
|
|
||||||
```java
|
|
||||||
public class CrapsAction extends BaseAction{
|
|
||||||
public CrapsAction(){
|
|
||||||
super(R.drawable.message_plus_crops_selector, R.string.input_panel_crops);
|
|
||||||
}
|
|
||||||
@Override
|
|
||||||
public void onClick() {
|
|
||||||
CrapsAttachment attachment = new CrapsAttachment();
|
|
||||||
IMMessage message = MessageBuilder.createCustomMessage(
|
|
||||||
getAccount(), getSessionType(), attachment.getValue().getDesc(), attachment
|
|
||||||
);
|
|
||||||
sendMessage(message);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
```
|
|
||||||
|
|
||||||
第七步,将该附件解析器注册到 SDK 中。为了保证生成历史消息时能够正确解析自定义附件,注册一般应放在 Application 的 onCreate 中完成
|
|
||||||
|
|
||||||
```java
|
|
||||||
NIMClient.getService(MsgService.class).registerCustomAttachmentParser(new CustomAttachParser());
|
|
||||||
```
|
|
||||||
|
|
||||||
第八步,注册扩展消息类型的显示ViewHolder,由于这里使用我们UIKIT,所以也需要注册到Application的onCreate中
|
|
||||||
|
|
||||||
```java
|
|
||||||
NimUIKit.registerMsgItemViewHolder(CrapsAttachment.class, MsgViewHolderCrops.class);
|
|
||||||
```
|
|
||||||
|
|
||||||
第九步,添加“骰子”的按钮到“+”号中,Demo是在SessionHelper.java里面,定制的单聊界面。
|
|
||||||
|
|
||||||
```java
|
|
||||||
ArrayList<BaseAction> actions = new ArrayList<>();
|
|
||||||
actions.add(new CrapsAction());
|
|
||||||
```
|
|
@@ -1,26 +0,0 @@
|
|||||||
# NIMKit 架构解析
|
|
||||||
|
|
||||||
## 总体结构
|
|
||||||
|
|
||||||
```
|
|
||||||
com.netease.nim.uikit
|
|
||||||
├── api #UIKit 数据接口、定制化接口
|
|
||||||
├── business #业务相关,P2P,Team,ChatRoom
|
|
||||||
├── common #通用 ui 组件、utils
|
|
||||||
├── impl #数据接口、定制化接口的默认实现
|
|
||||||
└── support #第三方库封装
|
|
||||||
```
|
|
||||||
|
|
||||||
以层次划分的形式,看一下各个模块之间如何交互,各自的角色和定位
|
|
||||||
|
|
||||||

|
|
||||||
|
|
||||||
- api 模块定义了 `UIkit` 的功能扩展接口、业务数据接口,提供了 `UIkit` 能力输出类 NimUIKit。APP 与 `UIkit` 交互只需要关注这个模块的接口。
|
|
||||||
|
|
||||||
- impl 是 `UIkit` 一些抽象接口的默认实现,如会话定制接口、用户信息接口,当 APP 不设置任何定制化时,也能保证 `UIKit` 组件正常运转。
|
|
||||||
|
|
||||||
- business 模块实现了 IM 相关的功能,例如联系人、单聊、群聊、聊天室等,能力以 `Activity` 或者 `Fragment` 形式展现。开发者可以通过 `NimUIKit` 提供的接口来启动 `Activity`,以 xml 或者 java 代码的方式集成 `Fragment`。
|
|
||||||
|
|
||||||
- common 模块是一些通用 ui 组件、工具类等等,与业务无关。
|
|
||||||
|
|
||||||
- support 是基于第三方库对业务的封装等等。
|
|
@@ -1,48 +0,0 @@
|
|||||||
# V4.4.0 升级指南
|
|
||||||
|
|
||||||
随着时间增长,`UIKit` 功能业务越来越复杂,让我们不得不再次审视 `UIKit` 的定位角色,划清其在 `Demo`、`SDK` 之间关系和职责,却发现 `UIkit` 与 `Demo` 已经开始糅杂、耦合;并且开发者自定义变得不直观,学习成本高。
|
|
||||||
因此在 V4.4.0 我们不得不进行一次 `UIKit` 重构。建议开发者耐心阅读下新版的[架构解析](架构解析.md)以及[接口说明](NimUikit定制化接口介绍.md)。由于内部结构发生了一些调整,有些类的包名发生了变化,若开发者升级到V4.4.0,请关注本文给出的迁移指导。
|
|
||||||
|
|
||||||
## 接口变更
|
|
||||||
|
|
||||||
### ToolBar
|
|
||||||
|
|
||||||
`ToolBarOptions` 去掉默认值,与任何业务没有耦合。新增加 `NimToolBarOptions`,配置了 `UIKit` 默认值。
|
|
||||||
|
|
||||||
### 消息撤回观察者
|
|
||||||
|
|
||||||
封装了默认的消息撤回观察者 `NimMessageRevokeObserver`,开发者可以直接使用。
|
|
||||||
|
|
||||||
### 通知栏用户信息提供者
|
|
||||||
|
|
||||||
封装了 `SDK` 通知栏提醒时需要的用户信息提供者 `NimUserInfoProvider`,可以在初始化 `SDK` 时使用。
|
|
||||||
|
|
||||||
### 数据访问接口变更
|
|
||||||
|
|
||||||
访问数据不在使用 xxxCache 接口,而是使用xxxProvider 的方式,参考[接口说明](NimUikit定制化接口介绍.md) 中数据请求接口、数据变化通知接口的介绍。
|
|
||||||
|
|
||||||
## 流程变更
|
|
||||||
|
|
||||||
### 初始化
|
|
||||||
|
|
||||||
- 新增加 `UIKitOption` 作为 `UIKit` 全局配置选项,开发者在 `UIKit` 初始化时可以根据需求进行配置。支持的自定义配置项参考[UIKit全局配置项介绍](Uikit全局配置项介绍.md)。
|
|
||||||
|
|
||||||
- 增加异步初始化选项,`UIKitOption` 中配置。采用异步初始化可以节省 Application OnCreate 耗时,但是必须保证初始化完成之后再使用 `UIKit` 功能。
|
|
||||||
|
|
||||||
### 登入登出
|
|
||||||
|
|
||||||
- 调用手动登录有两种选择,一是使用 `UIKit` 封装的登录方法,而是直接调用 `SDK` 登录方法,若选择后者,则需要在登录成功回调之后,调用 NimUIKit#loginSuccess() 方法。
|
|
||||||
|
|
||||||
- 主动登出或者被踢之后,请调用 NimUIKit#logout 方法,重置 `UIKit`。
|
|
||||||
|
|
||||||
### 聊天室
|
|
||||||
|
|
||||||
- 进入聊天室成功之后,请调用 NimUIKit#enterChatRoomSuccess() 方法。
|
|
||||||
|
|
||||||
- 退出聊天室之后,清调用 NimUIKit#exitedChatRoom() 方法。
|
|
||||||
|
|
||||||
## Package 变更
|
|
||||||
|
|
||||||
由于 `UIKit` 内部包结构进行了调整,因此烦请开发者 App 中利用开发工具重新引用包即可,特别注意布局文件里面引用 `View` 或者 `Fragment` 的包路径。
|
|
||||||
|
|
||||||
|
|
@@ -1,43 +0,0 @@
|
|||||||
# 定制联系人选择器
|
|
||||||
|
|
||||||
在创建群、邀请群成员、消息转发等场景经常需要使用到联系人选择器,联系人选择器中的默认的联系人是你的好友。启动联系人选择器时可以传入可选参数 `ContactSelectActivity.Option` 来做联系人过滤、默认选中、多选等操作。例如:
|
|
||||||
|
|
||||||
```java
|
|
||||||
ContactSelectActivity.Option option = new ContactSelectActivity.Option();
|
|
||||||
|
|
||||||
// 设置联系人选择器标题
|
|
||||||
option.title = "邀请群成员";
|
|
||||||
|
|
||||||
// 设置可见但不可操作的联系人
|
|
||||||
ArrayList<String> disableAccounts = new ArrayList<>();
|
|
||||||
disableAccounts.addAll(memberAccounts);
|
|
||||||
option.itemDisableFilter = new ContactIdFilter(disableAccounts);
|
|
||||||
|
|
||||||
// 限制最大可选人数及超限提示
|
|
||||||
int capacity = teamCapacity - memberAccounts.size();
|
|
||||||
option.maxSelectNum = capacity;
|
|
||||||
option.maxSelectedTip = getString(R.string.reach_team_member_capacity, teamCapacity);
|
|
||||||
|
|
||||||
// 打开联系人选择器
|
|
||||||
NimUIKit.startContactSelector(NormalTeamInfoActivity.this, option, REQUEST_CODE_CONTACT_SELECT);
|
|
||||||
```
|
|
||||||
|
|
||||||
可以通过 `ContactSelectActivity.Option` 来定制,目前支持:
|
|
||||||
|
|
||||||
|参数|说明|
|
|
||||||
|:---|:---|
|
|
||||||
|type|联系人选择器中数据源类型:好友(默认)、群、群成员(需要设置teamId)。参考 ContactSelectType|
|
|
||||||
|teamId|联系人选择器数据源类型为群成员时,需要设置群号|
|
|
||||||
|title|联系人选择器标题|
|
|
||||||
|multi|联系人单选/多选(默认)|
|
|
||||||
|minSelectNum|至少选择人数|
|
|
||||||
|minSelectedTip|低于最少选择人数的提示|
|
|
||||||
|maxSelectNum|最大可选人数|
|
|
||||||
|maxSelectedTip|超过最大可选人数的提示|
|
|
||||||
|showContactSelectArea|是否显示已选头像区域|
|
|
||||||
|alreadySelectedAccounts|默认勾选(且可操作)的联系人项|
|
|
||||||
|itemFilter|需要过滤(不显示)的联系人项|
|
|
||||||
|itemDisableFilter|需要disable(可见但不可操作)的联系人项|
|
|
||||||
|searchVisible|是否支持搜索|
|
|
||||||
|allowSelectEmpty|允许不选任何人点击确定|
|
|
||||||
|maxSelectNumVisible|是否显示最大数目,结合maxSelectNum,与搜索位置相同|
|
|
@@ -1,122 +0,0 @@
|
|||||||
# 定制通讯录
|
|
||||||
|
|
||||||
在添加了通讯录的基础上,可以做相应的定制化操作。包括:通讯录列表点击事件响应处理,定制通讯录列表的功能项等。
|
|
||||||
|
|
||||||
## 设置通讯录列表点击事件响应处理
|
|
||||||
|
|
||||||
通讯录列表提供点击事件的响应处理函数,见 `ContactEventListener` :
|
|
||||||
|
|
||||||
- 通讯录联系人项点击事件处理
|
|
||||||
- 通讯录联系人项长按事件处理,一般弹出菜单:移除好友、添加到星标好友等
|
|
||||||
- 联系人头像点击相应
|
|
||||||
|
|
||||||
```java
|
|
||||||
// 在Application初始化中设置
|
|
||||||
NimUIKit.setContactEventListener(new ContactEventListener() {
|
|
||||||
@Override
|
|
||||||
public void onItemClick(Context context, String account) {
|
|
||||||
// 进入个人资料页,开发者自行实现
|
|
||||||
UserProfileActivity.start(context, account);
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public void onItemLongClick(Context context, String account) {
|
|
||||||
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public void onAvatarClick(Context context, String account) {
|
|
||||||
// 进入个人资料页,开发者自行实现
|
|
||||||
UserProfileActivity.start(context, account);
|
|
||||||
}
|
|
||||||
});
|
|
||||||
```
|
|
||||||
|
|
||||||
## 定制通讯录列表功能项
|
|
||||||
|
|
||||||
定制通讯录列表功能项依靠 `ContactsCustomization` 实现,例如折叠群、黑名单、消息验证、我的电脑等。
|
|
||||||
|
|
||||||
首先定义功能项 `FuncItem`,示例:
|
|
||||||
|
|
||||||
```java
|
|
||||||
public final static class FuncItem extends AbsContactItem {
|
|
||||||
static final FuncItem NORMAL_TEAM = new FuncItem();
|
|
||||||
static final FuncItem BLACK_LIST = new FuncItem();
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public int getItemType() {
|
|
||||||
return ItemTypes.FUNC;
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public String belongsGroup() {
|
|
||||||
return null;
|
|
||||||
}
|
|
||||||
|
|
||||||
public static final class FuncViewHolder extends AbsContactViewHolder<FuncItem> {
|
|
||||||
private ImageView image;
|
|
||||||
private TextView funcName;
|
|
||||||
private TextView unreadNum;
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public View inflate(LayoutInflater inflater) {
|
|
||||||
View view = inflater.inflate(R.layout.func_contacts_item, null);
|
|
||||||
this.image = (ImageView) view.findViewById(R.id.img_head);
|
|
||||||
this.funcName = (TextView) view.findViewById(R.id.tv_func_name);
|
|
||||||
this.unreadNum = (TextView) view.findViewById(R.id.tab_new_msg_label);
|
|
||||||
|
|
||||||
return view;
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public void refresh(ContactDataAdapter contactAdapter, int position, FuncItem item) {
|
|
||||||
if (item == NORMAL_TEAM) {
|
|
||||||
funcName.setText("讨论组");
|
|
||||||
image.setImageResource(R.drawable.ic_secretary);
|
|
||||||
} else if (item == BLACK_LIST) {
|
|
||||||
funcName.setText("黑名单");
|
|
||||||
image.setImageResource(R.drawable.ic_black_list);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
static List<AbsContactItem> provide() {
|
|
||||||
List<AbsContactItem> items = new ArrayList<AbsContactItem>();
|
|
||||||
items.add(NORMAL_TEAM);
|
|
||||||
items.add(BLACK_LIST);
|
|
||||||
|
|
||||||
return items;
|
|
||||||
}
|
|
||||||
|
|
||||||
static void handle(Context context, AbsContactItem item) {
|
|
||||||
if (item == NORMAL_TEAM) {
|
|
||||||
TeamListActivity.start(context, ItemTypes.TEAMS.NORMAL_TEAM);
|
|
||||||
} else if (item == BLACK_LIST) {
|
|
||||||
BlackListActivity.start(context);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
```
|
|
||||||
然后添加功能项到 `ContactsCustomization`:
|
|
||||||
|
|
||||||
```java
|
|
||||||
// 功能项定制
|
|
||||||
fragment.setContactsCustomization(new ContactsCustomization() {
|
|
||||||
@Override
|
|
||||||
public Class<? extends AbsContactViewHolder<? extends AbsContactItem>> onGetFuncViewHolderClass() {
|
|
||||||
return FuncItem.FuncViewHolder.class;
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public List<AbsContactItem> onGetFuncItems() {
|
|
||||||
return FuncItem.provide();
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public void onFuncItemClick(AbsContactItem item) {
|
|
||||||
FuncItem.handle(getActivity(), item);
|
|
||||||
}
|
|
||||||
});
|
|
||||||
|
|
||||||
```
|
|
||||||
|
|
@@ -1,104 +0,0 @@
|
|||||||
# 定制最近联系人列表
|
|
||||||
|
|
||||||
RecentContactsFragment 实现了默认的列表点击事件处理,点击列表项将会直接跳转至默认的单聊或者群聊界面,若默认实现无法满足需求,开发者可以参照下文定制最近联系人列表来实现,详细可以参照云信 Demo。
|
|
||||||
|
|
||||||
## 界面布局
|
|
||||||
|
|
||||||
最近联系人的布局属性如下图所示:
|
|
||||||
|
|
||||||

|
|
||||||
|
|
||||||
|属性|说明|
|
|
||||||
|:---|:---|
|
|
||||||
|network_status_bar|网络状态背景条|
|
|
||||||
|status_desc_label|网络状态文字说明|
|
|
||||||
|img_head|头像|
|
|
||||||
|tv_nickname|昵称|
|
|
||||||
|tv_online_state|在线状态|
|
|
||||||
|tv_message|消息内容|
|
|
||||||
|tv_date_time|时间戳|
|
|
||||||
|img_msg_status|消息发送状态|
|
|
||||||
|unread_number_tip|未读红点,占坑用|
|
|
||||||
|unread_number_explosion|未读红点,全屏动画时使用|
|
|
||||||
|
|
||||||
## 深度定制
|
|
||||||
|
|
||||||
开发者也可以通过设置自定义事件回调函数 RecentContactsCallback 来定制,目前支持:
|
|
||||||
|
|
||||||
1\. 最近联系人列表数据加载完成的回调函数(默认不作处理)。
|
|
||||||
|
|
||||||
2\. 有未读数更新时的回调函数,供更新除最近联系人列表外的其他界面和未读指示(默认不作处理)。
|
|
||||||
|
|
||||||
3\. 最近联系人点击响应回调函数,以供打开会话窗口时传入定制化参数,或者做其他动作(默认跳转至聊天窗口)。
|
|
||||||
|
|
||||||
4\. 设置自定义消息的摘要消息,展示在最近联系人列表的消息缩略栏上。当然,你也可以自定义一些内建消息的缩略语,例如图片,语音,音视频会话等,自定义的缩略语会被优先使用。
|
|
||||||
|
|
||||||
5\. 设置Tip消息的摘要信息,展示在最近联系人列表的消息缩略栏上。
|
|
||||||
|
|
||||||
如下是云信 Demo对最近联系人列表的定制:
|
|
||||||
|
|
||||||
```java
|
|
||||||
// 设置自定义事件回调函数
|
|
||||||
contactsFragment.setCallback(new RecentContactsCallback() {
|
|
||||||
@Override
|
|
||||||
public void onRecentContactsLoaded() {
|
|
||||||
// 最近联系人列表加载完毕
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public void onUnreadCountChange(int unreadCount) {
|
|
||||||
// 未读数发生变化
|
|
||||||
ReminderManager.getInstance().updateSessionUnreadNum(unreadCount);
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public void onItemClick(RecentContact recent) {
|
|
||||||
// 回调函数,以供打开会话窗口时传入定制化参数,或者做其他动作
|
|
||||||
switch (recent.getSessionType()) {
|
|
||||||
case P2P:
|
|
||||||
SessionHelper.startP2PSession(getActivity(), recent.getContactId());
|
|
||||||
break;
|
|
||||||
case Team:
|
|
||||||
SessionHelper.startTeamSession(getActivity(), recent.getContactId());
|
|
||||||
break;
|
|
||||||
default:
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public String getDigestOfAttachment(MsgAttachment attachment) {
|
|
||||||
// 设置自定义消息的摘要消息,展示在最近联系人列表的消息缩略栏上
|
|
||||||
// 当然,你也可以自定义一些内建消息的缩略语,例如图片,语音,音视频会话等,自定义的缩略语会被优先使用。
|
|
||||||
if (attachment instanceof GuessAttachment) {
|
|
||||||
GuessAttachment guess = (GuessAttachment) attachment;
|
|
||||||
return guess.getValue().getDesc();
|
|
||||||
} else if (attachment instanceof RTSAttachment) {
|
|
||||||
RTSAttachment rts = (RTSAttachment) attachment;
|
|
||||||
return rts.getContent();
|
|
||||||
} else if (attachment instanceof StickerAttachment) {
|
|
||||||
return "[贴图]";
|
|
||||||
} else if (attachment instanceof SnapChatAttachment) {
|
|
||||||
return "[阅后即焚]";
|
|
||||||
}
|
|
||||||
return null;
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public String getDigestOfTipMsg(RecentContact recent) {
|
|
||||||
// 设置Tip消息的摘要信息,展示在最近联系人列表的消息缩略栏上
|
|
||||||
String msgId = recent.getRecentMessageId();
|
|
||||||
List<String> uuids = new ArrayList<>(1);
|
|
||||||
uuids.add(msgId);
|
|
||||||
List<IMMessage> msgs = NIMClient.getService(MsgService.class).queryMessageListByUuidBlock(uuids);
|
|
||||||
if (msgs != null && !msgs.isEmpty()) {
|
|
||||||
IMMessage msg = msgs.get(0);
|
|
||||||
Map<String, Object> content = msg.getRemoteExtension();
|
|
||||||
if (content != null && !content.isEmpty()) {
|
|
||||||
return (String) content.get("content");
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return null;
|
|
||||||
}
|
|
||||||
});
|
|
||||||
```
|
|
@@ -1,226 +0,0 @@
|
|||||||
# 定制聊天窗口
|
|
||||||
|
|
||||||
默认的聊天窗口已经能满足大部分的需求,考虑到开发者可能有更特殊的要求,UIKit 提供非常灵活的个性定制,包括聊天窗口界面样式、会话中点击事件、自定义扩展消息类型显示、消息撤回和转发过滤器等。
|
|
||||||
|
|
||||||
## <span id="聊天界面介绍">聊天界面介绍</span>
|
|
||||||
|
|
||||||
### <span id="聊天界面代码结构">聊天界面代码结构</span>
|
|
||||||
聊天界面的主要实现在 `MessageFragment` 中。主要包括两个子模块 MessageListPanelEx (消息列表)和 InputPanel (输入框)。MessageFragment 与 子模块之间的交互依靠 ModuleProxy 这个代理类。MessageFragment 与子模块之间的参数传递依靠 Container 这个容器。具体结构参考如下:
|
|
||||||
|
|
||||||

|
|
||||||
|
|
||||||
### <span id="聊天界面布局">聊天界面布局</span>
|
|
||||||
|
|
||||||
聊天界面相关属性,如下图:
|
|
||||||
|
|
||||||

|
|
||||||
|
|
||||||

|
|
||||||
|
|
||||||

|
|
||||||
|
|
||||||
## <span id="定制化">定制化</span>
|
|
||||||
|
|
||||||
### <span id="简单定制">简单定制</span>
|
|
||||||
|
|
||||||
直接修改组件中提供的 `UIKitOptions` 类,进行简单的开关配置,提供的功能如下:
|
|
||||||
|
|
||||||
|名称|定义|
|
|
||||||
|:---|:---|
|
|
||||||
|aitEnable|开启@功能|
|
|
||||||
|aitTeamMember|支持@群成员|
|
|
||||||
|aitIMRobot|支持在 IM 聊天中@机器人|
|
|
||||||
|aitChatRoomRobot|支持在 Chat Room 中@机器人|
|
|
||||||
|displayMsgTimeWithInterval|消息列表每隔多久显示一条消息时间信息,默认五分钟|
|
|
||||||
|messageCountLoadOnce|单次抓取消息条数配置|
|
|
||||||
|messageLeftBackground|IM 接收到的消息时,内容区域背景的drawable id|
|
|
||||||
|messageRightBackground|IM 发送出去的消息时,内容区域背景的drawable id|
|
|
||||||
|chatRoomMsgLeftBackground|chat room 接收到的消息时,内容区域背景的drawable id|
|
|
||||||
|chatRoomMsgRightBackground|chat room 发送出去的消息时,内容区域背景的drawable id|
|
|
||||||
|shouldHandleReceipt|全局是否使用消息已读,如果设置为false,UIKit 组件将不会发送、接收已读回执|
|
|
||||||
|maxInputTextLength|文本框最大输入字符数目|
|
|
||||||
|audioRecordType|录音类型,默认aac|
|
|
||||||
|audioRecordMaxTime|录音时长限制,单位秒,默认最长120s|
|
|
||||||
|disableAudioPlayedStatusIcon|不显示语音消息未读红点|
|
|
||||||
|disableAutoPlayNextAudio|禁止音频轮播|
|
|
||||||
|
|
||||||
### <span id="深度定制">深度定制</span>
|
|
||||||
|
|
||||||
- 深度定制可提供的配置有:
|
|
||||||
|
|
||||||
|配置项|
|
|
||||||
|:---|
|
|
||||||
|输入框更多菜单项定制|
|
|
||||||
|ActionBar 右侧按钮定制|
|
|
||||||
|自定义贴图定制|
|
|
||||||
|会话中点击事件的定制|
|
|
||||||
|
|
||||||
- 提供两种深度定制方式:
|
|
||||||
|
|
||||||
1\. 开发者可以通过设置默认配置,替换整个单聊和群聊的聊天界面。
|
|
||||||
|
|
||||||
2\. 开发者可以自定义每个独立的聊天界面。
|
|
||||||
|
|
||||||
- 定制步骤:
|
|
||||||
|
|
||||||
1\. 初始化并配置 SessionCustomization 对象;具体配置项如何配置,见下文。
|
|
||||||
|
|
||||||
2\. 挑选定制方式:
|
|
||||||
|
|
||||||
(1)设置默认配置。将 SessionCustomization 对象设置到默认配置项中。
|
|
||||||
|
|
||||||
```
|
|
||||||
// 设置单聊界面定制
|
|
||||||
NimUIKit.setCommonP2PSessionCustomization(commonP2PSessionCustomization);
|
|
||||||
|
|
||||||
// 设置群聊界面定制
|
|
||||||
NimUIKit.setCommonTeamSessionCustomization(commonTeamSessionCustomization);
|
|
||||||
```
|
|
||||||
|
|
||||||
(2)自定义独立的聊天界面。使用 NimUIKit#startChatting 方法,直接将 SessionCustomization 对象作为参数,打开聊天界面。
|
|
||||||
|
|
||||||
```
|
|
||||||
// 启动单聊
|
|
||||||
NimUIKit.startChatting(context, account, SessionTypeEnum.P2P, sessionCustomization);
|
|
||||||
|
|
||||||
// 启动群聊
|
|
||||||
NimUIKit.startChatting(context, teamId, SessionTypeEnum.Team, sessionCustomization);
|
|
||||||
```
|
|
||||||
|
|
||||||
#### <span id="输入框更多菜单项定制">输入框更多菜单项定制</span>
|
|
||||||
|
|
||||||
1\. 定义一个菜单项
|
|
||||||
|
|
||||||
可以进行菜单图片和文案资源的配置,并且定义图片的点击事件,以及处理回调事件。
|
|
||||||
|
|
||||||
具体步骤:
|
|
||||||
|
|
||||||
(1) 继承 BaseAction,初始化时,传入图片资源和文案资源。
|
|
||||||
|
|
||||||
(2)继承 BaseAction,根据需要重写 onClick() 或 onActivityResult() 方法
|
|
||||||
|
|
||||||
```
|
|
||||||
// 定义一个action
|
|
||||||
public class FileAction extends BaseAction {
|
|
||||||
|
|
||||||
public FileAction() {
|
|
||||||
super(R.drawable.message_plus_file_selector, R.string.input_panel_file);
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public void onClick() {
|
|
||||||
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public void onActivityResult(int requestCode, int resultCode, Intent data) {
|
|
||||||
|
|
||||||
}
|
|
||||||
}
|
|
||||||
```
|
|
||||||
|
|
||||||
2\. 按指定顺序添加菜单项到更多菜单视图。
|
|
||||||
|
|
||||||
步骤如下:
|
|
||||||
|
|
||||||
(1)构造 SessionCustomization 对象。
|
|
||||||
|
|
||||||
(2)构造 List<BaseAction> actions 集合,按照想要排列的顺序添加 action。
|
|
||||||
|
|
||||||
(3)设置 customization.actions
|
|
||||||
|
|
||||||
(4)调用 NimUIKit#startChatting 打开的聊天界面,即是配置好的效果。
|
|
||||||
|
|
||||||
```
|
|
||||||
SessionCustomization sessionCustomization = new SessionCustomization();
|
|
||||||
ArrayList<BaseAction> actions = new ArrayList<>();
|
|
||||||
actions.add(new AVChatAction(AVChatType.AUDIO));
|
|
||||||
actions.add(new AVChatAction(AVChatType.VIDEO));
|
|
||||||
actions.add(new SnapChatAction());
|
|
||||||
actions.add(new GuessAction());
|
|
||||||
actions.add(new FileAction());
|
|
||||||
sessionCustomization.actions = actions;
|
|
||||||
|
|
||||||
NimUIKit.startChatting(context, account, SessionTypeEnum.P2P, sessionCustomization, null);
|
|
||||||
```
|
|
||||||
|
|
||||||
#### <span id="ActionBar 右侧按钮定制">ActionBar 右侧按钮定制</span>
|
|
||||||
|
|
||||||
聊天界面的 ActionBar 右上角的按钮,支持自定义,包括按钮的图像,按钮的个数,按钮的点击事件。
|
|
||||||
|
|
||||||
具体步骤如下:
|
|
||||||
|
|
||||||
1\. 首先初始化对象 SessionCustomization;
|
|
||||||
|
|
||||||
2\. 初始化 ArrayList<SessionCustomization.OptionsButton> 列表 buttons;
|
|
||||||
|
|
||||||
3\. 初始化一个 SessionCustomization.OptionsButton 的 button;
|
|
||||||
|
|
||||||
4\. 设置 button的点击事件和图像资源;
|
|
||||||
|
|
||||||
5\. 将 button 添加到列表,并将列表设置到 sessionCustomization.buttons = buttons;
|
|
||||||
|
|
||||||
6\. 调用 NimUIKit#startChatting 打开的聊天界面,即是配置好的效果。
|
|
||||||
|
|
||||||
```
|
|
||||||
SessionCustomization sessionCustomization = new SessionCustomization();
|
|
||||||
ArrayList<SessionCustomization.OptionsButton> buttons = new ArrayList<>();
|
|
||||||
SessionCustomization.OptionsButton cloudMsgButton = new SessionCustomization.OptionsButton() {
|
|
||||||
@Override
|
|
||||||
public void onClick(Context context, View view, String sessionId) {
|
|
||||||
|
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
||||||
cloudMsgButton.iconId = R.drawable.nim_ic_messge_history;
|
|
||||||
|
|
||||||
buttons.add(cloudMsgButton);
|
|
||||||
sessionCustomization.buttons = buttons;
|
|
||||||
|
|
||||||
NimUIKit.startChatting(context, account, SessionTypeEnum.P2P, sessionCustomization, null);
|
|
||||||
```
|
|
||||||
|
|
||||||
#### <span id="自定义贴图定制">自定义贴图定制</span>
|
|
||||||
|
|
||||||
1\. 是否显示贴图表情开关
|
|
||||||
|
|
||||||
构造 SessionCustomization 对象,设置 withSticker,显示表情为 true,不显示表情为 false。再调用 NimUIKit#startChatting 函数即可。
|
|
||||||
|
|
||||||
```
|
|
||||||
SessionCustomization sessionCustomization = new SessionCustomization();
|
|
||||||
sessionCustomization.withSticker = true;
|
|
||||||
|
|
||||||
NimUIKit.startChatting(context, account, SessionTypeEnum.P2P, sessionCustomization, null);
|
|
||||||
```
|
|
||||||
|
|
||||||
2\. 贴图表情配置
|
|
||||||
|
|
||||||
替换 demo/assets/sticker 下的资源文件,并修改 StickerManager 中的数据源。
|
|
||||||
|
|
||||||
3\. 默认emoji表情图片及文案配置
|
|
||||||
|
|
||||||
替换 uikit/assets/emoji/default 文件夹下的资源文件 和 emoji.xml 中相应的设置。
|
|
||||||
|
|
||||||
#### <span id="会话中点击事件的定制">会话中点击事件的定制</span>
|
|
||||||
|
|
||||||
会话窗口消息列表提供一些点击事件的响应处理函数,见 SessionEventListener:
|
|
||||||
|
|
||||||
头像点击事件处理,一般用于打开用户资料页面
|
|
||||||
头像长按事件处理,一般用于群组@功能,或者弹出菜单,做拉黑,加好友等功能
|
|
||||||
|
|
||||||
```
|
|
||||||
SessionEventListener listener = new SessionEventListener() {
|
|
||||||
@Override
|
|
||||||
public void onAvatarClicked(Context context, IMMessage message) {
|
|
||||||
// 一般用于打开用户资料页面
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public void onAvatarLongClicked(Context context, IMMessage message) {
|
|
||||||
// 一般用于群组@功能,或者弹出菜单,做拉黑,加好友等功能
|
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
||||||
// 在Application初始化中设置
|
|
||||||
NimUIKit.setSessionListener(listener);
|
|
||||||
```
|
|
@@ -1,54 +0,0 @@
|
|||||||
# 机器人消息排版
|
|
||||||
|
|
||||||
## 前言
|
|
||||||
|
|
||||||
智能机器人消息作为云信内置的一种基本消息,相比其他消息类型,有其不一样的特殊性如下:
|
|
||||||
|
|
||||||
* **机器人的上下行消息**
|
|
||||||
|
|
||||||
机器人的消息分上行和下行两种。用户发给机器人的消息称为上行消息,通常以纯文本形式展现,在组件中,复用了和文本消息同样的配置;机器人发给用户的消息称为下行消息,消息模板由用户在管理后台配置,消息内容可以允许以文本,图片,按钮的形式进行自由组合,这样会造成在界面上的表现形式比较繁多。
|
|
||||||
|
|
||||||
* **机器人消息交互层面的迷惑性**
|
|
||||||
|
|
||||||
机器人消息经常会和文本消息有一定的类似,特定的交互形式下,两者还会由于用户的输入进行改变,以组件的交互举例:
|
|
||||||
* 当用户在输入框中 @ 的用户不包括机器人时,组件认为这是一条文本,以文本消息形式发出。
|
|
||||||
* 当用户在输入框中 @ 的用户包括机器人时,组件认为这是一段需要和机器人交互的内容,会自动以机器人消息形式发出。
|
|
||||||
|
|
||||||
虽然在界面展示形式上都是一个气泡中包含了一些文字,但由于 @ 的对象不同,实际上消息类型是有一定差异的。
|
|
||||||
|
|
||||||
* **机器人下行消息模板界面**
|
|
||||||
|
|
||||||
机器人消息模板由用户在管理后台,机器人知识库中自行配置。一般以 xml 布局文件形式下发到客户端,这个时候客户端需要解析整个模板数据,构造出适合的布局模型,再传入视图进行渲染。
|
|
||||||
|
|
||||||
|
|
||||||
## 机器人模板布局数据解析
|
|
||||||
|
|
||||||
由于机器人后台可提供的数据形式比较灵活,数据可以由后台内置的类型机型有限的组合,也可以由开发者进行完全的自定义。这里组件针对前一种有限组合的情况,提供了一套快速集成的模板数据转换到视图的方案。
|
|
||||||
|
|
||||||
机器人下行消息中,获取真正属于“机器人回复”的内容可通过 `RobotAttachment` 调用`getResponse` 方法,这已经是 `SDK` 将 xml 转换成 json 格式后的结果。
|
|
||||||
|
|
||||||
```java
|
|
||||||
RobotAttachment attachment = (RobotAttachment) message.getAttachment();
|
|
||||||
|
|
||||||
// 下行消息
|
|
||||||
if (attachment.isRobotSend()) {
|
|
||||||
String robotContent = attachment.getResponse();
|
|
||||||
}
|
|
||||||
```
|
|
||||||
|
|
||||||
接下来组件通过机器人回复内容 `robotContent` 构造`RobotResponseContent` 对象,交给 `RobotContentLinearLayout` 展示。
|
|
||||||
|
|
||||||
### 模板数据视图
|
|
||||||
|
|
||||||
机器人回复消息由文本、按钮、图片组成,其中按钮对应机器人协议中的 link 元素所对应的内容。按钮内部又可以嵌套文本、图片、按钮。
|
|
||||||
`UIKit` 提供了一组自定义View 展示不同的机器人消息元素。单条机器人下行消息整体视图为 `RobotContentLinearLayout`,可以从 `RobotResponseContent` 直接构建对象。
|
|
||||||
|
|
||||||
机器人回复内容元素与自定义视图对应关系
|
|
||||||
|
|
||||||
|内容元素|自定义视图|
|
|
||||||
|:---|:---|
|
|
||||||
|文本| RobotTextView|
|
|
||||||
|图片| RobotImageView|
|
|
||||||
|link| RobotLinkView|
|
|
||||||
|
|
||||||
|
|
Reference in New Issue
Block a user