commit 6ce9456c26c2b67d511cf4afc977edcefb454bc7 Author: wlh <646507849@qq.com> Date: Thu Dec 14 20:59:56 2023 +0800 中健 365 im diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..36b401f --- /dev/null +++ b/.gitignore @@ -0,0 +1,12 @@ +# OS X Files +.DS_Store +**/.DS_Store + + +# Gradle files +*/Android/.gradle/ + +# IDEA Files +*/Android/.idea/ + +*/Android/local.properties \ No newline at end of file diff --git a/.idea/.gitignore b/.idea/.gitignore new file mode 100644 index 0000000..26d3352 --- /dev/null +++ b/.idea/.gitignore @@ -0,0 +1,3 @@ +# Default ignored files +/shelf/ +/workspace.xml diff --git a/.idea/TUIKit.iml b/.idea/TUIKit.iml new file mode 100644 index 0000000..d6ebd48 --- /dev/null +++ b/.idea/TUIKit.iml @@ -0,0 +1,9 @@ + + + + + + + + + \ No newline at end of file diff --git a/.idea/modules.xml b/.idea/modules.xml new file mode 100644 index 0000000..bb0683a --- /dev/null +++ b/.idea/modules.xml @@ -0,0 +1,8 @@ + + + + + + + + \ No newline at end of file diff --git a/.idea/vcs.xml b/.idea/vcs.xml new file mode 100644 index 0000000..35eb1dd --- /dev/null +++ b/.idea/vcs.xml @@ -0,0 +1,6 @@ + + + + + + \ No newline at end of file diff --git a/README.md b/README.md new file mode 100644 index 0000000..fd0dea8 --- /dev/null +++ b/README.md @@ -0,0 +1,30 @@ +English | [简体中文](./README_ZH.md) + +# TUIKit (Android) + +## Download Links + +[Latest TUIChat download](https://im.sdk.cloud.tencent.cn/download/tuikit/7.2.4123/android/TUIChat.zip) + +[Latest TUIConversation download](https://im.sdk.cloud.tencent.cn/download/tuikit/7.2.4123/android/TUIConversation.zip) + +[Latest TUIContact download](https://im.sdk.cloud.tencent.cn/download/tuikit/7.2.4123/android/TUIContact.zip) + +[Latest TUIGroup download](https://im.sdk.cloud.tencent.cn/download/tuikit/7.2.4123/android/TUIGroup.zip) + +[Latest TUISearch download](https://im.sdk.cloud.tencent.cn/download/tuikit/7.2.4123/android/TUISearch.zip) + +[Latest TUICallKit download](https://im.sdk.cloud.tencent.cn/download/tuikit/7.2.4123/android/TUICallKit.zip) + +[Latest TUIOfflinePush download](https://im.sdk.cloud.tencent.cn/download/tuikit/7.2.4123/android/TUIOfflinePush.zip) + +[Latest TUICommunity download](https://im.sdk.cloud.tencent.cn/download/tuikit/7.2.4123/android/TUICommunity.zip) + +[Latest TUITranslation download](https://im.sdk.cloud.tencent.cn/download/tuikit/7.2.4123/android/TUITranslation.zip) + +[Latest TUICore download](https://im.sdk.cloud.tencent.cn/download/tuikit/7.2.4123/android/TUICore.zip) + +[Latest TIMCommon download](https://im.sdk.cloud.tencent.cn/download/tuikit/7.2.4123/android/TIMCommon.zip) + + +If you encounter a TUIKit bug, please submit a pull request. After successful merging, we will update the TUIKit library in time. diff --git a/README_ZH.md b/README_ZH.md new file mode 100644 index 0000000..bb67ce8 --- /dev/null +++ b/README_ZH.md @@ -0,0 +1,35 @@ +[English](./README.md) | 简体中文 + +# TUIKit(Android) + +## 下载地址 + +[最新 TUIChat 下载](https://im.sdk.cloud.tencent.cn/download/tuikit/7.2.4123/android/TUIChat.zip) + +[最新 TUIConversation 下载](https://im.sdk.cloud.tencent.cn/download/tuikit/7.2.4123/android/TUIConversation.zip) + +[最新 TUIContact 下载](https://im.sdk.cloud.tencent.cn/download/tuikit/7.2.4123/android/TUIContact.zip) + +[最新 TUIGroup 下载](https://im.sdk.cloud.tencent.cn/download/tuikit/7.2.4123/android/TUIGroup.zip) + +[最新 TUISearch 下载](https://im.sdk.cloud.tencent.cn/download/tuikit/7.2.4123/android/TUISearch.zip) + +[最新 TUICallKit 下载](https://im.sdk.cloud.tencent.cn/download/tuikit/7.2.4123/android/TUICallKit.zip) + +[最新 TUIOfflinePush 下载](https://im.sdk.cloud.tencent.cn/download/tuikit/7.2.4123/android/TUIOfflinePush.zip) + +[最新 TUICommunity 下载](https://im.sdk.cloud.tencent.cn/download/tuikit/7.2.4123/android/TUICommunity.zip) + +[最新 TUITranslation 下载](https://im.sdk.cloud.tencent.cn/download/tuikit/7.2.4123/android/TUITranslation.zip) + +[最新 TUICore 下载](https://im.sdk.cloud.tencent.cn/download/tuikit/7.2.4123/android/TUICore.zip) + +[最新 TIMCommon 下载](https://im.sdk.cloud.tencent.cn/download/tuikit/7.2.4123/android/TIMCommon.zip) + +## 交流&反馈 + +如果您遇到 TUIKit 的 Bug,欢迎提交 Pull Request,Merge 成功后我们会及时更新 pod 集成的 TUIKit 库 。 + +欢迎加入 QQ 群进行技术交流和反馈问题。 + +![img]( https://im.sdk.qcloud.com/tools/resource/officialwebsite/pictures/doc_tuikit_qq_group.jpg) diff --git a/TIMCommon/.gitignore b/TIMCommon/.gitignore new file mode 100644 index 0000000..aa724b7 --- /dev/null +++ b/TIMCommon/.gitignore @@ -0,0 +1,15 @@ +*.iml +.gradle +/local.properties +/.idea/caches +/.idea/libraries +/.idea/modules.xml +/.idea/workspace.xml +/.idea/navEditor.xml +/.idea/assetWizardSettings.xml +.DS_Store +/build +/captures +.externalNativeBuild +.cxx +local.properties diff --git a/TIMCommon/build.gradle b/TIMCommon/build.gradle new file mode 100644 index 0000000..0114b27 --- /dev/null +++ b/TIMCommon/build.gradle @@ -0,0 +1,5 @@ +// Top-level build file where you can add configuration options common to all sub-projects/modules. +plugins { + id 'com.android.application' version '7.4.1' apply false + id 'com.android.library' version '7.4.1' apply false +} \ No newline at end of file diff --git a/TIMCommon/gradle.properties b/TIMCommon/gradle.properties new file mode 100644 index 0000000..3e927b1 --- /dev/null +++ b/TIMCommon/gradle.properties @@ -0,0 +1,21 @@ +# Project-wide Gradle settings. +# IDE (e.g. Android Studio) users: +# Gradle settings configured through the IDE *will override* +# any settings specified in this file. +# For more details on how to configure your build environment visit +# http://www.gradle.org/docs/current/userguide/build_environment.html +# Specifies the JVM arguments used for the daemon process. +# The setting is particularly useful for tweaking memory settings. +org.gradle.jvmargs=-Xmx2048m -Dfile.encoding=UTF-8 +# When configured, Gradle will run in incubating parallel mode. +# This option should only be used with decoupled projects. More details, visit +# http://www.gradle.org/docs/current/userguide/multi_project_builds.html#sec:decoupled_projects +# org.gradle.parallel=true +# AndroidX package structure to make it clearer which packages are bundled with the +# Android operating system, and which are packaged with your app's APK +# https://developer.android.com/topic/libraries/support-library/androidx-rn +android.useAndroidX=true +# Enables namespacing of each library's R class so that its R class includes only the +# resources declared in the library itself and none from the library's dependencies, +# thereby reducing the size of the R class for that library +android.nonTransitiveRClass=true \ No newline at end of file diff --git a/TIMCommon/gradle/wrapper/gradle-wrapper.jar b/TIMCommon/gradle/wrapper/gradle-wrapper.jar new file mode 100644 index 0000000..e708b1c Binary files /dev/null and b/TIMCommon/gradle/wrapper/gradle-wrapper.jar differ diff --git a/TIMCommon/gradle/wrapper/gradle-wrapper.properties b/TIMCommon/gradle/wrapper/gradle-wrapper.properties new file mode 100644 index 0000000..ecf7412 --- /dev/null +++ b/TIMCommon/gradle/wrapper/gradle-wrapper.properties @@ -0,0 +1,6 @@ +#Fri Mar 10 11:10:42 CST 2023 +distributionBase=GRADLE_USER_HOME +distributionUrl=https\://services.gradle.org/distributions/gradle-7.5-bin.zip +distributionPath=wrapper/dists +zipStorePath=wrapper/dists +zipStoreBase=GRADLE_USER_HOME diff --git a/TIMCommon/gradlew b/TIMCommon/gradlew new file mode 100644 index 0000000..4f906e0 --- /dev/null +++ b/TIMCommon/gradlew @@ -0,0 +1,185 @@ +#!/usr/bin/env sh + +# +# Copyright 2015 the original author or authors. +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# https://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. +# + +############################################################################## +## +## Gradle start up script for UN*X +## +############################################################################## + +# Attempt to set APP_HOME +# Resolve links: $0 may be a link +PRG="$0" +# Need this for relative symlinks. +while [ -h "$PRG" ] ; do + ls=`ls -ld "$PRG"` + link=`expr "$ls" : '.*-> \(.*\)$'` + if expr "$link" : '/.*' > /dev/null; then + PRG="$link" + else + PRG=`dirname "$PRG"`"/$link" + fi +done +SAVED="`pwd`" +cd "`dirname \"$PRG\"`/" >/dev/null +APP_HOME="`pwd -P`" +cd "$SAVED" >/dev/null + +APP_NAME="Gradle" +APP_BASE_NAME=`basename "$0"` + +# Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script. +DEFAULT_JVM_OPTS='"-Xmx64m" "-Xms64m"' + +# Use the maximum available, or set MAX_FD != -1 to use that value. +MAX_FD="maximum" + +warn () { + echo "$*" +} + +die () { + echo + echo "$*" + echo + exit 1 +} + +# OS specific support (must be 'true' or 'false'). +cygwin=false +msys=false +darwin=false +nonstop=false +case "`uname`" in + CYGWIN* ) + cygwin=true + ;; + Darwin* ) + darwin=true + ;; + MINGW* ) + msys=true + ;; + NONSTOP* ) + nonstop=true + ;; +esac + +CLASSPATH=$APP_HOME/gradle/wrapper/gradle-wrapper.jar + + +# Determine the Java command to use to start the JVM. +if [ -n "$JAVA_HOME" ] ; then + if [ -x "$JAVA_HOME/jre/sh/java" ] ; then + # IBM's JDK on AIX uses strange locations for the executables + JAVACMD="$JAVA_HOME/jre/sh/java" + else + JAVACMD="$JAVA_HOME/bin/java" + fi + if [ ! -x "$JAVACMD" ] ; then + die "ERROR: JAVA_HOME is set to an invalid directory: $JAVA_HOME + +Please set the JAVA_HOME variable in your environment to match the +location of your Java installation." + fi +else + JAVACMD="java" + which java >/dev/null 2>&1 || die "ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH. + +Please set the JAVA_HOME variable in your environment to match the +location of your Java installation." +fi + +# Increase the maximum file descriptors if we can. +if [ "$cygwin" = "false" -a "$darwin" = "false" -a "$nonstop" = "false" ] ; then + MAX_FD_LIMIT=`ulimit -H -n` + if [ $? -eq 0 ] ; then + if [ "$MAX_FD" = "maximum" -o "$MAX_FD" = "max" ] ; then + MAX_FD="$MAX_FD_LIMIT" + fi + ulimit -n $MAX_FD + if [ $? -ne 0 ] ; then + warn "Could not set maximum file descriptor limit: $MAX_FD" + fi + else + warn "Could not query maximum file descriptor limit: $MAX_FD_LIMIT" + fi +fi + +# For Darwin, add options to specify how the application appears in the dock +if $darwin; then + GRADLE_OPTS="$GRADLE_OPTS \"-Xdock:name=$APP_NAME\" \"-Xdock:icon=$APP_HOME/media/gradle.icns\"" +fi + +# For Cygwin or MSYS, switch paths to Windows format before running java +if [ "$cygwin" = "true" -o "$msys" = "true" ] ; then + APP_HOME=`cygpath --path --mixed "$APP_HOME"` + CLASSPATH=`cygpath --path --mixed "$CLASSPATH"` + + JAVACMD=`cygpath --unix "$JAVACMD"` + + # We build the pattern for arguments to be converted via cygpath + ROOTDIRSRAW=`find -L / -maxdepth 1 -mindepth 1 -type d 2>/dev/null` + SEP="" + for dir in $ROOTDIRSRAW ; do + ROOTDIRS="$ROOTDIRS$SEP$dir" + SEP="|" + done + OURCYGPATTERN="(^($ROOTDIRS))" + # Add a user-defined pattern to the cygpath arguments + if [ "$GRADLE_CYGPATTERN" != "" ] ; then + OURCYGPATTERN="$OURCYGPATTERN|($GRADLE_CYGPATTERN)" + fi + # Now convert the arguments - kludge to limit ourselves to /bin/sh + i=0 + for arg in "$@" ; do + CHECK=`echo "$arg"|egrep -c "$OURCYGPATTERN" -` + CHECK2=`echo "$arg"|egrep -c "^-"` ### Determine if an option + + if [ $CHECK -ne 0 ] && [ $CHECK2 -eq 0 ] ; then ### Added a condition + eval `echo args$i`=`cygpath --path --ignore --mixed "$arg"` + else + eval `echo args$i`="\"$arg\"" + fi + i=`expr $i + 1` + done + case $i in + 0) set -- ;; + 1) set -- "$args0" ;; + 2) set -- "$args0" "$args1" ;; + 3) set -- "$args0" "$args1" "$args2" ;; + 4) set -- "$args0" "$args1" "$args2" "$args3" ;; + 5) set -- "$args0" "$args1" "$args2" "$args3" "$args4" ;; + 6) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" ;; + 7) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" ;; + 8) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" "$args7" ;; + 9) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" "$args7" "$args8" ;; + esac +fi + +# Escape application args +save () { + for i do printf %s\\n "$i" | sed "s/'/'\\\\''/g;1s/^/'/;\$s/\$/' \\\\/" ; done + echo " " +} +APP_ARGS=`save "$@"` + +# Collect all arguments for the java command, following the shell quoting and substitution rules +eval set -- $DEFAULT_JVM_OPTS $JAVA_OPTS $GRADLE_OPTS "\"-Dorg.gradle.appname=$APP_BASE_NAME\"" -classpath "\"$CLASSPATH\"" org.gradle.wrapper.GradleWrapperMain "$APP_ARGS" + +exec "$JAVACMD" "$@" diff --git a/TIMCommon/gradlew.bat b/TIMCommon/gradlew.bat new file mode 100644 index 0000000..107acd3 --- /dev/null +++ b/TIMCommon/gradlew.bat @@ -0,0 +1,89 @@ +@rem +@rem Copyright 2015 the original author or authors. +@rem +@rem Licensed under the Apache License, Version 2.0 (the "License"); +@rem you may not use this file except in compliance with the License. +@rem You may obtain a copy of the License at +@rem +@rem https://www.apache.org/licenses/LICENSE-2.0 +@rem +@rem Unless required by applicable law or agreed to in writing, software +@rem distributed under the License is distributed on an "AS IS" BASIS, +@rem WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +@rem See the License for the specific language governing permissions and +@rem limitations under the License. +@rem + +@if "%DEBUG%" == "" @echo off +@rem ########################################################################## +@rem +@rem Gradle startup script for Windows +@rem +@rem ########################################################################## + +@rem Set local scope for the variables with windows NT shell +if "%OS%"=="Windows_NT" setlocal + +set DIRNAME=%~dp0 +if "%DIRNAME%" == "" set DIRNAME=. +set APP_BASE_NAME=%~n0 +set APP_HOME=%DIRNAME% + +@rem Resolve any "." and ".." in APP_HOME to make it shorter. +for %%i in ("%APP_HOME%") do set APP_HOME=%%~fi + +@rem Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script. +set DEFAULT_JVM_OPTS="-Xmx64m" "-Xms64m" + +@rem Find java.exe +if defined JAVA_HOME goto findJavaFromJavaHome + +set JAVA_EXE=java.exe +%JAVA_EXE% -version >NUL 2>&1 +if "%ERRORLEVEL%" == "0" goto execute + +echo. +echo ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH. +echo. +echo Please set the JAVA_HOME variable in your environment to match the +echo location of your Java installation. + +goto fail + +:findJavaFromJavaHome +set JAVA_HOME=%JAVA_HOME:"=% +set JAVA_EXE=%JAVA_HOME%/bin/java.exe + +if exist "%JAVA_EXE%" goto execute + +echo. +echo ERROR: JAVA_HOME is set to an invalid directory: %JAVA_HOME% +echo. +echo Please set the JAVA_HOME variable in your environment to match the +echo location of your Java installation. + +goto fail + +:execute +@rem Setup the command line + +set CLASSPATH=%APP_HOME%\gradle\wrapper\gradle-wrapper.jar + + +@rem Execute Gradle +"%JAVA_EXE%" %DEFAULT_JVM_OPTS% %JAVA_OPTS% %GRADLE_OPTS% "-Dorg.gradle.appname=%APP_BASE_NAME%" -classpath "%CLASSPATH%" org.gradle.wrapper.GradleWrapperMain %* + +:end +@rem End local scope for the variables with windows NT shell +if "%ERRORLEVEL%"=="0" goto mainEnd + +:fail +rem Set variable GRADLE_EXIT_CONSOLE if you need the _script_ return code instead of +rem the _cmd.exe /c_ return code! +if not "" == "%GRADLE_EXIT_CONSOLE%" exit 1 +exit /b 1 + +:mainEnd +if "%OS%"=="Windows_NT" endlocal + +:omega diff --git a/TIMCommon/settings.gradle b/TIMCommon/settings.gradle new file mode 100644 index 0000000..15f8361 --- /dev/null +++ b/TIMCommon/settings.gradle @@ -0,0 +1,16 @@ +pluginManagement { + repositories { + google() + mavenCentral() + gradlePluginPortal() + } +} +dependencyResolutionManagement { + repositoriesMode.set(RepositoriesMode.FAIL_ON_PROJECT_REPOS) + repositories { + google() + mavenCentral() + } +} +rootProject.name = "TIMCommon" +include ':timcommon' diff --git a/TIMCommon/timcommon/.gitignore b/TIMCommon/timcommon/.gitignore new file mode 100644 index 0000000..42afabf --- /dev/null +++ b/TIMCommon/timcommon/.gitignore @@ -0,0 +1 @@ +/build \ No newline at end of file diff --git a/TIMCommon/timcommon/build.gradle b/TIMCommon/timcommon/build.gradle new file mode 100644 index 0000000..08b5679 --- /dev/null +++ b/TIMCommon/timcommon/build.gradle @@ -0,0 +1,51 @@ +plugins { + id 'com.android.library' +} + +android { + compileSdkVersion 30 + buildToolsVersion "30.0.3" + + defaultConfig { + minSdkVersion 19 + targetSdkVersion 30 + versionCode 1 + versionName "1.0" + } + + buildTypes { + release { + minifyEnabled false + } + } + compileOptions { + sourceCompatibility JavaVersion.VERSION_1_8 + targetCompatibility JavaVersion.VERSION_1_8 + } + + // 主题资源文件夹 + sourceSets { + main { + res.srcDirs += "src/main/res-light" + res.srcDirs += "src/main/res-lively" + res.srcDirs += "src/main/res-serious" + } + } +} + +afterEvaluate{ + generateReleaseBuildConfig.enabled =false + generateDebugBuildConfig.enabled =false +} + +dependencies { + + api project(':tuicore') + implementation 'com.google.code.gson:gson:2.9.1' + implementation 'androidx.appcompat:appcompat:1.3.1' + implementation 'com.github.bumptech.glide:glide:4.12.0' + implementation 'androidx.recyclerview:recyclerview:1.2.1' + implementation 'androidx.viewpager2:viewpager2:1.0.0' + implementation 'androidx.cardview:cardview:1.0.0' +} + diff --git a/TIMCommon/timcommon/consumer-rules.pro b/TIMCommon/timcommon/consumer-rules.pro new file mode 100644 index 0000000..e69de29 diff --git a/TIMCommon/timcommon/proguard-rules.pro b/TIMCommon/timcommon/proguard-rules.pro new file mode 100644 index 0000000..481bb43 --- /dev/null +++ b/TIMCommon/timcommon/proguard-rules.pro @@ -0,0 +1,21 @@ +# Add project specific ProGuard rules here. +# You can control the set of applied configuration files using the +# proguardFiles setting in build.gradle. +# +# For more details, see +# http://developer.android.com/guide/developing/tools/proguard.html + +# If your project uses WebView with JS, uncomment the following +# and specify the fully qualified class name to the JavaScript interface +# class: +#-keepclassmembers class fqcn.of.javascript.interface.for.webview { +# public *; +#} + +# Uncomment this to preserve the line number information for +# debugging stack traces. +#-keepattributes SourceFile,LineNumberTable + +# If you keep the line number information, uncomment this to +# hide the original source file name. +#-renamesourcefileattribute SourceFile \ No newline at end of file diff --git a/TIMCommon/timcommon/src/main/AndroidManifest.xml b/TIMCommon/timcommon/src/main/AndroidManifest.xml new file mode 100644 index 0000000..197c852 --- /dev/null +++ b/TIMCommon/timcommon/src/main/AndroidManifest.xml @@ -0,0 +1,38 @@ + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/TIMCommon/timcommon/src/main/assets/emoji/[NO]@2x.png b/TIMCommon/timcommon/src/main/assets/emoji/[NO]@2x.png new file mode 100644 index 0000000..b7ae6d7 Binary files /dev/null and b/TIMCommon/timcommon/src/main/assets/emoji/[NO]@2x.png differ diff --git a/TIMCommon/timcommon/src/main/assets/emoji/[OK]@2x.png b/TIMCommon/timcommon/src/main/assets/emoji/[OK]@2x.png new file mode 100644 index 0000000..e723cb6 Binary files /dev/null and b/TIMCommon/timcommon/src/main/assets/emoji/[OK]@2x.png differ diff --git a/TIMCommon/timcommon/src/main/assets/emoji/[下雨]@2x.png b/TIMCommon/timcommon/src/main/assets/emoji/[下雨]@2x.png new file mode 100644 index 0000000..d049b49 Binary files /dev/null and b/TIMCommon/timcommon/src/main/assets/emoji/[下雨]@2x.png differ diff --git a/TIMCommon/timcommon/src/main/assets/emoji/[么么哒]@2x.png b/TIMCommon/timcommon/src/main/assets/emoji/[么么哒]@2x.png new file mode 100644 index 0000000..f47fa56 Binary files /dev/null and b/TIMCommon/timcommon/src/main/assets/emoji/[么么哒]@2x.png differ diff --git a/TIMCommon/timcommon/src/main/assets/emoji/[乒乓]@2x.png b/TIMCommon/timcommon/src/main/assets/emoji/[乒乓]@2x.png new file mode 100644 index 0000000..56f307d Binary files /dev/null and b/TIMCommon/timcommon/src/main/assets/emoji/[乒乓]@2x.png differ diff --git a/TIMCommon/timcommon/src/main/assets/emoji/[便便]@2x.png b/TIMCommon/timcommon/src/main/assets/emoji/[便便]@2x.png new file mode 100644 index 0000000..adf4cc2 Binary files /dev/null and b/TIMCommon/timcommon/src/main/assets/emoji/[便便]@2x.png differ diff --git a/TIMCommon/timcommon/src/main/assets/emoji/[信封]@2x.png b/TIMCommon/timcommon/src/main/assets/emoji/[信封]@2x.png new file mode 100644 index 0000000..c571ad2 Binary files /dev/null and b/TIMCommon/timcommon/src/main/assets/emoji/[信封]@2x.png differ diff --git a/TIMCommon/timcommon/src/main/assets/emoji/[偷笑]@2x.png b/TIMCommon/timcommon/src/main/assets/emoji/[偷笑]@2x.png new file mode 100644 index 0000000..a303375 Binary files /dev/null and b/TIMCommon/timcommon/src/main/assets/emoji/[偷笑]@2x.png differ diff --git a/TIMCommon/timcommon/src/main/assets/emoji/[傲慢]@2x.png b/TIMCommon/timcommon/src/main/assets/emoji/[傲慢]@2x.png new file mode 100644 index 0000000..34fa967 Binary files /dev/null and b/TIMCommon/timcommon/src/main/assets/emoji/[傲慢]@2x.png differ diff --git a/TIMCommon/timcommon/src/main/assets/emoji/[再见]@2x.png b/TIMCommon/timcommon/src/main/assets/emoji/[再见]@2x.png new file mode 100644 index 0000000..497e464 Binary files /dev/null and b/TIMCommon/timcommon/src/main/assets/emoji/[再见]@2x.png differ diff --git a/TIMCommon/timcommon/src/main/assets/emoji/[冷汗]@2x.png b/TIMCommon/timcommon/src/main/assets/emoji/[冷汗]@2x.png new file mode 100644 index 0000000..137ec0f Binary files /dev/null and b/TIMCommon/timcommon/src/main/assets/emoji/[冷汗]@2x.png differ diff --git a/TIMCommon/timcommon/src/main/assets/emoji/[凋谢]@2x.png b/TIMCommon/timcommon/src/main/assets/emoji/[凋谢]@2x.png new file mode 100644 index 0000000..dd05090 Binary files /dev/null and b/TIMCommon/timcommon/src/main/assets/emoji/[凋谢]@2x.png differ diff --git a/TIMCommon/timcommon/src/main/assets/emoji/[刀]@2x.png b/TIMCommon/timcommon/src/main/assets/emoji/[刀]@2x.png new file mode 100644 index 0000000..ab4fc8d Binary files /dev/null and b/TIMCommon/timcommon/src/main/assets/emoji/[刀]@2x.png differ diff --git a/TIMCommon/timcommon/src/main/assets/emoji/[删除]@2x.png b/TIMCommon/timcommon/src/main/assets/emoji/[删除]@2x.png new file mode 100644 index 0000000..113e20f Binary files /dev/null and b/TIMCommon/timcommon/src/main/assets/emoji/[删除]@2x.png differ diff --git a/TIMCommon/timcommon/src/main/assets/emoji/[勾引]@2x.png b/TIMCommon/timcommon/src/main/assets/emoji/[勾引]@2x.png new file mode 100644 index 0000000..04060f5 Binary files /dev/null and b/TIMCommon/timcommon/src/main/assets/emoji/[勾引]@2x.png differ diff --git a/TIMCommon/timcommon/src/main/assets/emoji/[发呆]@2x.png b/TIMCommon/timcommon/src/main/assets/emoji/[发呆]@2x.png new file mode 100644 index 0000000..d8393d9 Binary files /dev/null and b/TIMCommon/timcommon/src/main/assets/emoji/[发呆]@2x.png differ diff --git a/TIMCommon/timcommon/src/main/assets/emoji/[发抖]@2x.png b/TIMCommon/timcommon/src/main/assets/emoji/[发抖]@2x.png new file mode 100644 index 0000000..094ed1d Binary files /dev/null and b/TIMCommon/timcommon/src/main/assets/emoji/[发抖]@2x.png differ diff --git a/TIMCommon/timcommon/src/main/assets/emoji/[可怜]@2x.png b/TIMCommon/timcommon/src/main/assets/emoji/[可怜]@2x.png new file mode 100644 index 0000000..73963b7 Binary files /dev/null and b/TIMCommon/timcommon/src/main/assets/emoji/[可怜]@2x.png differ diff --git a/TIMCommon/timcommon/src/main/assets/emoji/[可爱]@2x.png b/TIMCommon/timcommon/src/main/assets/emoji/[可爱]@2x.png new file mode 100644 index 0000000..a235324 Binary files /dev/null and b/TIMCommon/timcommon/src/main/assets/emoji/[可爱]@2x.png differ diff --git a/TIMCommon/timcommon/src/main/assets/emoji/[右哼哼]@2x.png b/TIMCommon/timcommon/src/main/assets/emoji/[右哼哼]@2x.png new file mode 100644 index 0000000..e1ca267 Binary files /dev/null and b/TIMCommon/timcommon/src/main/assets/emoji/[右哼哼]@2x.png differ diff --git a/TIMCommon/timcommon/src/main/assets/emoji/[右太极]@2x.png b/TIMCommon/timcommon/src/main/assets/emoji/[右太极]@2x.png new file mode 100644 index 0000000..6b55dfb Binary files /dev/null and b/TIMCommon/timcommon/src/main/assets/emoji/[右太极]@2x.png differ diff --git a/TIMCommon/timcommon/src/main/assets/emoji/[右车头]@2x.png b/TIMCommon/timcommon/src/main/assets/emoji/[右车头]@2x.png new file mode 100644 index 0000000..85fdb86 Binary files /dev/null and b/TIMCommon/timcommon/src/main/assets/emoji/[右车头]@2x.png differ diff --git a/TIMCommon/timcommon/src/main/assets/emoji/[吐]@2x.png b/TIMCommon/timcommon/src/main/assets/emoji/[吐]@2x.png new file mode 100644 index 0000000..0265a28 Binary files /dev/null and b/TIMCommon/timcommon/src/main/assets/emoji/[吐]@2x.png differ diff --git a/TIMCommon/timcommon/src/main/assets/emoji/[吓]@2x.png b/TIMCommon/timcommon/src/main/assets/emoji/[吓]@2x.png new file mode 100644 index 0000000..b03f3b6 Binary files /dev/null and b/TIMCommon/timcommon/src/main/assets/emoji/[吓]@2x.png differ diff --git a/TIMCommon/timcommon/src/main/assets/emoji/[咒骂]@2x.png b/TIMCommon/timcommon/src/main/assets/emoji/[咒骂]@2x.png new file mode 100644 index 0000000..c65faa6 Binary files /dev/null and b/TIMCommon/timcommon/src/main/assets/emoji/[咒骂]@2x.png differ diff --git a/TIMCommon/timcommon/src/main/assets/emoji/[咖啡]@2x.png b/TIMCommon/timcommon/src/main/assets/emoji/[咖啡]@2x.png new file mode 100644 index 0000000..dd595d3 Binary files /dev/null and b/TIMCommon/timcommon/src/main/assets/emoji/[咖啡]@2x.png differ diff --git a/TIMCommon/timcommon/src/main/assets/emoji/[啤酒]@2x.png b/TIMCommon/timcommon/src/main/assets/emoji/[啤酒]@2x.png new file mode 100644 index 0000000..52289b0 Binary files /dev/null and b/TIMCommon/timcommon/src/main/assets/emoji/[啤酒]@2x.png differ diff --git a/TIMCommon/timcommon/src/main/assets/emoji/[嘘]@2x.png b/TIMCommon/timcommon/src/main/assets/emoji/[嘘]@2x.png new file mode 100644 index 0000000..e011a35 Binary files /dev/null and b/TIMCommon/timcommon/src/main/assets/emoji/[嘘]@2x.png differ diff --git a/TIMCommon/timcommon/src/main/assets/emoji/[回头]@2x.png b/TIMCommon/timcommon/src/main/assets/emoji/[回头]@2x.png new file mode 100644 index 0000000..84660e2 Binary files /dev/null and b/TIMCommon/timcommon/src/main/assets/emoji/[回头]@2x.png differ diff --git a/TIMCommon/timcommon/src/main/assets/emoji/[困]@2x.png b/TIMCommon/timcommon/src/main/assets/emoji/[困]@2x.png new file mode 100644 index 0000000..cc0447f Binary files /dev/null and b/TIMCommon/timcommon/src/main/assets/emoji/[困]@2x.png differ diff --git a/TIMCommon/timcommon/src/main/assets/emoji/[坏笑]@2x.png b/TIMCommon/timcommon/src/main/assets/emoji/[坏笑]@2x.png new file mode 100644 index 0000000..36f3c32 Binary files /dev/null and b/TIMCommon/timcommon/src/main/assets/emoji/[坏笑]@2x.png differ diff --git a/TIMCommon/timcommon/src/main/assets/emoji/[多云]@2x.png b/TIMCommon/timcommon/src/main/assets/emoji/[多云]@2x.png new file mode 100644 index 0000000..71d23d6 Binary files /dev/null and b/TIMCommon/timcommon/src/main/assets/emoji/[多云]@2x.png differ diff --git a/TIMCommon/timcommon/src/main/assets/emoji/[大兵]@2x.png b/TIMCommon/timcommon/src/main/assets/emoji/[大兵]@2x.png new file mode 100644 index 0000000..94b0d52 Binary files /dev/null and b/TIMCommon/timcommon/src/main/assets/emoji/[大兵]@2x.png differ diff --git a/TIMCommon/timcommon/src/main/assets/emoji/[大哭]@2x.png b/TIMCommon/timcommon/src/main/assets/emoji/[大哭]@2x.png new file mode 100644 index 0000000..e816c56 Binary files /dev/null and b/TIMCommon/timcommon/src/main/assets/emoji/[大哭]@2x.png differ diff --git a/TIMCommon/timcommon/src/main/assets/emoji/[太阳]@2x.png b/TIMCommon/timcommon/src/main/assets/emoji/[太阳]@2x.png new file mode 100644 index 0000000..a0d229a Binary files /dev/null and b/TIMCommon/timcommon/src/main/assets/emoji/[太阳]@2x.png differ diff --git a/TIMCommon/timcommon/src/main/assets/emoji/[奋斗]@2x.png b/TIMCommon/timcommon/src/main/assets/emoji/[奋斗]@2x.png new file mode 100644 index 0000000..3347c97 Binary files /dev/null and b/TIMCommon/timcommon/src/main/assets/emoji/[奋斗]@2x.png differ diff --git a/TIMCommon/timcommon/src/main/assets/emoji/[奶瓶]@2x.png b/TIMCommon/timcommon/src/main/assets/emoji/[奶瓶]@2x.png new file mode 100644 index 0000000..0d4b8d9 Binary files /dev/null and b/TIMCommon/timcommon/src/main/assets/emoji/[奶瓶]@2x.png differ diff --git a/TIMCommon/timcommon/src/main/assets/emoji/[委屈]@2x.png b/TIMCommon/timcommon/src/main/assets/emoji/[委屈]@2x.png new file mode 100644 index 0000000..3f136dd Binary files /dev/null and b/TIMCommon/timcommon/src/main/assets/emoji/[委屈]@2x.png differ diff --git a/TIMCommon/timcommon/src/main/assets/emoji/[害羞]@2x.png b/TIMCommon/timcommon/src/main/assets/emoji/[害羞]@2x.png new file mode 100644 index 0000000..30a9341 Binary files /dev/null and b/TIMCommon/timcommon/src/main/assets/emoji/[害羞]@2x.png differ diff --git a/TIMCommon/timcommon/src/main/assets/emoji/[尴尬]@2x.png b/TIMCommon/timcommon/src/main/assets/emoji/[尴尬]@2x.png new file mode 100644 index 0000000..62afd7e Binary files /dev/null and b/TIMCommon/timcommon/src/main/assets/emoji/[尴尬]@2x.png differ diff --git a/TIMCommon/timcommon/src/main/assets/emoji/[左哼哼]@2x.png b/TIMCommon/timcommon/src/main/assets/emoji/[左哼哼]@2x.png new file mode 100644 index 0000000..e6f64df Binary files /dev/null and b/TIMCommon/timcommon/src/main/assets/emoji/[左哼哼]@2x.png differ diff --git a/TIMCommon/timcommon/src/main/assets/emoji/[左太极]@2x.png b/TIMCommon/timcommon/src/main/assets/emoji/[左太极]@2x.png new file mode 100644 index 0000000..5a88a56 Binary files /dev/null and b/TIMCommon/timcommon/src/main/assets/emoji/[左太极]@2x.png differ diff --git a/TIMCommon/timcommon/src/main/assets/emoji/[左车头]@2x.png b/TIMCommon/timcommon/src/main/assets/emoji/[左车头]@2x.png new file mode 100644 index 0000000..e05c11c Binary files /dev/null and b/TIMCommon/timcommon/src/main/assets/emoji/[左车头]@2x.png differ diff --git a/TIMCommon/timcommon/src/main/assets/emoji/[差劲]@2x.png b/TIMCommon/timcommon/src/main/assets/emoji/[差劲]@2x.png new file mode 100644 index 0000000..455365a Binary files /dev/null and b/TIMCommon/timcommon/src/main/assets/emoji/[差劲]@2x.png differ diff --git a/TIMCommon/timcommon/src/main/assets/emoji/[弱]@2x.png b/TIMCommon/timcommon/src/main/assets/emoji/[弱]@2x.png new file mode 100644 index 0000000..fe5fbf9 Binary files /dev/null and b/TIMCommon/timcommon/src/main/assets/emoji/[弱]@2x.png differ diff --git a/TIMCommon/timcommon/src/main/assets/emoji/[强]@2x.png b/TIMCommon/timcommon/src/main/assets/emoji/[强]@2x.png new file mode 100644 index 0000000..f4f7a5b Binary files /dev/null and b/TIMCommon/timcommon/src/main/assets/emoji/[强]@2x.png differ diff --git a/TIMCommon/timcommon/src/main/assets/emoji/[彩带]@2x.png b/TIMCommon/timcommon/src/main/assets/emoji/[彩带]@2x.png new file mode 100644 index 0000000..88cd3f0 Binary files /dev/null and b/TIMCommon/timcommon/src/main/assets/emoji/[彩带]@2x.png differ diff --git a/TIMCommon/timcommon/src/main/assets/emoji/[彩球]@2x.png b/TIMCommon/timcommon/src/main/assets/emoji/[彩球]@2x.png new file mode 100644 index 0000000..8a60eed Binary files /dev/null and b/TIMCommon/timcommon/src/main/assets/emoji/[彩球]@2x.png differ diff --git a/TIMCommon/timcommon/src/main/assets/emoji/[得意]@2x.png b/TIMCommon/timcommon/src/main/assets/emoji/[得意]@2x.png new file mode 100644 index 0000000..4027f8c Binary files /dev/null and b/TIMCommon/timcommon/src/main/assets/emoji/[得意]@2x.png differ diff --git a/TIMCommon/timcommon/src/main/assets/emoji/[微笑]@2x.png b/TIMCommon/timcommon/src/main/assets/emoji/[微笑]@2x.png new file mode 100644 index 0000000..bb57a0a Binary files /dev/null and b/TIMCommon/timcommon/src/main/assets/emoji/[微笑]@2x.png differ diff --git a/TIMCommon/timcommon/src/main/assets/emoji/[心碎了]@2x.png b/TIMCommon/timcommon/src/main/assets/emoji/[心碎了]@2x.png new file mode 100644 index 0000000..a11ed0f Binary files /dev/null and b/TIMCommon/timcommon/src/main/assets/emoji/[心碎了]@2x.png differ diff --git a/TIMCommon/timcommon/src/main/assets/emoji/[快哭了]@2x.png b/TIMCommon/timcommon/src/main/assets/emoji/[快哭了]@2x.png new file mode 100644 index 0000000..8f49f69 Binary files /dev/null and b/TIMCommon/timcommon/src/main/assets/emoji/[快哭了]@2x.png differ diff --git a/TIMCommon/timcommon/src/main/assets/emoji/[怄火]@2x.png b/TIMCommon/timcommon/src/main/assets/emoji/[怄火]@2x.png new file mode 100644 index 0000000..7277dae Binary files /dev/null and b/TIMCommon/timcommon/src/main/assets/emoji/[怄火]@2x.png differ diff --git a/TIMCommon/timcommon/src/main/assets/emoji/[怒]@2x.png b/TIMCommon/timcommon/src/main/assets/emoji/[怒]@2x.png new file mode 100644 index 0000000..a4e4a69 Binary files /dev/null and b/TIMCommon/timcommon/src/main/assets/emoji/[怒]@2x.png differ diff --git a/TIMCommon/timcommon/src/main/assets/emoji/[惊恐]@2x.png b/TIMCommon/timcommon/src/main/assets/emoji/[惊恐]@2x.png new file mode 100644 index 0000000..617810a Binary files /dev/null and b/TIMCommon/timcommon/src/main/assets/emoji/[惊恐]@2x.png differ diff --git a/TIMCommon/timcommon/src/main/assets/emoji/[惊讶]@2x.png b/TIMCommon/timcommon/src/main/assets/emoji/[惊讶]@2x.png new file mode 100644 index 0000000..df93544 Binary files /dev/null and b/TIMCommon/timcommon/src/main/assets/emoji/[惊讶]@2x.png differ diff --git a/TIMCommon/timcommon/src/main/assets/emoji/[憨笑]@2x.png b/TIMCommon/timcommon/src/main/assets/emoji/[憨笑]@2x.png new file mode 100644 index 0000000..4c5b4ba Binary files /dev/null and b/TIMCommon/timcommon/src/main/assets/emoji/[憨笑]@2x.png differ diff --git a/TIMCommon/timcommon/src/main/assets/emoji/[手枪]@2x.png b/TIMCommon/timcommon/src/main/assets/emoji/[手枪]@2x.png new file mode 100644 index 0000000..58af826 Binary files /dev/null and b/TIMCommon/timcommon/src/main/assets/emoji/[手枪]@2x.png differ diff --git a/TIMCommon/timcommon/src/main/assets/emoji/[打哈欠]@2x.png b/TIMCommon/timcommon/src/main/assets/emoji/[打哈欠]@2x.png new file mode 100644 index 0000000..14490ce Binary files /dev/null and b/TIMCommon/timcommon/src/main/assets/emoji/[打哈欠]@2x.png differ diff --git a/TIMCommon/timcommon/src/main/assets/emoji/[抓狂]@2x.png b/TIMCommon/timcommon/src/main/assets/emoji/[抓狂]@2x.png new file mode 100644 index 0000000..0556a4d Binary files /dev/null and b/TIMCommon/timcommon/src/main/assets/emoji/[抓狂]@2x.png differ diff --git a/TIMCommon/timcommon/src/main/assets/emoji/[折磨]@2x.png b/TIMCommon/timcommon/src/main/assets/emoji/[折磨]@2x.png new file mode 100644 index 0000000..d9dfd53 Binary files /dev/null and b/TIMCommon/timcommon/src/main/assets/emoji/[折磨]@2x.png differ diff --git a/TIMCommon/timcommon/src/main/assets/emoji/[抠鼻]@2x.png b/TIMCommon/timcommon/src/main/assets/emoji/[抠鼻]@2x.png new file mode 100644 index 0000000..cbc61e8 Binary files /dev/null and b/TIMCommon/timcommon/src/main/assets/emoji/[抠鼻]@2x.png differ diff --git a/TIMCommon/timcommon/src/main/assets/emoji/[抱抱]@2x.png b/TIMCommon/timcommon/src/main/assets/emoji/[抱抱]@2x.png new file mode 100644 index 0000000..828971e Binary files /dev/null and b/TIMCommon/timcommon/src/main/assets/emoji/[抱抱]@2x.png differ diff --git a/TIMCommon/timcommon/src/main/assets/emoji/[抱拳]@2x.png b/TIMCommon/timcommon/src/main/assets/emoji/[抱拳]@2x.png new file mode 100644 index 0000000..95b5ee0 Binary files /dev/null and b/TIMCommon/timcommon/src/main/assets/emoji/[抱拳]@2x.png differ diff --git a/TIMCommon/timcommon/src/main/assets/emoji/[拳头]@2x.png b/TIMCommon/timcommon/src/main/assets/emoji/[拳头]@2x.png new file mode 100644 index 0000000..6d501d3 Binary files /dev/null and b/TIMCommon/timcommon/src/main/assets/emoji/[拳头]@2x.png differ diff --git a/TIMCommon/timcommon/src/main/assets/emoji/[挥手]@2x.png b/TIMCommon/timcommon/src/main/assets/emoji/[挥手]@2x.png new file mode 100644 index 0000000..ad5d3e0 Binary files /dev/null and b/TIMCommon/timcommon/src/main/assets/emoji/[挥手]@2x.png differ diff --git a/TIMCommon/timcommon/src/main/assets/emoji/[握手]@2x.png b/TIMCommon/timcommon/src/main/assets/emoji/[握手]@2x.png new file mode 100644 index 0000000..10a615b Binary files /dev/null and b/TIMCommon/timcommon/src/main/assets/emoji/[握手]@2x.png differ diff --git a/TIMCommon/timcommon/src/main/assets/emoji/[撇嘴]@2x.png b/TIMCommon/timcommon/src/main/assets/emoji/[撇嘴]@2x.png new file mode 100644 index 0000000..d8839f4 Binary files /dev/null and b/TIMCommon/timcommon/src/main/assets/emoji/[撇嘴]@2x.png differ diff --git a/TIMCommon/timcommon/src/main/assets/emoji/[擦汗]@2x.png b/TIMCommon/timcommon/src/main/assets/emoji/[擦汗]@2x.png new file mode 100644 index 0000000..8804e98 Binary files /dev/null and b/TIMCommon/timcommon/src/main/assets/emoji/[擦汗]@2x.png differ diff --git a/TIMCommon/timcommon/src/main/assets/emoji/[敲打]@2x.png b/TIMCommon/timcommon/src/main/assets/emoji/[敲打]@2x.png new file mode 100644 index 0000000..3dfff7b Binary files /dev/null and b/TIMCommon/timcommon/src/main/assets/emoji/[敲打]@2x.png differ diff --git a/TIMCommon/timcommon/src/main/assets/emoji/[晕]@2x.png b/TIMCommon/timcommon/src/main/assets/emoji/[晕]@2x.png new file mode 100644 index 0000000..2bd83a4 Binary files /dev/null and b/TIMCommon/timcommon/src/main/assets/emoji/[晕]@2x.png differ diff --git a/TIMCommon/timcommon/src/main/assets/emoji/[月亮]@2x.png b/TIMCommon/timcommon/src/main/assets/emoji/[月亮]@2x.png new file mode 100644 index 0000000..eb37fb9 Binary files /dev/null and b/TIMCommon/timcommon/src/main/assets/emoji/[月亮]@2x.png differ diff --git a/TIMCommon/timcommon/src/main/assets/emoji/[棒棒糖]@2x.png b/TIMCommon/timcommon/src/main/assets/emoji/[棒棒糖]@2x.png new file mode 100644 index 0000000..4f58458 Binary files /dev/null and b/TIMCommon/timcommon/src/main/assets/emoji/[棒棒糖]@2x.png differ diff --git a/TIMCommon/timcommon/src/main/assets/emoji/[汽车]@2x.png b/TIMCommon/timcommon/src/main/assets/emoji/[汽车]@2x.png new file mode 100644 index 0000000..14363af Binary files /dev/null and b/TIMCommon/timcommon/src/main/assets/emoji/[汽车]@2x.png differ diff --git a/TIMCommon/timcommon/src/main/assets/emoji/[沙发]@2x.png b/TIMCommon/timcommon/src/main/assets/emoji/[沙发]@2x.png new file mode 100644 index 0000000..87d233a Binary files /dev/null and b/TIMCommon/timcommon/src/main/assets/emoji/[沙发]@2x.png differ diff --git a/TIMCommon/timcommon/src/main/assets/emoji/[流汗]@2x.png b/TIMCommon/timcommon/src/main/assets/emoji/[流汗]@2x.png new file mode 100644 index 0000000..d835eeb Binary files /dev/null and b/TIMCommon/timcommon/src/main/assets/emoji/[流汗]@2x.png differ diff --git a/TIMCommon/timcommon/src/main/assets/emoji/[流泪]@2x.png b/TIMCommon/timcommon/src/main/assets/emoji/[流泪]@2x.png new file mode 100644 index 0000000..61dab0a Binary files /dev/null and b/TIMCommon/timcommon/src/main/assets/emoji/[流泪]@2x.png differ diff --git a/TIMCommon/timcommon/src/main/assets/emoji/[激动]@2x.png b/TIMCommon/timcommon/src/main/assets/emoji/[激动]@2x.png new file mode 100644 index 0000000..262b317 Binary files /dev/null and b/TIMCommon/timcommon/src/main/assets/emoji/[激动]@2x.png differ diff --git a/TIMCommon/timcommon/src/main/assets/emoji/[灯泡]@2x.png b/TIMCommon/timcommon/src/main/assets/emoji/[灯泡]@2x.png new file mode 100644 index 0000000..b61a829 Binary files /dev/null and b/TIMCommon/timcommon/src/main/assets/emoji/[灯泡]@2x.png differ diff --git a/TIMCommon/timcommon/src/main/assets/emoji/[炸弹]@2x.png b/TIMCommon/timcommon/src/main/assets/emoji/[炸弹]@2x.png new file mode 100644 index 0000000..62b5736 Binary files /dev/null and b/TIMCommon/timcommon/src/main/assets/emoji/[炸弹]@2x.png differ diff --git a/TIMCommon/timcommon/src/main/assets/emoji/[熊猫]@2x.png b/TIMCommon/timcommon/src/main/assets/emoji/[熊猫]@2x.png new file mode 100644 index 0000000..e4088f8 Binary files /dev/null and b/TIMCommon/timcommon/src/main/assets/emoji/[熊猫]@2x.png differ diff --git a/TIMCommon/timcommon/src/main/assets/emoji/[爆筋]@2x.png b/TIMCommon/timcommon/src/main/assets/emoji/[爆筋]@2x.png new file mode 100644 index 0000000..af054a7 Binary files /dev/null and b/TIMCommon/timcommon/src/main/assets/emoji/[爆筋]@2x.png differ diff --git a/TIMCommon/timcommon/src/main/assets/emoji/[爱你]@2x.png b/TIMCommon/timcommon/src/main/assets/emoji/[爱你]@2x.png new file mode 100644 index 0000000..5be2bd0 Binary files /dev/null and b/TIMCommon/timcommon/src/main/assets/emoji/[爱你]@2x.png differ diff --git a/TIMCommon/timcommon/src/main/assets/emoji/[爱心]@2x.png b/TIMCommon/timcommon/src/main/assets/emoji/[爱心]@2x.png new file mode 100644 index 0000000..4474769 Binary files /dev/null and b/TIMCommon/timcommon/src/main/assets/emoji/[爱心]@2x.png differ diff --git a/TIMCommon/timcommon/src/main/assets/emoji/[爱情]@2x.png b/TIMCommon/timcommon/src/main/assets/emoji/[爱情]@2x.png new file mode 100644 index 0000000..4f2263d Binary files /dev/null and b/TIMCommon/timcommon/src/main/assets/emoji/[爱情]@2x.png differ diff --git a/TIMCommon/timcommon/src/main/assets/emoji/[猪头]@2x.png b/TIMCommon/timcommon/src/main/assets/emoji/[猪头]@2x.png new file mode 100644 index 0000000..535548b Binary files /dev/null and b/TIMCommon/timcommon/src/main/assets/emoji/[猪头]@2x.png differ diff --git a/TIMCommon/timcommon/src/main/assets/emoji/[猫咪]@2x.png b/TIMCommon/timcommon/src/main/assets/emoji/[猫咪]@2x.png new file mode 100644 index 0000000..c0b1a89 Binary files /dev/null and b/TIMCommon/timcommon/src/main/assets/emoji/[猫咪]@2x.png differ diff --git a/TIMCommon/timcommon/src/main/assets/emoji/[献吻]@2x.png b/TIMCommon/timcommon/src/main/assets/emoji/[献吻]@2x.png new file mode 100644 index 0000000..33da041 Binary files /dev/null and b/TIMCommon/timcommon/src/main/assets/emoji/[献吻]@2x.png differ diff --git a/TIMCommon/timcommon/src/main/assets/emoji/[玫瑰]@2x.png b/TIMCommon/timcommon/src/main/assets/emoji/[玫瑰]@2x.png new file mode 100644 index 0000000..faac04b Binary files /dev/null and b/TIMCommon/timcommon/src/main/assets/emoji/[玫瑰]@2x.png differ diff --git a/TIMCommon/timcommon/src/main/assets/emoji/[瓢虫]@2x.png b/TIMCommon/timcommon/src/main/assets/emoji/[瓢虫]@2x.png new file mode 100644 index 0000000..8fcd467 Binary files /dev/null and b/TIMCommon/timcommon/src/main/assets/emoji/[瓢虫]@2x.png differ diff --git a/TIMCommon/timcommon/src/main/assets/emoji/[疑问]@2x.png b/TIMCommon/timcommon/src/main/assets/emoji/[疑问]@2x.png new file mode 100644 index 0000000..72376dd Binary files /dev/null and b/TIMCommon/timcommon/src/main/assets/emoji/[疑问]@2x.png differ diff --git a/TIMCommon/timcommon/src/main/assets/emoji/[白眼]@2x.png b/TIMCommon/timcommon/src/main/assets/emoji/[白眼]@2x.png new file mode 100644 index 0000000..0a8a259 Binary files /dev/null and b/TIMCommon/timcommon/src/main/assets/emoji/[白眼]@2x.png differ diff --git a/TIMCommon/timcommon/src/main/assets/emoji/[皮球]@2x.png b/TIMCommon/timcommon/src/main/assets/emoji/[皮球]@2x.png new file mode 100644 index 0000000..6dcf45e Binary files /dev/null and b/TIMCommon/timcommon/src/main/assets/emoji/[皮球]@2x.png differ diff --git a/TIMCommon/timcommon/src/main/assets/emoji/[睡觉]@2x.png b/TIMCommon/timcommon/src/main/assets/emoji/[睡觉]@2x.png new file mode 100644 index 0000000..aafb55f Binary files /dev/null and b/TIMCommon/timcommon/src/main/assets/emoji/[睡觉]@2x.png differ diff --git a/TIMCommon/timcommon/src/main/assets/emoji/[磕头]@2x.png b/TIMCommon/timcommon/src/main/assets/emoji/[磕头]@2x.png new file mode 100644 index 0000000..817923a Binary files /dev/null and b/TIMCommon/timcommon/src/main/assets/emoji/[磕头]@2x.png differ diff --git a/TIMCommon/timcommon/src/main/assets/emoji/[示爱]@2x.png b/TIMCommon/timcommon/src/main/assets/emoji/[示爱]@2x.png new file mode 100644 index 0000000..7be52ca Binary files /dev/null and b/TIMCommon/timcommon/src/main/assets/emoji/[示爱]@2x.png differ diff --git a/TIMCommon/timcommon/src/main/assets/emoji/[礼品袋]@2x.png b/TIMCommon/timcommon/src/main/assets/emoji/[礼品袋]@2x.png new file mode 100644 index 0000000..5d017c8 Binary files /dev/null and b/TIMCommon/timcommon/src/main/assets/emoji/[礼品袋]@2x.png differ diff --git a/TIMCommon/timcommon/src/main/assets/emoji/[礼物]@2x.png b/TIMCommon/timcommon/src/main/assets/emoji/[礼物]@2x.png new file mode 100644 index 0000000..5b606ff Binary files /dev/null and b/TIMCommon/timcommon/src/main/assets/emoji/[礼物]@2x.png differ diff --git a/TIMCommon/timcommon/src/main/assets/emoji/[篮球]@2x.png b/TIMCommon/timcommon/src/main/assets/emoji/[篮球]@2x.png new file mode 100644 index 0000000..aee5674 Binary files /dev/null and b/TIMCommon/timcommon/src/main/assets/emoji/[篮球]@2x.png differ diff --git a/TIMCommon/timcommon/src/main/assets/emoji/[米饭]@2x.png b/TIMCommon/timcommon/src/main/assets/emoji/[米饭]@2x.png new file mode 100644 index 0000000..2c469a9 Binary files /dev/null and b/TIMCommon/timcommon/src/main/assets/emoji/[米饭]@2x.png differ diff --git a/TIMCommon/timcommon/src/main/assets/emoji/[糗大了]@2x.png b/TIMCommon/timcommon/src/main/assets/emoji/[糗大了]@2x.png new file mode 100644 index 0000000..4373f1b Binary files /dev/null and b/TIMCommon/timcommon/src/main/assets/emoji/[糗大了]@2x.png differ diff --git a/TIMCommon/timcommon/src/main/assets/emoji/[红双喜]@2x.png b/TIMCommon/timcommon/src/main/assets/emoji/[红双喜]@2x.png new file mode 100644 index 0000000..b37d964 Binary files /dev/null and b/TIMCommon/timcommon/src/main/assets/emoji/[红双喜]@2x.png differ diff --git a/TIMCommon/timcommon/src/main/assets/emoji/[红灯笼]@2x.png b/TIMCommon/timcommon/src/main/assets/emoji/[红灯笼]@2x.png new file mode 100644 index 0000000..bb432af Binary files /dev/null and b/TIMCommon/timcommon/src/main/assets/emoji/[红灯笼]@2x.png differ diff --git a/TIMCommon/timcommon/src/main/assets/emoji/[纸巾]@2x.png b/TIMCommon/timcommon/src/main/assets/emoji/[纸巾]@2x.png new file mode 100644 index 0000000..10469fe Binary files /dev/null and b/TIMCommon/timcommon/src/main/assets/emoji/[纸巾]@2x.png differ diff --git a/TIMCommon/timcommon/src/main/assets/emoji/[胜利]@2x.png b/TIMCommon/timcommon/src/main/assets/emoji/[胜利]@2x.png new file mode 100644 index 0000000..ac5cefa Binary files /dev/null and b/TIMCommon/timcommon/src/main/assets/emoji/[胜利]@2x.png differ diff --git a/TIMCommon/timcommon/src/main/assets/emoji/[色]@2x.png b/TIMCommon/timcommon/src/main/assets/emoji/[色]@2x.png new file mode 100644 index 0000000..a45e109 Binary files /dev/null and b/TIMCommon/timcommon/src/main/assets/emoji/[色]@2x.png differ diff --git a/TIMCommon/timcommon/src/main/assets/emoji/[药]@2x.png b/TIMCommon/timcommon/src/main/assets/emoji/[药]@2x.png new file mode 100644 index 0000000..1588f9e Binary files /dev/null and b/TIMCommon/timcommon/src/main/assets/emoji/[药]@2x.png differ diff --git a/TIMCommon/timcommon/src/main/assets/emoji/[菜刀]@2x.png b/TIMCommon/timcommon/src/main/assets/emoji/[菜刀]@2x.png new file mode 100644 index 0000000..69072bb Binary files /dev/null and b/TIMCommon/timcommon/src/main/assets/emoji/[菜刀]@2x.png differ diff --git a/TIMCommon/timcommon/src/main/assets/emoji/[蛋糕]@2x.png b/TIMCommon/timcommon/src/main/assets/emoji/[蛋糕]@2x.png new file mode 100644 index 0000000..279eb79 Binary files /dev/null and b/TIMCommon/timcommon/src/main/assets/emoji/[蛋糕]@2x.png differ diff --git a/TIMCommon/timcommon/src/main/assets/emoji/[蜡烛]@2x.png b/TIMCommon/timcommon/src/main/assets/emoji/[蜡烛]@2x.png new file mode 100644 index 0000000..9f06f3e Binary files /dev/null and b/TIMCommon/timcommon/src/main/assets/emoji/[蜡烛]@2x.png differ diff --git a/TIMCommon/timcommon/src/main/assets/emoji/[街舞]@2x.png b/TIMCommon/timcommon/src/main/assets/emoji/[街舞]@2x.png new file mode 100644 index 0000000..c8045d8 Binary files /dev/null and b/TIMCommon/timcommon/src/main/assets/emoji/[街舞]@2x.png differ diff --git a/TIMCommon/timcommon/src/main/assets/emoji/[衰]@2x.png b/TIMCommon/timcommon/src/main/assets/emoji/[衰]@2x.png new file mode 100644 index 0000000..b055d46 Binary files /dev/null and b/TIMCommon/timcommon/src/main/assets/emoji/[衰]@2x.png differ diff --git a/TIMCommon/timcommon/src/main/assets/emoji/[西瓜]@2x.png b/TIMCommon/timcommon/src/main/assets/emoji/[西瓜]@2x.png new file mode 100644 index 0000000..0d79d2a Binary files /dev/null and b/TIMCommon/timcommon/src/main/assets/emoji/[西瓜]@2x.png differ diff --git a/TIMCommon/timcommon/src/main/assets/emoji/[调皮]@2x.png b/TIMCommon/timcommon/src/main/assets/emoji/[调皮]@2x.png new file mode 100644 index 0000000..11f0646 Binary files /dev/null and b/TIMCommon/timcommon/src/main/assets/emoji/[调皮]@2x.png differ diff --git a/TIMCommon/timcommon/src/main/assets/emoji/[象棋]@2x.png b/TIMCommon/timcommon/src/main/assets/emoji/[象棋]@2x.png new file mode 100644 index 0000000..cf35cad Binary files /dev/null and b/TIMCommon/timcommon/src/main/assets/emoji/[象棋]@2x.png differ diff --git a/TIMCommon/timcommon/src/main/assets/emoji/[跳绳]@2x.png b/TIMCommon/timcommon/src/main/assets/emoji/[跳绳]@2x.png new file mode 100644 index 0000000..17cffd3 Binary files /dev/null and b/TIMCommon/timcommon/src/main/assets/emoji/[跳绳]@2x.png differ diff --git a/TIMCommon/timcommon/src/main/assets/emoji/[跳跳]@2x.png b/TIMCommon/timcommon/src/main/assets/emoji/[跳跳]@2x.png new file mode 100644 index 0000000..b5646c6 Binary files /dev/null and b/TIMCommon/timcommon/src/main/assets/emoji/[跳跳]@2x.png differ diff --git a/TIMCommon/timcommon/src/main/assets/emoji/[车厢]@2x.png b/TIMCommon/timcommon/src/main/assets/emoji/[车厢]@2x.png new file mode 100644 index 0000000..a1276eb Binary files /dev/null and b/TIMCommon/timcommon/src/main/assets/emoji/[车厢]@2x.png differ diff --git a/TIMCommon/timcommon/src/main/assets/emoji/[转圈]@2x.png b/TIMCommon/timcommon/src/main/assets/emoji/[转圈]@2x.png new file mode 100644 index 0000000..2c15311 Binary files /dev/null and b/TIMCommon/timcommon/src/main/assets/emoji/[转圈]@2x.png differ diff --git a/TIMCommon/timcommon/src/main/assets/emoji/[鄙视]@2x.png b/TIMCommon/timcommon/src/main/assets/emoji/[鄙视]@2x.png new file mode 100644 index 0000000..65b1aec Binary files /dev/null and b/TIMCommon/timcommon/src/main/assets/emoji/[鄙视]@2x.png differ diff --git a/TIMCommon/timcommon/src/main/assets/emoji/[酷]@2x.png b/TIMCommon/timcommon/src/main/assets/emoji/[酷]@2x.png new file mode 100644 index 0000000..06d8163 Binary files /dev/null and b/TIMCommon/timcommon/src/main/assets/emoji/[酷]@2x.png differ diff --git a/TIMCommon/timcommon/src/main/assets/emoji/[钞票]@2x.png b/TIMCommon/timcommon/src/main/assets/emoji/[钞票]@2x.png new file mode 100644 index 0000000..7218532 Binary files /dev/null and b/TIMCommon/timcommon/src/main/assets/emoji/[钞票]@2x.png differ diff --git a/TIMCommon/timcommon/src/main/assets/emoji/[钻戒]@2x.png b/TIMCommon/timcommon/src/main/assets/emoji/[钻戒]@2x.png new file mode 100644 index 0000000..787cb6a Binary files /dev/null and b/TIMCommon/timcommon/src/main/assets/emoji/[钻戒]@2x.png differ diff --git a/TIMCommon/timcommon/src/main/assets/emoji/[闪电]@2x.png b/TIMCommon/timcommon/src/main/assets/emoji/[闪电]@2x.png new file mode 100644 index 0000000..84f1526 Binary files /dev/null and b/TIMCommon/timcommon/src/main/assets/emoji/[闪电]@2x.png differ diff --git a/TIMCommon/timcommon/src/main/assets/emoji/[闭嘴]@2x.png b/TIMCommon/timcommon/src/main/assets/emoji/[闭嘴]@2x.png new file mode 100644 index 0000000..d1d3d3b Binary files /dev/null and b/TIMCommon/timcommon/src/main/assets/emoji/[闭嘴]@2x.png differ diff --git a/TIMCommon/timcommon/src/main/assets/emoji/[闹钟]@2x.png b/TIMCommon/timcommon/src/main/assets/emoji/[闹钟]@2x.png new file mode 100644 index 0000000..daade80 Binary files /dev/null and b/TIMCommon/timcommon/src/main/assets/emoji/[闹钟]@2x.png differ diff --git a/TIMCommon/timcommon/src/main/assets/emoji/[阴险]@2x.png b/TIMCommon/timcommon/src/main/assets/emoji/[阴险]@2x.png new file mode 100644 index 0000000..e277198 Binary files /dev/null and b/TIMCommon/timcommon/src/main/assets/emoji/[阴险]@2x.png differ diff --git a/TIMCommon/timcommon/src/main/assets/emoji/[难过]@2x.png b/TIMCommon/timcommon/src/main/assets/emoji/[难过]@2x.png new file mode 100644 index 0000000..35bd8ec Binary files /dev/null and b/TIMCommon/timcommon/src/main/assets/emoji/[难过]@2x.png differ diff --git a/TIMCommon/timcommon/src/main/assets/emoji/[雨伞]@2x.png b/TIMCommon/timcommon/src/main/assets/emoji/[雨伞]@2x.png new file mode 100644 index 0000000..5011da4 Binary files /dev/null and b/TIMCommon/timcommon/src/main/assets/emoji/[雨伞]@2x.png differ diff --git a/TIMCommon/timcommon/src/main/assets/emoji/[青蛙]@2x.png b/TIMCommon/timcommon/src/main/assets/emoji/[青蛙]@2x.png new file mode 100644 index 0000000..4b69277 Binary files /dev/null and b/TIMCommon/timcommon/src/main/assets/emoji/[青蛙]@2x.png differ diff --git a/TIMCommon/timcommon/src/main/assets/emoji/[面条]@2x.png b/TIMCommon/timcommon/src/main/assets/emoji/[面条]@2x.png new file mode 100644 index 0000000..78e8e71 Binary files /dev/null and b/TIMCommon/timcommon/src/main/assets/emoji/[面条]@2x.png differ diff --git a/TIMCommon/timcommon/src/main/assets/emoji/[鞭炮]@2x.png b/TIMCommon/timcommon/src/main/assets/emoji/[鞭炮]@2x.png new file mode 100644 index 0000000..ec12c3e Binary files /dev/null and b/TIMCommon/timcommon/src/main/assets/emoji/[鞭炮]@2x.png differ diff --git a/TIMCommon/timcommon/src/main/assets/emoji/[风车]@2x.png b/TIMCommon/timcommon/src/main/assets/emoji/[风车]@2x.png new file mode 100644 index 0000000..02321c3 Binary files /dev/null and b/TIMCommon/timcommon/src/main/assets/emoji/[风车]@2x.png differ diff --git a/TIMCommon/timcommon/src/main/assets/emoji/[飞吻]@2x.png b/TIMCommon/timcommon/src/main/assets/emoji/[飞吻]@2x.png new file mode 100644 index 0000000..a9a4e16 Binary files /dev/null and b/TIMCommon/timcommon/src/main/assets/emoji/[飞吻]@2x.png differ diff --git a/TIMCommon/timcommon/src/main/assets/emoji/[飞机]@2x.png b/TIMCommon/timcommon/src/main/assets/emoji/[飞机]@2x.png new file mode 100644 index 0000000..f407930 Binary files /dev/null and b/TIMCommon/timcommon/src/main/assets/emoji/[飞机]@2x.png differ diff --git a/TIMCommon/timcommon/src/main/assets/emoji/[饥饿]@2x.png b/TIMCommon/timcommon/src/main/assets/emoji/[饥饿]@2x.png new file mode 100644 index 0000000..5bbd805 Binary files /dev/null and b/TIMCommon/timcommon/src/main/assets/emoji/[饥饿]@2x.png differ diff --git a/TIMCommon/timcommon/src/main/assets/emoji/[香蕉]@2x.png b/TIMCommon/timcommon/src/main/assets/emoji/[香蕉]@2x.png new file mode 100644 index 0000000..b3bde3c Binary files /dev/null and b/TIMCommon/timcommon/src/main/assets/emoji/[香蕉]@2x.png differ diff --git a/TIMCommon/timcommon/src/main/assets/emoji/[骷髅]@2x.png b/TIMCommon/timcommon/src/main/assets/emoji/[骷髅]@2x.png new file mode 100644 index 0000000..c4ee653 Binary files /dev/null and b/TIMCommon/timcommon/src/main/assets/emoji/[骷髅]@2x.png differ diff --git a/TIMCommon/timcommon/src/main/assets/emoji/[麦克风]@2x.png b/TIMCommon/timcommon/src/main/assets/emoji/[麦克风]@2x.png new file mode 100644 index 0000000..0e0ae99 Binary files /dev/null and b/TIMCommon/timcommon/src/main/assets/emoji/[麦克风]@2x.png differ diff --git a/TIMCommon/timcommon/src/main/assets/emoji/[麻将]@2x.png b/TIMCommon/timcommon/src/main/assets/emoji/[麻将]@2x.png new file mode 100644 index 0000000..011d8e2 Binary files /dev/null and b/TIMCommon/timcommon/src/main/assets/emoji/[麻将]@2x.png differ diff --git a/TIMCommon/timcommon/src/main/assets/emoji/[鼓掌]@2x.png b/TIMCommon/timcommon/src/main/assets/emoji/[鼓掌]@2x.png new file mode 100644 index 0000000..18cd1d2 Binary files /dev/null and b/TIMCommon/timcommon/src/main/assets/emoji/[鼓掌]@2x.png differ diff --git a/TIMCommon/timcommon/src/main/assets/emoji/[龇牙]@2x.png b/TIMCommon/timcommon/src/main/assets/emoji/[龇牙]@2x.png new file mode 100644 index 0000000..4fed0fc Binary files /dev/null and b/TIMCommon/timcommon/src/main/assets/emoji/[龇牙]@2x.png differ diff --git a/TIMCommon/timcommon/src/main/java/com/tencent/qcloud/tuikit/timcommon/TIMCommonConfig.java b/TIMCommon/timcommon/src/main/java/com/tencent/qcloud/tuikit/timcommon/TIMCommonConfig.java new file mode 100644 index 0000000..f380165 --- /dev/null +++ b/TIMCommon/timcommon/src/main/java/com/tencent/qcloud/tuikit/timcommon/TIMCommonConfig.java @@ -0,0 +1,68 @@ +package com.tencent.qcloud.tuikit.timcommon; + +public class TIMCommonConfig { + private static boolean enableGroupGridAvatar = true; + private static int defaultAvatarImage; + private static int defaultGroupAvatarImage; + + /** + * 获取群组会话否展示九宫格样式的头像,默认为 true + * Gets whether to display the avatar in the nine-square grid style in the group conversation, the default is true + */ + public static boolean isEnableGroupGridAvatar() { + return enableGroupGridAvatar; + } + + /** + * 设置群组会话是否展示九宫格样式的头像 + * Set whether to display the avatar in the nine-square grid style in group conversations + */ + public static void setEnableGroupGridAvatar(boolean enableGroupGridAvatar) { + TIMCommonConfig.enableGroupGridAvatar = enableGroupGridAvatar; + } + + /** + * 获取 c2c 会话的默认头像 + * + * Get the default avatar for c2c conversation + * + * @return + */ + public static int getDefaultAvatarImage() { + return defaultAvatarImage; + } + + /** + * 设置 c2c 会话的默认头像 + * + *Set the default avatar for c2c conversation + * + * @return + */ + public static void setDefaultAvatarImage(int defaultAvatarImage) { + TIMCommonConfig.defaultAvatarImage = defaultAvatarImage; + } + + /** + * 获取 group 会话的默认头像 + * + * Get the default avatar for group conversation + * + * @return + */ + public static int getDefaultGroupAvatarImage() { + return defaultGroupAvatarImage; + } + + /** + * 设置 group 会话的默认头像 + * + *Set the default avatar for group conversation + * + * @return + */ + public static void setDefaultGroupAvatarImage(int defaultGroupAvatarImage) { + TIMCommonConfig.defaultGroupAvatarImage = defaultGroupAvatarImage; + } + +} diff --git a/TIMCommon/timcommon/src/main/java/com/tencent/qcloud/tuikit/timcommon/TIMCommonService.java b/TIMCommon/timcommon/src/main/java/com/tencent/qcloud/tuikit/timcommon/TIMCommonService.java new file mode 100644 index 0000000..636ff0e --- /dev/null +++ b/TIMCommon/timcommon/src/main/java/com/tencent/qcloud/tuikit/timcommon/TIMCommonService.java @@ -0,0 +1,28 @@ +package com.tencent.qcloud.tuikit.timcommon; + +import android.content.Context; + +import com.tencent.qcloud.tuicore.ServiceInitializer; + +public class TIMCommonService extends ServiceInitializer { + + @Override + public void init(Context context) { + super.init(context); + } + + @Override + public int getLightThemeResId() { + return R.style.TIMCommonLightTheme; + } + + @Override + public int getLivelyThemeResId() { + return R.style.TIMCommonLivelyTheme; + } + + @Override + public int getSeriousThemeResId() { + return R.style.TIMCommonSeriousTheme; + } +} diff --git a/TIMCommon/timcommon/src/main/java/com/tencent/qcloud/tuikit/timcommon/bean/MessageFeature.java b/TIMCommon/timcommon/src/main/java/com/tencent/qcloud/tuikit/timcommon/bean/MessageFeature.java new file mode 100644 index 0000000..e446579 --- /dev/null +++ b/TIMCommon/timcommon/src/main/java/com/tencent/qcloud/tuikit/timcommon/bean/MessageFeature.java @@ -0,0 +1,33 @@ +package com.tencent.qcloud.tuikit.timcommon.bean; + +import java.io.Serializable; + +/* + * 通过消息携带功能宏,主要用于新老版本兼容,使用 cloudCustomData 字段。 + * 像对方输入中功能。 + * + * Carrying function macros through messages,Mainly used to be compatible with old and new versions,Use the cloudCustomData field. + * Such as Typing function. + */ +public class MessageFeature implements Serializable { + public static final int VERSION = 1; + + private int needTyping = 1; // message typing feature ... + private int version = VERSION; + + public int getVersion() { + return version; + } + + public void setVersion(int version) { + this.version = version; + } + + public int getNeedTyping() { + return needTyping; + } + + public void setNeedTyping(int needTyping) { + this.needTyping = needTyping; + } +} diff --git a/TIMCommon/timcommon/src/main/java/com/tencent/qcloud/tuikit/timcommon/bean/MessageReactBean.java b/TIMCommon/timcommon/src/main/java/com/tencent/qcloud/tuikit/timcommon/bean/MessageReactBean.java new file mode 100644 index 0000000..9bd3d53 --- /dev/null +++ b/TIMCommon/timcommon/src/main/java/com/tencent/qcloud/tuikit/timcommon/bean/MessageReactBean.java @@ -0,0 +1,66 @@ +package com.tencent.qcloud.tuikit.timcommon.bean; + +import java.io.Serializable; +import java.util.LinkedHashMap; +import java.util.LinkedHashSet; +import java.util.Map; +import java.util.Set; + +public class MessageReactBean implements Serializable { + public static final int VERSION = 1; + + private Map> reacts; + private int version = VERSION; + + private transient Map reactUserBeanMap; + + public void operateUser(String emojiId, String userId) { + if (reacts == null) { + reacts = new LinkedHashMap<>(); + } + Set userList = reacts.get(emojiId); + if (userList == null) { + userList = new LinkedHashSet<>(); + reacts.put(emojiId, userList); + } + if (userList.contains(userId)) { + userList.remove(userId); + } else { + userList.add(userId); + } + if (userList.isEmpty()) { + reacts.remove(emojiId); + } + } + + public Map> getReacts() { + return reacts; + } + + public void setReacts(Map> reacts) { + this.reacts = reacts; + } + + public int getReactSize() { + if (reacts != null) { + return reacts.size(); + } + return 0; + } + + public void setReactUserBeanMap(Map reactUserBeanMap) { + this.reactUserBeanMap = reactUserBeanMap; + } + + public Map getReactUserBeanMap() { + return reactUserBeanMap; + } + + public int getVersion() { + return version; + } + + public void setVersion(int version) { + this.version = version; + } +} diff --git a/TIMCommon/timcommon/src/main/java/com/tencent/qcloud/tuikit/timcommon/bean/MessageReceiptInfo.java b/TIMCommon/timcommon/src/main/java/com/tencent/qcloud/tuikit/timcommon/bean/MessageReceiptInfo.java new file mode 100644 index 0000000..069db17 --- /dev/null +++ b/TIMCommon/timcommon/src/main/java/com/tencent/qcloud/tuikit/timcommon/bean/MessageReceiptInfo.java @@ -0,0 +1,55 @@ +package com.tencent.qcloud.tuikit.timcommon.bean; + +import com.tencent.imsdk.v2.V2TIMMessageReceipt; + +import java.io.Serializable; + +public class MessageReceiptInfo implements Serializable { + private V2TIMMessageReceipt messageReceipt; + + public void setMessageReceipt(V2TIMMessageReceipt messageReceipt) { + this.messageReceipt = messageReceipt; + } + + public String getUserID() { + if (messageReceipt != null) { + return messageReceipt.getUserID(); + } + return null; + } + + public boolean isPeerRead() { + if (messageReceipt != null) { + return messageReceipt.isPeerRead(); + } + return false; + } + + public String getGroupID() { + if (messageReceipt != null) { + return messageReceipt.getGroupID(); + } + return null; + } + + public long getReadCount() { + if (messageReceipt != null) { + return messageReceipt.getReadCount(); + } + return 0; + } + + public long getUnreadCount() { + if (messageReceipt != null) { + return messageReceipt.getUnreadCount(); + } + return 0; + } + + public String getMsgID() { + if (messageReceipt != null) { + return messageReceipt.getMsgID(); + } + return null; + } +} diff --git a/TIMCommon/timcommon/src/main/java/com/tencent/qcloud/tuikit/timcommon/bean/MessageRepliesBean.java b/TIMCommon/timcommon/src/main/java/com/tencent/qcloud/tuikit/timcommon/bean/MessageRepliesBean.java new file mode 100644 index 0000000..53d7bbe --- /dev/null +++ b/TIMCommon/timcommon/src/main/java/com/tencent/qcloud/tuikit/timcommon/bean/MessageRepliesBean.java @@ -0,0 +1,115 @@ +package com.tencent.qcloud.tuikit.timcommon.bean; + +import android.text.TextUtils; + +import java.io.Serializable; +import java.util.ArrayList; +import java.util.List; + +public class MessageRepliesBean implements Serializable { + public static final int VERSION = 1; + + public static class ReplyBean implements Serializable { + private String messageID; + private String messageAbstract; + private String messageSender; + transient private String senderFaceUrl; + transient private String senderShowName; + public String getMessageID() { + return messageID; + } + + public void setMessageID(String messageID) { + this.messageID = messageID; + } + + public String getMessageAbstract() { + return messageAbstract; + } + + public void setMessageAbstract(String messageAbstract) { + this.messageAbstract = messageAbstract; + } + + public String getMessageSender() { + return messageSender; + } + + public void setMessageSender(String messageSender) { + this.messageSender = messageSender; + } + + public void setSenderFaceUrl(String senderFaceUrl) { + this.senderFaceUrl = senderFaceUrl; + } + + public String getSenderFaceUrl() { + return senderFaceUrl; + } + + public void setSenderShowName(String senderShowName) { + this.senderShowName = senderShowName; + } + + public String getSenderShowName() { + if (TextUtils.isEmpty(senderShowName)) { + return messageSender; + } + return senderShowName; + } + } + + private List replies; + private int version = VERSION; + + public void addReplyMessage(String messageId, String messageAbstract, String sender) { + if (replies == null) { + replies = new ArrayList<>(); + } + for (ReplyBean replyBean : replies) { + if (TextUtils.equals(replyBean.messageID, messageId)) { + return; + } + } + ReplyBean replyBean = new ReplyBean(); + replyBean.messageID = messageId; + replyBean.messageAbstract = messageAbstract; + replyBean.messageSender = sender; + replies.add(replyBean); + } + + public void removeReplyMessage(String messageID) { + if (replies == null) { + return; + } + for (ReplyBean replyBean : replies) { + if (TextUtils.equals(replyBean.messageID, messageID)) { + replies.remove(replyBean); + return; + } + } + } + + public void setVersion(int version) { + this.version = version; + } + + public int getVersion() { + return version; + } + + public List getReplies() { + return replies; + } + + public void setReplies(List replies) { + this.replies = replies; + } + + public int getRepliesSize() { + if (replies != null) { + return replies.size(); + } + return 0; + } +} diff --git a/TIMCommon/timcommon/src/main/java/com/tencent/qcloud/tuikit/timcommon/bean/ReactUserBean.java b/TIMCommon/timcommon/src/main/java/com/tencent/qcloud/tuikit/timcommon/bean/ReactUserBean.java new file mode 100644 index 0000000..16622ab --- /dev/null +++ b/TIMCommon/timcommon/src/main/java/com/tencent/qcloud/tuikit/timcommon/bean/ReactUserBean.java @@ -0,0 +1,65 @@ +package com.tencent.qcloud.tuikit.timcommon.bean; + +import android.text.TextUtils; + +import java.io.Serializable; + +public class ReactUserBean implements Serializable { + private String userId; + private String nikeName; + private String nameCard; + private String friendRemark; + private String faceUrl; + + public String getUserId() { + return userId; + } + + public void setUserId(String userId) { + this.userId = userId; + } + + public String getNikeName() { + return nikeName; + } + + public void setNikeName(String nikeName) { + this.nikeName = nikeName; + } + + public String getFriendRemark() { + return friendRemark; + } + + public void setFriendRemark(String friendRemark) { + this.friendRemark = friendRemark; + } + + public void setNameCard(String nameCard) { + this.nameCard = nameCard; + } + + public String getNameCard() { + return nameCard; + } + + public String getDisplayString() { + if (!TextUtils.isEmpty(nameCard)) { + return nameCard; + } else if (!TextUtils.isEmpty(friendRemark)) { + return friendRemark; + } else if (!TextUtils.isEmpty(nikeName)) { + return nikeName; + } else { + return userId; + } + } + + public String getFaceUrl() { + return faceUrl; + } + + public void setFaceUrl(String faceUrl) { + this.faceUrl = faceUrl; + } +} diff --git a/TIMCommon/timcommon/src/main/java/com/tencent/qcloud/tuikit/timcommon/bean/TUIMessageBean.java b/TIMCommon/timcommon/src/main/java/com/tencent/qcloud/tuikit/timcommon/bean/TUIMessageBean.java new file mode 100644 index 0000000..41aa82f --- /dev/null +++ b/TIMCommon/timcommon/src/main/java/com/tencent/qcloud/tuikit/timcommon/bean/TUIMessageBean.java @@ -0,0 +1,528 @@ +package com.tencent.qcloud.tuikit.timcommon.bean; + +import android.text.TextUtils; + +import com.tencent.imsdk.v2.V2TIMManager; +import com.tencent.imsdk.v2.V2TIMMessage; +import com.tencent.qcloud.tuikit.timcommon.R; +import com.tencent.qcloud.tuikit.timcommon.TIMCommonService; +import com.tencent.qcloud.tuikit.timcommon.util.MessageBuilder; +import com.tencent.qcloud.tuikit.timcommon.util.MessageParser; +import com.tencent.qcloud.tuikit.timcommon.util.TIMCommonConstants; + +import org.json.JSONException; +import org.json.JSONObject; + +import java.io.Serializable; + +public abstract class TUIMessageBean implements Serializable { + /** + * 消息正常状态 + * + * message normal + */ + public static final int MSG_STATUS_NORMAL = 0; + /** + * 消息发送中状态 + * + * message sending + */ + public static final int MSG_STATUS_SENDING = 1; + /** + * 消息发送成功状态 + * + * message send success + */ + public static final int MSG_STATUS_SEND_SUCCESS = 2; + /** + * 消息发送失败状态 + * + * message send failed + */ + public static final int MSG_STATUS_SEND_FAIL = 3; + + /** + * 消息未读状态 + * + * message unread + */ + public static final int MSG_STATUS_READ = 0x111; + /** + * 消息删除状态 + * + * message deleted + */ + public static final int MSG_STATUS_DELETE = 0x112; + /** + * 消息撤回状态 + * + * messaage revoked + */ + public static final int MSG_STATUS_REVOKE = 0x113; + + /** + * 消息内容下载中状态 + * + * message downloading + */ + public static final int MSG_STATUS_DOWNLOADING = 4; + /** + * 消息内容未下载状态 + * + * message undownloaded + */ + public static final int MSG_STATUS_UN_DOWNLOAD = 5; + /** + * 消息内容已下载状态 + * + * message downloaded + */ + public static final int MSG_STATUS_DOWNLOADED = 6; + /** + * 消息翻译初始化状态 + * + * message translation unknown + */ + public static final int MSG_TRANSLATE_STATUS_UNKNOWN = 0; + /** + * 消息翻译隐藏状态 + * + * message translation hidden + */ + public static final int MSG_TRANSLATE_STATUS_HIDDEN = 1; + /** + * 消息翻译进行中状态 + * + * message translation loading + */ + public static final int MSG_TRANSLATE_STATUS_LOADING = 2; + /** + * 消息翻译展示状态 + * + * message translation shown + */ + public static final int MSG_TRANSLATE_STATUS_SHOWN = 3; + + public static final String TRANSLATION_KEY = "translation"; + public static final String TRANSLATION_VIEW_STATUS_KEY = "translation_view_status"; + + private V2TIMMessage v2TIMMessage; + private long msgTime; + private String extra; + private String id; + private boolean isGroup; + private int status; + private int downloadStatus; + private String selectText; + private int translationStatus = MSG_TRANSLATE_STATUS_UNKNOWN; + private boolean excludeFromHistory; + private boolean isUseMsgReceiverAvatar = false; + private boolean isEnableForward = true; + + public void setExcludeFromHistory(boolean excludeFromHistory) { + this.excludeFromHistory = excludeFromHistory; + } + + public boolean isExcludeFromHistory() { + return excludeFromHistory; + } + + public void setUseMsgReceiverAvatar(boolean useMsgReceiverAvatar) { + isUseMsgReceiverAvatar = useMsgReceiverAvatar; + } + + public boolean isUseMsgReceiverAvatar() { + return isUseMsgReceiverAvatar; + } + + public boolean isEnableForward() { + return isEnableForward; + } + + public void setEnableForward(boolean enableForward) { + isEnableForward = enableForward; + } + + private MessageReceiptInfo messageReceiptInfo; + private MessageRepliesBean messageRepliesBean; + private MessageReactBean messageReactBean; + + public MessageReactBean getMessageReactBean() { + return messageReactBean; + } + + public MessageRepliesBean getMessageRepliesBean() { + return messageRepliesBean; + } + + public void setMessageReactBean(MessageReactBean messageReactBean) { + this.messageReactBean = messageReactBean; + MessageBuilder.mergeCloudCustomData(this, TIMCommonConstants.MESSAGE_REACT_KEY, messageReactBean); + } + + public void setMessageRepliesBean(MessageRepliesBean messageRepliesBean) { + this.messageRepliesBean = messageRepliesBean; + MessageBuilder.mergeCloudCustomData(this, TIMCommonConstants.MESSAGE_REPLIES_KEY, messageRepliesBean); + } + + public void setMessageReceiptInfo(MessageReceiptInfo messageReceiptInfo) { + this.messageReceiptInfo = messageReceiptInfo; + } + + public long getReadCount() { + if (messageReceiptInfo != null) { + return messageReceiptInfo.getReadCount(); + } + return 0; + } + + public long getUnreadCount() { + if (messageReceiptInfo != null) { + return messageReceiptInfo.getUnreadCount(); + } + return 0; + } + + public void setCommonAttribute(V2TIMMessage v2TIMMessage) { + msgTime = System.currentTimeMillis() / 1000; + this.v2TIMMessage = v2TIMMessage; + + if (v2TIMMessage == null) { + return; + } + + id = v2TIMMessage.getMsgID(); + isGroup = !TextUtils.isEmpty(v2TIMMessage.getGroupID()); + + if (v2TIMMessage.getStatus() == V2TIMMessage.V2TIM_MSG_STATUS_LOCAL_REVOKED) { + status = MSG_STATUS_REVOKE; + if (isSelf()) { + extra = TIMCommonService.getAppContext().getString(R.string.revoke_tips_you); + } else if (isGroup) { + String message = TIMCommonConstants.covert2HTMLString(getSender()); + extra = message + TIMCommonService.getAppContext().getString(R.string.revoke_tips); + } else { + extra = TIMCommonService.getAppContext().getString(R.string.revoke_tips_other); + } + } else { + if (isSelf()) { + if (v2TIMMessage.getStatus() == V2TIMMessage.V2TIM_MSG_STATUS_SEND_FAIL) { + status = MSG_STATUS_SEND_FAIL; + } else if (v2TIMMessage.getStatus() == V2TIMMessage.V2TIM_MSG_STATUS_SEND_SUCC) { + status = MSG_STATUS_SEND_SUCCESS; + } else if (v2TIMMessage.getStatus() == V2TIMMessage.V2TIM_MSG_STATUS_SENDING) { + status = MSG_STATUS_SENDING; + } + } + } + + messageReactBean = MessageParser.parseMessageReact(this); + messageRepliesBean = MessageParser.parseMessageReplies(this); + } + + public boolean isPeerRead() { + if (messageReceiptInfo != null) { + return messageReceiptInfo.isPeerRead(); + } + return false; + } + + public boolean isAllRead() { + return getUnreadCount() == 0 && getReadCount() > 0; + } + + public boolean isUnread() { + return getReadCount() == 0; + } + + /** + * 获取要显示在会话列表的消息摘要 + * + * Get a summary of messages to display in the conversation list + * @return + */ + public abstract String onGetDisplayString(); + + public abstract void onProcessMessage(V2TIMMessage v2TIMMessage); + + public final long getMessageTime() { + if (v2TIMMessage != null) { + long timestamp = v2TIMMessage.getTimestamp(); + if (timestamp != 0) { + return timestamp; + } + } + return msgTime; + } + + public long getMsgSeq() { + if (v2TIMMessage != null) { + return v2TIMMessage.getSeq(); + } + return 0; + } + + public void setId(String id) { + this.id = id; + } + + public String getId() { + return id; + } + + + public String getUserId() { + if (v2TIMMessage != null) { + return v2TIMMessage.getUserID(); + } + return ""; + } + + public boolean isSelf() { + if (v2TIMMessage != null) { + return v2TIMMessage.isSelf(); + } + return true; + } + + public String getSender() { + String sender = null; + if (v2TIMMessage != null) { + sender = v2TIMMessage.getSender(); + } + if (TextUtils.isEmpty(sender)) { + sender = V2TIMManager.getInstance().getLoginUser(); + } + return sender; + } + + public V2TIMMessage getV2TIMMessage() { + return v2TIMMessage; + } + + public boolean isGroup() { + return isGroup; + } + + public void setGroup(boolean group) { + isGroup = group; + } + + public String getGroupId() { + if (v2TIMMessage != null) { + return v2TIMMessage.getGroupID(); + } + return ""; + } + + public String getNameCard() { + if (v2TIMMessage != null) { + return v2TIMMessage.getNameCard(); + } + return ""; + } + + public String getNickName() { + if (v2TIMMessage != null) { + return v2TIMMessage.getNickName(); + } + return ""; + } + + public String getFriendRemark() { + if (v2TIMMessage != null) { + return v2TIMMessage.getFriendRemark(); + } + return ""; + } + + public String getUserDisplayName() { + String displayName; + if (!TextUtils.isEmpty(getNameCard())) { + displayName = getNameCard(); + } else if (!TextUtils.isEmpty(getFriendRemark())) { + displayName = getFriendRemark(); + } else if (!TextUtils.isEmpty(getNickName())) { + displayName = getNickName(); + } else { + displayName = getSender(); + } + return displayName; + } + + public String getFaceUrl() { + if (v2TIMMessage != null) { + return v2TIMMessage.getFaceUrl(); + } + return ""; + } + + public void setStatus(int status) { + this.status = status; + } + + public int getStatus() { + return status; + } + + public void setExtra(String extra) { + this.extra = extra; + } + + public String getExtra() { + return extra; + } + + public void setDownloadStatus(int downloadStatus) { + this.downloadStatus = downloadStatus; + } + + public int getDownloadStatus() { + return downloadStatus; + } + + public int getMsgType() { + if (v2TIMMessage != null) { + return v2TIMMessage.getElemType(); + } else { + return V2TIMMessage.V2TIM_ELEM_TYPE_NONE; + } + } + + public boolean isNeedReadReceipt() { + if (v2TIMMessage != null) { + return v2TIMMessage.isNeedReadReceipt(); + } + return false; + } + + public void setNeedReadReceipt(boolean isNeedReceipt) { + if (v2TIMMessage != null) { + v2TIMMessage.setNeedReadReceipt(isNeedReceipt); + } + } + + public void setTranslationStatus(int status) { + if (status != MSG_TRANSLATE_STATUS_UNKNOWN && + status != MSG_TRANSLATE_STATUS_HIDDEN && + status != MSG_TRANSLATE_STATUS_SHOWN && + status != MSG_TRANSLATE_STATUS_LOADING) { + return; + } + + if (status == translationStatus) { + return; + } + + if (status == MSG_TRANSLATE_STATUS_LOADING) { + translationStatus = MSG_TRANSLATE_STATUS_LOADING; + return; + } + + translationStatus = status; + + if (v2TIMMessage != null) { + String localCustomData = v2TIMMessage.getLocalCustomData(); + JSONObject customJson = new JSONObject(); + try { + if (!TextUtils.isEmpty(localCustomData)) { + customJson = new JSONObject(localCustomData); + } + customJson.put(TRANSLATION_VIEW_STATUS_KEY, status); + v2TIMMessage.setLocalCustomData(customJson.toString()); + } catch (JSONException e) { + e.printStackTrace(); + } + } + } + + public int getTranslationStatus() { + if (translationStatus != MSG_TRANSLATE_STATUS_UNKNOWN) { + return translationStatus; + } + + if (v2TIMMessage != null) { + String localCustomData = v2TIMMessage.getLocalCustomData(); + if (TextUtils.isEmpty(localCustomData)) { + return translationStatus; + } + try { + JSONObject customJson = new JSONObject(localCustomData); + if (customJson.has(TRANSLATION_VIEW_STATUS_KEY)) { + translationStatus = customJson.getInt(TRANSLATION_VIEW_STATUS_KEY); + } + } catch (JSONException e) { + e.printStackTrace(); + } + } + return translationStatus; + } + + public void setTranslation(String translation) { + if (v2TIMMessage != null) { + String localCustomData = v2TIMMessage.getLocalCustomData(); + JSONObject customJson = new JSONObject(); + try { + if (!TextUtils.isEmpty(localCustomData)) { + customJson = new JSONObject(localCustomData); + } + customJson.put(TRANSLATION_KEY, translation); + customJson.put(TRANSLATION_VIEW_STATUS_KEY, MSG_TRANSLATE_STATUS_SHOWN); + v2TIMMessage.setLocalCustomData(customJson.toString()); + } catch (JSONException e) { + e.printStackTrace(); + } + translationStatus = MSG_TRANSLATE_STATUS_SHOWN; + } + } + + public String getTranslation() { + String translation = ""; + if (v2TIMMessage != null) { + String localCustomData = v2TIMMessage.getLocalCustomData(); + if (TextUtils.isEmpty(localCustomData)) { + return translation; + } + try { + JSONObject customJson = new JSONObject(localCustomData); + if (customJson.has(TRANSLATION_KEY)) { + translation = customJson.getString(TRANSLATION_KEY); + } + } catch (JSONException e) { + e.printStackTrace(); + } + } + return translation; + } + + public void setV2TIMMessage(V2TIMMessage v2TIMMessage) { + this.v2TIMMessage = v2TIMMessage; + setCommonAttribute(v2TIMMessage); + onProcessMessage(v2TIMMessage); + } + + public void update(TUIMessageBean messageBean) { + setV2TIMMessage(messageBean.getV2TIMMessage()); + } + + public String getSelectText() { + return selectText; + } + + public void setSelectText(String text) { + this.selectText = text; + } + + public MessageFeature isSupportTyping() { + return MessageParser.isSupportTyping(this); + } + + public void setMessageTypingFeature(MessageFeature messageFeature) { + MessageBuilder.mergeCloudCustomData(this, TIMCommonConstants.MESSAGE_FEATURE_KEY, messageFeature); + } + + public Class getReplyQuoteBeanClass() { + return null; + } + +} diff --git a/TIMCommon/timcommon/src/main/java/com/tencent/qcloud/tuikit/timcommon/bean/TUIReplyQuoteBean.java b/TIMCommon/timcommon/src/main/java/com/tencent/qcloud/tuikit/timcommon/bean/TUIReplyQuoteBean.java new file mode 100644 index 0000000..a26d697 --- /dev/null +++ b/TIMCommon/timcommon/src/main/java/com/tencent/qcloud/tuikit/timcommon/bean/TUIReplyQuoteBean.java @@ -0,0 +1,36 @@ +package com.tencent.qcloud.tuikit.timcommon.bean; + +import java.io.Serializable; + +public abstract class TUIReplyQuoteBean implements Serializable { + private T messageBean; + protected String defaultAbstract; + protected int messageType; + + public abstract void onProcessReplyQuoteBean(T messageBean); + + public void setMessageBean(T messageBean) { + this.messageBean = messageBean; + } + + public void setDefaultAbstract(String defaultAbstract) { + this.defaultAbstract = defaultAbstract; + } + + public void setMessageType(int messageType) { + this.messageType = messageType; + } + + public int getMessageType() { + return messageType; + } + + public T getMessageBean() { + return messageBean; + } + + public String getDefaultAbstract() { + return defaultAbstract; + } + +} diff --git a/TIMCommon/timcommon/src/main/java/com/tencent/qcloud/tuikit/timcommon/classicui/component/BeginnerGuidePage.java b/TIMCommon/timcommon/src/main/java/com/tencent/qcloud/tuikit/timcommon/classicui/component/BeginnerGuidePage.java new file mode 100644 index 0000000..b86daca --- /dev/null +++ b/TIMCommon/timcommon/src/main/java/com/tencent/qcloud/tuikit/timcommon/classicui/component/BeginnerGuidePage.java @@ -0,0 +1,182 @@ +package com.tencent.qcloud.tuikit.timcommon.classicui.component; + +import android.animation.ValueAnimator; +import android.app.Activity; +import android.graphics.drawable.ColorDrawable; +import android.view.Gravity; +import android.view.LayoutInflater; +import android.view.View; +import android.view.ViewGroup; +import android.view.Window; +import android.view.WindowManager; +import android.view.animation.LinearInterpolator; +import android.widget.ImageView; +import android.widget.PopupWindow; + +import androidx.annotation.NonNull; +import androidx.recyclerview.widget.RecyclerView; +import androidx.viewpager2.widget.ViewPager2; + +import com.tencent.qcloud.tuicore.util.SPUtils; +import com.tencent.qcloud.tuikit.timcommon.R; +import com.tencent.qcloud.tuikit.timcommon.component.impl.GlideEngine; +import com.tencent.qcloud.tuikit.timcommon.util.ScreenUtil; +import com.tencent.qcloud.tuikit.timcommon.util.SoftKeyBoardUtil; +import com.tencent.qcloud.tuikit.timcommon.util.TIMCommonConstants; + +public class BeginnerGuidePage { + + private PopupWindow popupWindow; + private ViewPager2 viewPager; + private OnFinishListener onFinishListener; + private int[] resIDs; + public BeginnerGuidePage(Activity activity) { + View popupView = LayoutInflater.from(activity).inflate(R.layout.layout_beginner_guide, null); + viewPager = popupView.findViewById(R.id.view_pager); + + popupWindow = new PopupWindow(popupView, WindowManager.LayoutParams.MATCH_PARENT, WindowManager.LayoutParams.MATCH_PARENT, true) { + @Override + public void showAtLocation(View anchor, int gravity, int x, int y) { + if (activity != null && !activity.isFinishing()) { + Window dialogWindow = activity.getWindow(); + startAnimation(dialogWindow, true); + } + super.showAtLocation(anchor, gravity, x, y); + } + + @Override + public void dismiss() { + if (activity != null && !activity.isFinishing()) { + Window dialogWindow = activity.getWindow(); + startAnimation(dialogWindow, false); + } + + super.dismiss(); + } + }; + popupWindow.setBackgroundDrawable(new ColorDrawable()); + popupWindow.setTouchable(true); + popupWindow.setOutsideTouchable(false); + popupWindow.setAnimationStyle(R.style.BeginnerGuidePopupAnimation); + viewPager.setUserInputEnabled(false); + viewPager.setAdapter(new GuideAdapter()); + } + + private void startAnimation(Window window, boolean isShow) { + LinearInterpolator interpolator = new LinearInterpolator(); + ValueAnimator animator; + if (isShow) { + animator = ValueAnimator.ofFloat(1.0f, 0.5f); + } else { + animator = ValueAnimator.ofFloat(0.5f, 1.0f); + } + animator.addUpdateListener(new ValueAnimator.AnimatorUpdateListener() { + @Override + public void onAnimationUpdate(ValueAnimator animation) { + WindowManager.LayoutParams lp = window.getAttributes(); + lp.alpha = (float) animation.getAnimatedValue(); + window.setAttributes(lp); + } + }); + + animator.setDuration(200); + animator.setInterpolator(interpolator); + animator.start(); + } + + public void setPagesResIDs(int... imageResIDs) { + resIDs = imageResIDs; + viewPager.setOffscreenPageLimit(resIDs.length); + viewPager.getAdapter().notifyDataSetChanged(); + viewPager.setCurrentItem(0, false); + } + + public void setOnFinishListener(OnFinishListener onFinishListener) { + this.onFinishListener = onFinishListener; + } + + public void show(View rootView, int gravity) { + if (popupWindow != null) { + popupWindow.showAtLocation(rootView, gravity, 0, 0); + } + } + + class GuideAdapter extends RecyclerView.Adapter { + @NonNull + @Override + public GuideViewHolder onCreateViewHolder(@NonNull ViewGroup parent, int viewType) { + return new GuideViewHolder(LayoutInflater.from(parent.getContext()).inflate(R.layout.layout_beginner_guide_item, parent ,false)); + } + + @Override + public void onBindViewHolder(@NonNull GuideViewHolder holder, int position) { + ViewGroup.LayoutParams params = holder.image.getLayoutParams(); + if (params != null) { + params.width = ScreenUtil.getScreenWidth(holder.image.getContext()); + params.height = ScreenUtil.getScreenHeight(holder.image.getContext()); + holder.image.setLayoutParams(params); + } + GlideEngine.loadImage(holder.image, resIDs[position]); + holder.itemView.setOnClickListener(new View.OnClickListener() { + @Override + public void onClick(View v) { + if (viewPager != null) { + int currentPos = holder.getBindingAdapterPosition(); + if (currentPos < getItemCount() - 1) { + viewPager.setCurrentItem(currentPos + 1, true); + } else { + if (onFinishListener != null) { + onFinishListener.onFinish(); + } + if (popupWindow != null && popupWindow.isShowing()) { + popupWindow.dismiss(); + } + } + } + } + }); + } + + @Override + public int getItemCount() { + if (resIDs == null) { + return 0; + } + return resIDs.length; + } + + class GuideViewHolder extends RecyclerView.ViewHolder { + private final ImageView image; + public GuideViewHolder(@NonNull View itemView) { + super(itemView); + image = itemView.findViewById(R.id.center_image); + } + } + } + + + public static void showBeginnerGuideThen(View view, Runnable runnable) { + boolean isShowGuide = SPUtils.getInstance(TIMCommonConstants.CHAT_SETTINGS_SP_NAME).getBoolean(TIMCommonConstants.CHAT_REPLY_GUIDE_SHOW_SP_KEY, true); + if (isShowGuide) { + SoftKeyBoardUtil.hideKeyBoard(view.getWindowToken()); + SPUtils.getInstance(TIMCommonConstants.CHAT_SETTINGS_SP_NAME).put(TIMCommonConstants.CHAT_REPLY_GUIDE_SHOW_SP_KEY, false); + + BeginnerGuidePage guidePage = new BeginnerGuidePage((Activity) view.getContext()); + guidePage.setPagesResIDs(R.drawable.chat_reply_guide, R.drawable.chat_quote_guide); + guidePage.setOnFinishListener(new BeginnerGuidePage.OnFinishListener() { + @Override + public void onFinish() { + runnable.run(); + } + }); + guidePage.show(view, Gravity.NO_GRAVITY); + } else { + runnable.run(); + } + } + + @FunctionalInterface + public interface OnFinishListener { + void onFinish(); + } +} diff --git a/TIMCommon/timcommon/src/main/java/com/tencent/qcloud/tuikit/timcommon/classicui/widget/message/ChatFlowReactView.java b/TIMCommon/timcommon/src/main/java/com/tencent/qcloud/tuikit/timcommon/classicui/widget/message/ChatFlowReactView.java new file mode 100644 index 0000000..9d0efae --- /dev/null +++ b/TIMCommon/timcommon/src/main/java/com/tencent/qcloud/tuikit/timcommon/classicui/widget/message/ChatFlowReactView.java @@ -0,0 +1,256 @@ +package com.tencent.qcloud.tuikit.timcommon.classicui.widget.message; + +import android.content.Context; +import android.graphics.Bitmap; +import android.util.AttributeSet; +import android.util.DisplayMetrics; +import android.util.TypedValue; +import android.view.LayoutInflater; +import android.view.View; +import android.view.ViewGroup; +import android.widget.ImageView; +import android.widget.TextView; + +import androidx.annotation.NonNull; +import androidx.annotation.Nullable; +import androidx.recyclerview.widget.RecyclerView; + +import com.tencent.qcloud.tuicore.TUIThemeManager; +import com.tencent.qcloud.tuikit.timcommon.R; +import com.tencent.qcloud.tuikit.timcommon.bean.MessageReactBean; +import com.tencent.qcloud.tuikit.timcommon.bean.ReactUserBean; +import com.tencent.qcloud.tuikit.timcommon.component.face.FaceManager; + +import java.util.ArrayList; +import java.util.Map; +import java.util.Set; + +public class ChatFlowReactView extends RecyclerView { + private ChatFlowReactLayoutManager layoutManager; + private ChatFlowReactAdapter adapter; + private int themeColorId; + public ChatFlowReactView(@NonNull Context context) { + super(context); + initView(); + } + + public ChatFlowReactView(@NonNull Context context, @Nullable AttributeSet attrs) { + super(context, attrs); + initView(); + } + + public ChatFlowReactView(@NonNull Context context, @Nullable AttributeSet attrs, int defStyleAttr) { + super(context, attrs, defStyleAttr); + initView(); + } + + private void initView() { + + DisplayMetrics displayMetrics = getResources().getDisplayMetrics(); + float spacingVertical = TypedValue.applyDimension(TypedValue.COMPLEX_UNIT_DIP, 7.68f, displayMetrics); + float spacingHorizontal = TypedValue.applyDimension(TypedValue.COMPLEX_UNIT_DIP, 5.76f, displayMetrics); + + layoutManager = new ChatFlowReactLayoutManager(spacingHorizontal, spacingVertical); + setLayoutManager(layoutManager); + adapter = new ChatFlowReactAdapter(); + setAdapter(adapter); + } + + public void setThemeColorId(int themeColorId) { + this.themeColorId = themeColorId; + adapter.setThemeColorId(themeColorId); + } + + public void setReactOnClickListener(ReactOnClickListener reactOnClickListener) { + if (adapter != null) { + adapter.setReactOnClickListener(reactOnClickListener); + } + } + + public void setData(MessageReactBean reactBean) { + if (adapter != null) { + adapter.setData(reactBean); + adapter.notifyDataSetChanged(); + } + } + + static class ChatFlowReactAdapter extends Adapter { + private MessageReactBean data; + private ReactOnClickListener reactOnClickListener; + private int themeColorId; + public void setReactOnClickListener(ReactOnClickListener reactOnClickListener) { + this.reactOnClickListener = reactOnClickListener; + } + + public void setThemeColorId(int themeColorId) { + this.themeColorId = themeColorId; + } + + public void setData(MessageReactBean data) { + this.data = data; + } + + @NonNull + @Override + public ChatFlowReactViewHolder onCreateViewHolder(@NonNull ViewGroup parent, int viewType) { + View view = LayoutInflater.from(parent.getContext()).inflate(R.layout.chat_flow_react_item_layout, parent, false); + return new ChatFlowReactViewHolder(view); + } + + @Override + public void onBindViewHolder(@NonNull ChatFlowReactViewHolder holder, int position) { + Map.Entry> entry = new ArrayList<>(data.getReacts().entrySet()).get(position); + String emojiId = entry.getKey(); + Set userIds = entry.getValue(); + + Bitmap bitmap = FaceManager.getEmoji(emojiId); + holder.faceImageView.setImageBitmap(bitmap); + + holder.faceImageView.setOnClickListener(new OnClickListener() { + @Override + public void onClick(View v) { + if (reactOnClickListener != null) { + reactOnClickListener.onClick(emojiId); + } + } + }); + if (themeColorId != 0) { + holder.userTextView.setTextColor(holder.userTextView.getResources().getColor(themeColorId)); + holder.userTextView.setTextColor(holder.userTextView.getResources().getColor(themeColorId)); + } else { + holder.userTextView.setTextColor(holder.userTextView.getResources().getColor( + TUIThemeManager.getAttrResId(holder.userTextView.getContext(), R.attr.chat_react_text_color))); + holder.userTextView.setTextColor(holder.userTextView.getResources().getColor( + TUIThemeManager.getAttrResId(holder.userTextView.getContext(), R.attr.chat_react_text_color))); + } + holder.userTextView.setText(formatDisplayUserName(userIds)); + } + + private String getUserDisplayName(String id) { + if (data.getReactUserBeanMap() == null) { + return id; + } else { + ReactUserBean reactUserBean = data.getReactUserBeanMap().get(id); + if (reactUserBean == null) { + return id; + } else { + return reactUserBean.getDisplayString(); + } + } + } + + @Override + public int getItemCount() { + if (data != null) { + return data.getReactSize(); + } + return 0; + } + + private String formatDisplayUserName(Set userIds) { + StringBuilder stringBuilder = new StringBuilder(); + int index = 0; + for (String userId : userIds) { + stringBuilder.append(getUserDisplayName(userId)); + index++; + if (index != userIds.size()) { + stringBuilder.append("、"); + } + } + return stringBuilder.toString(); + } + } + + public interface ReactOnClickListener { + void onClick(String emojiId); + } + + static class ChatFlowReactViewHolder extends ViewHolder { + public TextView userTextView; + public ImageView faceImageView; + public ChatFlowReactViewHolder(@NonNull View itemView) { + super(itemView); + userTextView = itemView.findViewById(R.id.users_tv); + faceImageView = itemView.findViewById(R.id.face_iv); + } + } + + + // ChatReactView is just a simple flowLayout + static class ChatFlowReactLayoutManager extends LayoutManager { + private int verticalSpacing = 0; + private int horizontalSpacing = 0; + + public ChatFlowReactLayoutManager() {} + + public ChatFlowReactLayoutManager(float horizontalSpacing, float verticalSpacing) { + this.verticalSpacing = Math.round(verticalSpacing); + this.horizontalSpacing = Math.round(horizontalSpacing); + } + + @Override + public LayoutParams generateDefaultLayoutParams() { + return new LayoutParams(ViewGroup.LayoutParams.WRAP_CONTENT, ViewGroup.LayoutParams.WRAP_CONTENT); + } + + @Override + public void onLayoutChildren(Recycler recycler, State state) { + detachAndScrapAttachedViews(recycler); + int itemCount = getItemCount(); + if (itemCount == 0) { + return; + } + + int offsetTop; + int offsetBottom; + int offsetLeft; + int offsetRight = getPaddingStart(); + + boolean isLineFirstItem = true; + boolean isFirstLine = true; + + int currentMaxBottom = 0; + int nextMaxBottom = 0; + for (int i = 0; i < itemCount; i++) { + View childView = recycler.getViewForPosition(i); + addView(childView); + measureChildWithMargins(childView, 0, 0); + int childMeasuredWidth = getDecoratedMeasuredWidth(childView); + int childMeasuredHeight = getDecoratedMeasuredHeight(childView); + + if (i != 0 && offsetRight + horizontalSpacing + childMeasuredWidth > + getWidth() - getPaddingStart() - getPaddingEnd()) { + // switch a new line + isLineFirstItem = true; + isFirstLine = false; + currentMaxBottom = nextMaxBottom; + } + + if (isLineFirstItem) { + offsetLeft = getPaddingStart(); + } else { + offsetLeft = offsetRight + horizontalSpacing; + } + + if (isFirstLine) { + offsetTop = getPaddingTop(); + } else { + offsetTop = currentMaxBottom + verticalSpacing; + } + + offsetRight = offsetLeft + childMeasuredWidth; + offsetBottom = offsetTop + childMeasuredHeight; + nextMaxBottom = Math.max(nextMaxBottom, offsetBottom); + + layoutDecoratedWithMargins(childView, offsetLeft, offsetTop, offsetRight, offsetBottom); + + isLineFirstItem = false; + } + } + + @Override + public boolean isAutoMeasureEnabled() { + return true; + } + } +} diff --git a/TIMCommon/timcommon/src/main/java/com/tencent/qcloud/tuikit/timcommon/classicui/widget/message/MessageBaseHolder.java b/TIMCommon/timcommon/src/main/java/com/tencent/qcloud/tuikit/timcommon/classicui/widget/message/MessageBaseHolder.java new file mode 100644 index 0000000..7d116a4 --- /dev/null +++ b/TIMCommon/timcommon/src/main/java/com/tencent/qcloud/tuikit/timcommon/classicui/widget/message/MessageBaseHolder.java @@ -0,0 +1,190 @@ +package com.tencent.qcloud.tuikit.timcommon.classicui.widget.message; + +import android.animation.Animator; +import android.animation.ArgbEvaluator; +import android.animation.ValueAnimator; +import android.graphics.Color; +import android.graphics.PorterDuff; +import android.graphics.drawable.Drawable; +import android.view.View; +import android.widget.CheckBox; +import android.widget.FrameLayout; +import android.widget.LinearLayout; +import android.widget.RelativeLayout; +import android.widget.TextView; + +import androidx.cardview.widget.CardView; +import androidx.recyclerview.widget.RecyclerView; + +import com.tencent.qcloud.tuikit.timcommon.R; +import com.tencent.qcloud.tuikit.timcommon.bean.TUIMessageBean; +import com.tencent.qcloud.tuikit.timcommon.component.MessageProperties; +import com.tencent.qcloud.tuikit.timcommon.interfaces.ICommonMessageAdapter; +import com.tencent.qcloud.tuikit.timcommon.interfaces.OnItemClickListener; +import com.tencent.qcloud.tuikit.timcommon.util.DateTimeUtil; + +import java.util.Date; + +public abstract class MessageBaseHolder extends RecyclerView.ViewHolder { + public static final int MSG_TYPE_HEADER_VIEW = -99; + + public ICommonMessageAdapter mAdapter; + public MessageProperties properties = MessageProperties.getInstance(); + protected OnItemClickListener onItemClickListener; + + public TextView chatTimeText; + public FrameLayout msgContentFrame; + public LinearLayout msgReplyDetailLayout; + public LinearLayout msgArea; + public CardView cardView; + public LinearLayout msgAreaAndReply; + public ChatFlowReactView reactView; + public CheckBox mMutiSelectCheckBox; + public RelativeLayout rightGroupLayout; + public RelativeLayout mContentLayout; + + private ValueAnimator highLightAnimator; + + public MessageBaseHolder(View itemView) { + super(itemView); + chatTimeText = itemView.findViewById(R.id.message_top_time_tv); + msgContentFrame = itemView.findViewById(R.id.msg_content_fl); + msgReplyDetailLayout = itemView.findViewById(R.id.msg_reply_detail_fl); + reactView = itemView.findViewById(R.id.reacts_view); + msgArea = itemView.findViewById(R.id.msg_area); + cardView = itemView.findViewById(R.id.cardView); + msgAreaAndReply = itemView.findViewById(R.id.msg_area_and_reply); + mMutiSelectCheckBox = itemView.findViewById(R.id.select_checkbox); + rightGroupLayout = itemView.findViewById(R.id.right_group_layout); + mContentLayout = itemView.findViewById(R.id.message_content_layout); + initVariableLayout(); + } + + public abstract int getVariableLayout(); + + private void setVariableLayout(int resId) { + if (msgContentFrame.getChildCount() == 0) { + View.inflate(itemView.getContext(), resId, msgContentFrame); + } + } + + private void initVariableLayout() { + if (getVariableLayout() != 0) { + setVariableLayout(getVariableLayout()); + } + } + + public void setAdapter(ICommonMessageAdapter adapter) { + mAdapter = adapter; + } + + public void setOnItemClickListener(OnItemClickListener listener) { + this.onItemClickListener = listener; + } + + public OnItemClickListener getOnItemClickListener() { + return this.onItemClickListener; + } + + public void layoutViews(final TUIMessageBean msg, final int position) { + if (properties.getChatTimeBubble() != null) { + chatTimeText.setBackground(properties.getChatTimeBubble()); + } + if (properties.getChatTimeFontColor() != 0) { + chatTimeText.setTextColor(properties.getChatTimeFontColor()); + } + if (properties.getChatTimeFontSize() != 0) { + chatTimeText.setTextSize(properties.getChatTimeFontSize()); + } + + if (position > 1) { + TUIMessageBean last = mAdapter.getItem(position - 1); + if (last != null) { + if (msg.getMessageTime() - last.getMessageTime() >= 5 * 60) { + chatTimeText.setVisibility(View.VISIBLE); + chatTimeText.setText(DateTimeUtil.getTimeFormatText(new Date(msg.getMessageTime() * 1000))); + } else { + chatTimeText.setVisibility(View.GONE); + } + } + } else { + chatTimeText.setVisibility(View.VISIBLE); + chatTimeText.setText(DateTimeUtil.getTimeFormatText(new Date(msg.getMessageTime() * 1000))); + } + + if (cardView != null) { + cardView.setRadius(0); + cardView.setCardElevation(0); + cardView.setMaxCardElevation(0); + } + } + + public void stopHighLight() { + if (highLightAnimator != null) { + highLightAnimator.cancel(); + } + clearHighLightBackground(); + } + + public void startHighLight() { + int highLightColorDark = itemView.getResources().getColor(com.tencent.qcloud.tuikit.timcommon.R.color.chat_message_bubble_high_light_dark_color); + int highLightColorLight = itemView.getResources().getColor(com.tencent.qcloud.tuikit.timcommon.R.color.chat_message_bubble_high_light_light_color); + + if (highLightAnimator == null) { + ArgbEvaluator argbEvaluator = new ArgbEvaluator(); + highLightAnimator = new ValueAnimator(); + highLightAnimator.setIntValues(highLightColorDark, highLightColorLight); + highLightAnimator.addUpdateListener(new ValueAnimator.AnimatorUpdateListener() { + @Override + public void onAnimationUpdate(ValueAnimator animation) { + Integer color = (Integer) animation.getAnimatedValue(); + setHighLightBackground(color); + } + }); + highLightAnimator.addListener(new Animator.AnimatorListener() { + @Override + public void onAnimationStart(Animator animation) { + + } + + @Override + public void onAnimationEnd(Animator animation) { + clearHighLightBackground(); + } + + @Override + public void onAnimationCancel(Animator animation) { + clearHighLightBackground(); + } + + @Override + public void onAnimationRepeat(Animator animation) { + + } + }); + highLightAnimator.setEvaluator(argbEvaluator); + highLightAnimator.setRepeatCount(3); + highLightAnimator.setDuration(250); + highLightAnimator.setRepeatMode(ValueAnimator.REVERSE); + } + highLightAnimator.start(); + } + + public void setHighLightBackground(int color) { + Drawable drawable = msgArea.getBackground(); + if (drawable != null) { + drawable.setColorFilter(color, PorterDuff.Mode.SRC_IN); + } + } + + public void clearHighLightBackground() { + Drawable drawable = msgArea.getBackground(); + if (drawable != null) { + drawable.setColorFilter(null); + } + } + + public CardView getCardView() { + return cardView; + } +} diff --git a/TIMCommon/timcommon/src/main/java/com/tencent/qcloud/tuikit/timcommon/classicui/widget/message/MessageContentHolder.java b/TIMCommon/timcommon/src/main/java/com/tencent/qcloud/tuikit/timcommon/classicui/widget/message/MessageContentHolder.java new file mode 100644 index 0000000..ed0c7f7 --- /dev/null +++ b/TIMCommon/timcommon/src/main/java/com/tencent/qcloud/tuikit/timcommon/classicui/widget/message/MessageContentHolder.java @@ -0,0 +1,626 @@ +package com.tencent.qcloud.tuikit.timcommon.classicui.widget.message; + +import android.text.TextUtils; +import android.view.Gravity; +import android.view.View; +import android.view.ViewGroup; +import android.widget.FrameLayout; +import android.widget.ImageView; +import android.widget.LinearLayout; +import android.widget.ProgressBar; +import android.widget.TextView; + +import androidx.recyclerview.widget.RecyclerView; + +import com.tencent.imsdk.v2.V2TIMManager; +import com.tencent.imsdk.v2.V2TIMMessage; +import com.tencent.imsdk.v2.V2TIMUserFullInfo; +import com.tencent.imsdk.v2.V2TIMValueCallback; +import com.tencent.qcloud.tuicore.TUIConstants; +import com.tencent.qcloud.tuicore.TUICore; +import com.tencent.qcloud.tuicore.TUIThemeManager; +import com.tencent.qcloud.tuikit.timcommon.R; +import com.tencent.qcloud.tuikit.timcommon.TIMCommonService; +import com.tencent.qcloud.tuikit.timcommon.bean.MessageReactBean; +import com.tencent.qcloud.tuikit.timcommon.bean.MessageRepliesBean; +import com.tencent.qcloud.tuikit.timcommon.bean.TUIMessageBean; +import com.tencent.qcloud.tuikit.timcommon.component.fragments.BaseFragment; +import com.tencent.qcloud.tuikit.timcommon.component.gatherimage.UserIconView; +import com.tencent.qcloud.tuikit.timcommon.util.DateTimeUtil; +import com.tencent.qcloud.tuikit.timcommon.util.ScreenUtil; +import com.tencent.qcloud.tuikit.timcommon.util.TIMCommonLog; + +import java.util.ArrayList; +import java.util.Date; +import java.util.HashMap; +import java.util.List; + +public abstract class MessageContentHolder extends MessageBaseHolder { + + public UserIconView leftUserIcon; + public UserIconView rightUserIcon; + public TextView usernameText; + public LinearLayout msgContentLinear; + public ProgressBar sendingProgress; + public ImageView statusImage; + public TextView isReadText; + public TextView unreadAudioText; + public TextView messageDetailsTimeTv; + private FrameLayout translationContentFrameLayout; + + public boolean isForwardMode = false; + public boolean isReplyDetailMode = false; + public boolean isMultiSelectMode = false; + + private List mDataSource = new ArrayList<>(); + protected SelectTextHelper selectableTextHelper; + // 是否显示翻译的内容。合并转发的消息详情界面不用展示翻译内容。 + // Whether to display the translated content. The merged-forwarded message details activity does not display the translated content. + protected boolean isNeedShowTranslation = true; + protected boolean isShowRead = false; + private BaseFragment fragment; + private RecyclerView recyclerView; + + public MessageContentHolder(View itemView) { + super(itemView); + leftUserIcon = itemView.findViewById(R.id.left_user_icon_view); + rightUserIcon = itemView.findViewById(R.id.right_user_icon_view); + usernameText = itemView.findViewById(R.id.user_name_tv); + msgContentLinear = itemView.findViewById(R.id.msg_content_ll); + statusImage = itemView.findViewById(R.id.message_status_iv); + sendingProgress = itemView.findViewById(R.id.message_sending_pb); + isReadText = itemView.findViewById(R.id.is_read_tv); + unreadAudioText = itemView.findViewById(R.id.audio_unread); + messageDetailsTimeTv = itemView.findViewById(R.id.msg_detail_time_tv); + translationContentFrameLayout = itemView.findViewById(R.id.translate_content_fl); + } + + public void setFragment(BaseFragment fragment) { + this.fragment = fragment; + } + + public void setRecyclerView(RecyclerView recyclerView) { + this.recyclerView = recyclerView; + } + + public void setDataSource(List dataSource) { + if (dataSource == null || dataSource.isEmpty()) { + mDataSource = null; + } + + List mediaSource = new ArrayList<>(); + for(TUIMessageBean messageBean : dataSource) { + int type = messageBean.getMsgType(); + if (type == V2TIMMessage.V2TIM_ELEM_TYPE_IMAGE || type == V2TIMMessage.V2TIM_ELEM_TYPE_VIDEO) { + mediaSource.add(messageBean); + } + } + mDataSource = mediaSource; + } + + public List getDataSource() { + return mDataSource; + } + + public void resetSelectableText() { + if (selectableTextHelper != null) { + selectableTextHelper.reset(); + } + } + + @Override + public void layoutViews(final TUIMessageBean msg, final int position) { + super.layoutViews(msg, position); + + if (isForwardMode || isReplyDetailMode) { + leftUserIcon.setVisibility(View.VISIBLE); + rightUserIcon.setVisibility(View.GONE); + } else { + if (msg.isSelf()) { + leftUserIcon.setVisibility(View.GONE); + rightUserIcon.setVisibility(View.VISIBLE); + } else { + leftUserIcon.setVisibility(View.VISIBLE); + rightUserIcon.setVisibility(View.GONE); + } + } + if (properties.getAvatar() != 0) { + leftUserIcon.setDefaultImageResId(properties.getAvatar()); + rightUserIcon.setDefaultImageResId(properties.getAvatar()); + } else { + leftUserIcon.setDefaultImageResId(TUIThemeManager.getAttrResId(leftUserIcon.getContext(), com.tencent.qcloud.tuikit.timcommon.R.attr.core_default_user_icon)); + rightUserIcon.setDefaultImageResId(TUIThemeManager.getAttrResId(rightUserIcon.getContext(), com.tencent.qcloud.tuikit.timcommon.R.attr.core_default_user_icon)); + } + if (properties.getAvatarRadius() != 0) { + leftUserIcon.setRadius(properties.getAvatarRadius()); + rightUserIcon.setRadius(properties.getAvatarRadius()); + } else { + int radius = ScreenUtil.dip2px(25); + leftUserIcon.setRadius(radius); + rightUserIcon.setRadius(radius); + } + if (properties.getAvatarSize() != null && properties.getAvatarSize().length == 2) { + ViewGroup.LayoutParams params = leftUserIcon.getLayoutParams(); + params.width = properties.getAvatarSize()[0]; + params.height = properties.getAvatarSize()[1]; + leftUserIcon.setLayoutParams(params); + + params = rightUserIcon.getLayoutParams(); + params.width = properties.getAvatarSize()[0]; + params.height = properties.getAvatarSize()[1]; + rightUserIcon.setLayoutParams(params); + } + + if (isForwardMode || isReplyDetailMode) { + usernameText.setVisibility(View.VISIBLE); + } else { + if (msg.isSelf()) { + if (properties.getRightNameVisibility() == 0) { + usernameText.setVisibility(View.GONE); + } else { + usernameText.setVisibility(properties.getRightNameVisibility()); + } + } else { + if (properties.getLeftNameVisibility() == 0) { + if (msg.isGroup()) { + usernameText.setVisibility(View.VISIBLE); + } else { + usernameText.setVisibility(View.GONE); + } + } else { + usernameText.setVisibility(properties.getLeftNameVisibility()); + } + } + } + if (properties.getNameFontColor() != 0) { + usernameText.setTextColor(properties.getNameFontColor()); + } + if (properties.getNameFontSize() != 0) { + usernameText.setTextSize(properties.getNameFontSize()); + } + + if (!TextUtils.isEmpty(msg.getNameCard())) { + usernameText.setText(msg.getNameCard()); + } else if (!TextUtils.isEmpty(msg.getFriendRemark())) { + usernameText.setText(msg.getFriendRemark()); + } else if (!TextUtils.isEmpty(msg.getNickName())) { + usernameText.setText(msg.getNickName()); + } else { + usernameText.setText(msg.getSender()); + } + + loadAvatar(msg); + + if (isForwardMode || isReplyDetailMode) { + sendingProgress.setVisibility(View.GONE); + } else { + if (msg.isSelf()) { + if (msg.getStatus() == TUIMessageBean.MSG_STATUS_SEND_FAIL + || msg.getStatus() == TUIMessageBean.MSG_STATUS_SEND_SUCCESS + || msg.isPeerRead()) { + sendingProgress.setVisibility(View.GONE); + } else { + sendingProgress.setVisibility(View.VISIBLE); + } + } else { + sendingProgress.setVisibility(View.GONE); + } + } + + if (isForwardMode || isReplyDetailMode) { + msgArea.setBackgroundResource(TUIThemeManager.getAttrResId(itemView.getContext(), R.attr.chat_bubble_other_bg)); + statusImage.setVisibility(View.GONE); + } else { + if (msg.isSelf()) { + if (properties.getRightBubble() != null && properties.getRightBubble().getConstantState() != null) { + msgArea.setBackground(properties.getRightBubble().getConstantState().newDrawable()); + } else { + msgArea.setBackgroundResource(TUIThemeManager.getAttrResId(itemView.getContext(), R.attr.chat_bubble_self_bg)); + } + } else { + if (properties.getLeftBubble() != null && properties.getLeftBubble().getConstantState() != null) { + msgArea.setBackground(properties.getLeftBubble().getConstantState().newDrawable()); + } else { + msgArea.setBackgroundResource(TUIThemeManager.getAttrResId(itemView.getContext(), R.attr.chat_bubble_other_bg)); + } + } + + if (onItemClickListener != null) { + msgContentFrame.setOnLongClickListener(new View.OnLongClickListener() { + @Override + public boolean onLongClick(View v) { + onItemClickListener.onMessageLongClick(v, position, msg); + return true; + } + }); + + msgArea.setOnLongClickListener(new View.OnLongClickListener() { + @Override + public boolean onLongClick(View v) { + onItemClickListener.onMessageLongClick(msgArea, position, msg); + return true; + } + }); + + leftUserIcon.setOnClickListener(new View.OnClickListener() { + @Override + public void onClick(View view) { + onItemClickListener.onUserIconClick(view, position, msg); + } + }); + leftUserIcon.setOnLongClickListener(new View.OnLongClickListener() { + @Override + public boolean onLongClick(View view) { + onItemClickListener.onUserIconLongClick(view, position, msg); + return true; + } + }); + rightUserIcon.setOnClickListener(new View.OnClickListener() { + @Override + public void onClick(View view) { + onItemClickListener.onUserIconClick(view, position, msg); + } + }); + } + + if (msg.getStatus() == TUIMessageBean.MSG_STATUS_SEND_FAIL) { + statusImage.setVisibility(View.VISIBLE); + msgContentFrame.setOnClickListener(new View.OnClickListener() { + @Override + public void onClick(View view) { + if (onItemClickListener != null) { + onItemClickListener.onMessageLongClick(msgContentFrame, position, msg); + } + } + }); + statusImage.setOnClickListener(new View.OnClickListener() { + @Override + public void onClick(View v) { + if (onItemClickListener != null) { + onItemClickListener.onSendFailBtnClick(statusImage, position, msg); + } + } + }); + } else { + msgContentFrame.setOnClickListener(new View.OnClickListener() { + @Override + public void onClick(View v) { + if (onItemClickListener != null) { + onItemClickListener.onMessageClick(msgContentFrame, position, msg); + } + } + }); + statusImage.setVisibility(View.GONE); + } + } + + if (isForwardMode || isReplyDetailMode) { + setGravity(true); + msgContentLinear.removeView(msgAreaAndReply); + msgContentLinear.addView(msgAreaAndReply); + } else { + if (msg.isSelf()) { + setGravity(false); + msgContentLinear.removeView(msgAreaAndReply); + msgContentLinear.addView(msgAreaAndReply); + } else { + setGravity(true); + msgContentLinear.removeView(msgAreaAndReply); + msgContentLinear.addView(msgAreaAndReply, 0); + } + } + + if (rightGroupLayout != null) { + rightGroupLayout.setVisibility(View.VISIBLE); + } + msgContentLinear.setVisibility(View.VISIBLE); + + // clear isReadText status + isReadText.setTextColor(isReadText.getResources().getColor(R.color.text_gray1)); + isReadText.setOnClickListener(null); + + if (isForwardMode || isReplyDetailMode) { + isReadText.setVisibility(View.GONE); + unreadAudioText.setVisibility(View.GONE); + } else { + if (isShowRead) { + if (msg.isSelf() && TUIMessageBean.MSG_STATUS_SEND_SUCCESS == msg.getStatus()) { + if (!msg.isNeedReadReceipt()) { + isReadText.setVisibility(View.GONE); + } else { + showReadText(msg); + } + } else { + isReadText.setVisibility(View.GONE); + } + } + unreadAudioText.setVisibility(View.GONE); + } + + if (isReplyDetailMode) { + chatTimeText.setVisibility(View.GONE); + } + + setReplyContent(msg); + setReactContent(msg); + if (isNeedShowTranslation) { + setTranslationContent(msg); + } + + setMessageAreaPadding(); + + layoutVariableViews(msg, position); + } + + private void setTranslationContent(TUIMessageBean msg) { + HashMap param = new HashMap<>(); + param.put(TUIConstants.TUIChat.MESSAGE_BEAN, msg); + param.put(TUIConstants.TUIChat.CHAT_RECYCLER_VIEW, recyclerView); + param.put(TUIConstants.TUIChat.FRAGMENT, fragment); + + TUICore.raiseExtension(TUIConstants.TUITranslation.Extension.TranslationView.CLASSIC_EXTENSION_ID, translationContentFrameLayout, param); + } + + private void loadAvatar(TUIMessageBean msg) { + if (msg.isUseMsgReceiverAvatar()) { + String userId = ""; + if (TextUtils.equals(msg.getSender(), V2TIMManager.getInstance().getLoginUser())) { + userId = msg.getUserId(); + } else { + userId = V2TIMManager.getInstance().getLoginUser(); + } + List idList = new ArrayList<>(); + idList.add(userId); + V2TIMManager.getInstance().getUsersInfo(idList, new V2TIMValueCallback>() { + @Override + public void onSuccess(List v2TIMUserFullInfos) { + V2TIMUserFullInfo userInfo = v2TIMUserFullInfos.get(0); + if (userInfo == null) { + setupAvatar("", msg.isSelf()); + } else { + setupAvatar(userInfo.getFaceUrl(), msg.isSelf()); + } + } + + @Override + public void onError(int code, String desc) { + setupAvatar("", msg.isSelf()); + } + }); + } else { + setupAvatar(msg.getFaceUrl(), msg.isSelf()); + } + } + + private void setupAvatar(String faceUrl, boolean right) { + if (!TextUtils.isEmpty(faceUrl)) { + List urllist = new ArrayList<>(); + urllist.add(faceUrl); + if (isForwardMode || isReplyDetailMode) { + leftUserIcon.setIconUrls(urllist); + } else { + if (right) { + rightUserIcon.setIconUrls(urllist); + } else { + leftUserIcon.setIconUrls(urllist); + } + } + } else { + rightUserIcon.setIconUrls(null); + leftUserIcon.setIconUrls(null); + } + } + + protected void setMessageAreaPadding() { + // after setting background, the padding will be reset + int paddingHorizontal = itemView.getResources().getDimensionPixelSize(R.dimen.chat_message_area_padding_left_right); + int paddingVertical = itemView.getResources().getDimensionPixelSize(R.dimen.chat_message_area_padding_top_bottom); + msgArea.setPadding(paddingHorizontal, paddingVertical, paddingHorizontal, paddingVertical); + } + + protected void setGravity(boolean isStart) { + int gravity = isStart ? Gravity.START : Gravity.END; + msgAreaAndReply.setGravity(gravity); + ViewGroup.LayoutParams layoutParams = msgContentFrame.getLayoutParams(); + if (layoutParams instanceof FrameLayout.LayoutParams) { + ((FrameLayout.LayoutParams) layoutParams).gravity = gravity; + } else if (layoutParams instanceof LinearLayout.LayoutParams) { + ((LinearLayout.LayoutParams) layoutParams).gravity = gravity; + } + msgContentFrame.setLayoutParams(layoutParams); + } + + private void setReplyContent(TUIMessageBean messageBean) { + MessageRepliesBean messageRepliesBean = messageBean.getMessageRepliesBean(); + if (messageRepliesBean != null && messageRepliesBean.getRepliesSize() > 0) { + TextView replyNumText = msgReplyDetailLayout.findViewById(R.id.reply_num); + replyNumText.setText(replyNumText.getResources().getString(R.string.chat_reply_num, messageRepliesBean.getRepliesSize())); + msgReplyDetailLayout.setVisibility(View.VISIBLE); + msgReplyDetailLayout.setOnClickListener(new View.OnClickListener() { + @Override + public void onClick(View v) { + if (onItemClickListener != null) { + onItemClickListener.onReplyDetailClick(messageBean); + } + } + }); + } else { + msgReplyDetailLayout.setVisibility(View.GONE); + msgReplyDetailLayout.setOnClickListener(null); + } + if (!isReplyDetailMode) { + messageDetailsTimeTv.setVisibility(View.GONE); + } else { + messageDetailsTimeTv.setText(DateTimeUtil.getTimeFormatText(new Date(messageBean.getMessageTime() * 1000))); + messageDetailsTimeTv.setVisibility(View.VISIBLE); + msgReplyDetailLayout.setVisibility(View.GONE); + } + } + + private void setReactContent(TUIMessageBean messageBean) { + MessageReactBean messageReactBean = messageBean.getMessageReactBean(); + if (messageReactBean != null && messageReactBean.getReactSize() > 0) { + reactView.setVisibility(View.VISIBLE); + reactView.setData(messageReactBean); + reactView.setOnLongClickListener(new View.OnLongClickListener() { + @Override + public boolean onLongClick(View v) { + if (onItemClickListener != null) { + onItemClickListener.onMessageLongClick(v, 0, messageBean); + } + return true; + } + }); + if (!isForwardMode) { + reactView.setReactOnClickListener(new ChatFlowReactView.ReactOnClickListener() { + @Override + public void onClick(String emojiId) { + if (onItemClickListener != null) { + onItemClickListener.onReactOnClick(emojiId, messageBean); + } + } + }); + } else { + reactView.setOnLongClickListener(null); + } + } else { + reactView.setVisibility(View.GONE); + reactView.setOnLongClickListener(null); + } + if (!messageBean.isSelf() || isForwardMode || isReplyDetailMode) { + reactView.setThemeColorId(TUIThemeManager.getAttrResId(reactView.getContext(), R.attr.chat_react_other_text_color)); + } else { + reactView.setThemeColorId(0); + } + } + + private void showReadText(TUIMessageBean msg) { + if (msg.isGroup()) { + isReadText.setVisibility(View.VISIBLE); + if (msg.isAllRead()) { + isReadText.setText(R.string.has_all_read); + } else if (msg.isUnread()) { + isReadText.setTextColor(isReadText.getResources().getColor(TUIThemeManager.getAttrResId(isReadText.getContext(), R.attr.chat_read_receipt_text_color))); + isReadText.setText(R.string.unread); + isReadText.setOnClickListener(new View.OnClickListener() { + @Override + public void onClick(View v) { + onReadStatusClick(v, msg); + } + }); + } else { + long readCount = msg.getReadCount(); + if (readCount > 0) { + isReadText.setText(isReadText.getResources().getString(R.string.someone_has_read, readCount)); + isReadText.setTextColor(isReadText.getResources().getColor(TUIThemeManager.getAttrResId(isReadText.getContext(), R.attr.chat_read_receipt_text_color))); + isReadText.setOnClickListener(new View.OnClickListener() { + @Override + public void onClick(View v) { + onReadStatusClick(v, msg); + } + }); + } + } + } else { + isReadText.setVisibility(View.VISIBLE); + if (msg.isPeerRead()) { + isReadText.setText(R.string.has_read); + } else { + isReadText.setText(R.string.unread); + isReadText.setTextColor(isReadText.getResources().getColor(TUIThemeManager.getAttrResId(isReadText.getContext(), R.attr.chat_read_receipt_text_color))); + isReadText.setOnClickListener(new View.OnClickListener() { + @Override + public void onClick(View v) { + onReadStatusClick(v, msg); + } + }); + } + } + } + + public abstract void layoutVariableViews(final TUIMessageBean msg, final int position); + + public void onRecycled() { + if (selectableTextHelper != null) { + selectableTextHelper.destroy(); + } + } + + public void onReadStatusClick(View view, TUIMessageBean messageBean) { + if (onItemClickListener != null) { + onItemClickListener.onMessageReadStatusClick(view, messageBean); + } + } + + protected void setSelectableTextHelper(TUIMessageBean msg, TextView textView, int position, boolean isEmoji) { + if (selectableTextHelper != null) { + selectableTextHelper.destroy(); + } + selectableTextHelper = new SelectTextHelper + .Builder(textView) + .setCursorHandleColor(TIMCommonService.getAppContext().getResources().getColor(R.color.font_blue)) + .setCursorHandleSizeInDp(18) + .setSelectedColor(TIMCommonService.getAppContext().getResources().getColor(R.color.test_blue)) + .setSelectAll(true) + .setIsEmoji(isEmoji) + .setScrollShow(false) + .setSelectedAllNoPop(true) + .setMagnifierShow(false) + .build(); + + selectableTextHelper.setSelectListener(new SelectTextHelper.OnSelectListener() { + @Override + public void onClick(View v) { + } + + @Override + public void onLongClick(View v) { + } + + @Override + public void onTextSelected(CharSequence content) { + String selectedText = content.toString(); + msg.setSelectText(selectedText); + TIMCommonLog.d("TextMessageHolder", "onTextSelected selectedText = " + selectedText); + if (onItemClickListener != null) { + onItemClickListener.onTextSelected(msgArea, position, msg); + } + } + + @Override + public void onDismiss() { + msg.setSelectText(msg.getExtra()); + } + + @Override + public void onClickUrl(String url) { + } + + @Override + public void onSelectAllShowCustomPop() { + } + + @Override + public void onReset() { + msg.setSelectText(null); + msg.setSelectText(msg.getExtra()); + } + + @Override + public void onDismissCustomPop() { + } + + @Override + public void onScrolling() { + } + }); + } + + public void setNeedShowTranslation(boolean needShowTranslation) { + isNeedShowTranslation = needShowTranslation; + } + + public void setShowRead(boolean showRead) { + isShowRead = showRead; + } +} diff --git a/TIMCommon/timcommon/src/main/java/com/tencent/qcloud/tuikit/timcommon/classicui/widget/message/SelectTextHelper.java b/TIMCommon/timcommon/src/main/java/com/tencent/qcloud/tuikit/timcommon/classicui/widget/message/SelectTextHelper.java new file mode 100644 index 0000000..0efb9ea --- /dev/null +++ b/TIMCommon/timcommon/src/main/java/com/tencent/qcloud/tuikit/timcommon/classicui/widget/message/SelectTextHelper.java @@ -0,0 +1,937 @@ +package com.tencent.qcloud.tuikit.timcommon.classicui.widget.message; + +import android.content.Context; +import android.content.res.Resources; +import android.graphics.Bitmap; +import android.graphics.Canvas; +import android.graphics.Paint; +import android.graphics.drawable.BitmapDrawable; +import android.graphics.drawable.Drawable; +import android.graphics.drawable.LayerDrawable; +import android.graphics.drawable.ShapeDrawable; +import android.os.Build; +import android.text.Layout; +import android.text.Selection; +import android.text.Spannable; +import android.text.Spanned; +import android.text.TextUtils; +import android.text.method.LinkMovementMethod; +import android.text.style.BackgroundColorSpan; +import android.text.style.ClickableSpan; +import android.text.style.ImageSpan; +import android.text.style.URLSpan; +import android.util.Pair; +import android.view.Gravity; +import android.view.MotionEvent; +import android.view.View; +import android.view.ViewConfiguration; +import android.view.ViewGroup; +import android.view.ViewTreeObserver; +import android.widget.Magnifier; +import android.widget.PopupWindow; +import android.widget.TextView; + +import androidx.annotation.ColorInt; +import androidx.annotation.DrawableRes; +import androidx.annotation.StringRes; + +import com.tencent.qcloud.tuikit.timcommon.classicui.component.BeginnerGuidePage; +import com.tencent.qcloud.tuikit.timcommon.component.face.FaceManager; +import com.tencent.qcloud.tuikit.timcommon.util.TIMCommonLog; + +import java.util.LinkedList; +import java.util.List; +import java.util.regex.Matcher; +import java.util.regex.Pattern; + + +public class SelectTextHelper { + private static final String TAG = SelectTextHelper.class.getSimpleName(); + + private static int DEFAULT_SELECTION_LENGTH = 2; + private static int DEFAULT_SHOW_DURATION = 100; + + private CursorHandle mStartHandle; + private CursorHandle mEndHandle; + private Magnifier mMagnifier; + private SelectionInfo mSelectionInfo = new SelectionInfo(); + private OnSelectListener mSelectListener; + + private Context mContext; + private TextView mTextView; + private Spannable mSpannable; + + private int mTouchX; + private int mTouchY; + private int mTextViewMarginStart = 0;// textView的marginStart值 + + private int mSelectedColor; + private int mCursorHandleColor; + private int mCursorHandleSize; + private boolean mSelectAll; + private boolean mSelectedAllNoPop; + private boolean mScrollShow; + private boolean mMagnifierShow; + private int mPopSpanCount; + private int mPopBgResource; + private int mPopArrowImg; + private boolean mIsEmoji = false; + private List> itemTextList; + private List itemListenerList = new LinkedList<>(); + + private BackgroundColorSpan mSpan; + private boolean isHideWhenScroll; + private boolean isHide = true; + private boolean usedClickListener = false; + + private ViewTreeObserver.OnPreDrawListener mOnPreDrawListener; + private ViewTreeObserver.OnScrollChangedListener mOnScrollChangedListener; + private View.OnTouchListener mRootTouchListener; + + + public interface OnSelectListener { + void onClick(View v);// 点击textView + + void onLongClick(View v);// 长按textView + + void onTextSelected(CharSequence content);// 选中文本回调 + + void onDismiss();// 解除弹窗回调 + + void onClickUrl(String url);// 点击文本里的url回调 + + void onSelectAllShowCustomPop();// 全选显示自定义弹窗回调 + + void onReset();// 重置回调 + + void onDismissCustomPop();// 解除自定义弹窗回调 + + void onScrolling();// 正在滚动回调 + } + + public static class Builder { + private TextView mTextView; + private int mCursorHandleColor = 0xFF1379D6; + private int mSelectedColor = 0xFFAFE1F4; + private float mCursorHandleSizeInDp = 24; + private boolean mSelectAll = true; + private boolean mSelectedAllNoPop = false; + private boolean mScrollShow = true; + private boolean mMagnifierShow = true; + private int mPopSpanCount = 5; + private int mPopBgResource = 0; + private int mPopArrowImg = 0; + private boolean mIsEmoji = false; + private List> itemTextList = new LinkedList<>(); + private List itemListenerList = new LinkedList<>(); + + public Builder(TextView textView) { + mTextView = textView; + } + + /** + * 选择游标颜色 + */ + public Builder setCursorHandleColor(@ColorInt int cursorHandleColor) { + mCursorHandleColor = cursorHandleColor; + return this; + } + + /** + * 选择游标大小 + */ + public Builder setCursorHandleSizeInDp(float cursorHandleSizeInDp) { + mCursorHandleSizeInDp = cursorHandleSizeInDp; + return this; + } + + /** + * 选中文本的颜色 + */ + public Builder setSelectedColor(@ColorInt int selectedBgColor) { + mSelectedColor = selectedBgColor; + return this; + } + + /** + * 全选 + */ + public Builder setSelectAll(boolean selectAll) { + mSelectAll = selectAll; + return this; + } + + /** + * 已经全选无弹窗 + */ + public Builder setSelectedAllNoPop(boolean selectedAllNoPop) { + mSelectedAllNoPop = selectedAllNoPop; + return this; + } + + /** + * 滑动依然显示弹窗 + */ + public Builder setScrollShow(boolean scrollShow) { + mScrollShow = scrollShow; + return this; + } + + /** + * 显示放大镜 + */ + public Builder setMagnifierShow(boolean magnifierShow) { + mMagnifierShow = magnifierShow; + return this; + } + + /** + * 弹窗每行个数 + */ + public Builder setPopSpanCount(int popSpanCount) { + mPopSpanCount = popSpanCount; + return this; + } + + /** + * 弹窗背景颜色、弹窗箭头 + */ + public Builder setPopStyle(int popBgResource, int popArrowImg) { + mPopBgResource = popBgResource; + mPopArrowImg = popArrowImg; + return this; + } + + public Builder setIsEmoji(boolean isEmoji) { + mIsEmoji = isEmoji; + return this; + } + + public Builder addItem(@DrawableRes int drawableId, @StringRes int textResId, onSeparateItemClickListener listener) { + itemTextList.add(new Pair<>(drawableId, mTextView.getContext().getResources().getString(textResId))); + itemListenerList.add(listener); + return this; + } + + public Builder addItem(@DrawableRes int drawableId, String itemText, onSeparateItemClickListener listener) { + itemTextList.add(new Pair<>(drawableId, itemText)); + itemListenerList.add(listener); + return this; + } + + public SelectTextHelper build() { + return new SelectTextHelper(this); + } + + public interface onSeparateItemClickListener { + void onClick(); + } + } + + public SelectTextHelper(Builder builder) { + mTextView = builder.mTextView; + mContext = mTextView.getContext(); + mSelectedColor = builder.mSelectedColor; + mCursorHandleColor = builder.mCursorHandleColor; + mSelectAll = builder.mSelectAll; + mIsEmoji = builder.mIsEmoji; + mScrollShow = builder.mScrollShow; + mMagnifierShow = builder.mMagnifierShow; + mPopSpanCount = builder.mPopSpanCount; + mPopBgResource = builder.mPopBgResource; + mPopArrowImg = builder.mPopArrowImg; + mSelectedAllNoPop = builder.mSelectedAllNoPop; + itemTextList = builder.itemTextList; + itemListenerList = builder.itemListenerList; + mCursorHandleSize = dp2px(builder.mCursorHandleSizeInDp); + init(); + } + + /** + * 重置弹窗 + */ + public void reset() { + TIMCommonLog.d(TAG, "reset"); + hideSelectView(); + resetSelectionInfo(); + // 重置弹窗回调 + if (mSelectListener != null) { + mSelectListener.onReset(); + } + } + + + /** + * 选择文本监听 + */ + public void setSelectListener(OnSelectListener selectListener) { + mSelectListener = selectListener; + } + + /** + * 销毁 + */ + public void destroy() { + mTextView.getViewTreeObserver().removeOnScrollChangedListener(mOnScrollChangedListener); + mTextView.getViewTreeObserver().removeOnPreDrawListener(mOnPreDrawListener); + mTextView.getRootView().setOnTouchListener(null); + reset(); + mStartHandle = null; + mEndHandle = null; + } + + /** + * 全选 + */ + public void selectAll() { + showAllView(); + } + + /** + * public end + */ + + private void init() { + mTextView.setText(mTextView.getText(), TextView.BufferType.SPANNABLE); + + mTextView.setOnTouchListener((v, event) -> { + mTouchX = (int) event.getX(); + mTouchY = (int) event.getY(); + return false; + }); + + mTextView.setOnClickListener(v -> { + if (usedClickListener) { + usedClickListener = false; + return; + } + if (null != mSelectListener) { + mSelectListener.onDismiss(); + } + reset(); + if (null != mSelectListener) { + mSelectListener.onClick(mTextView); + } + }); + + mTextView.setOnLongClickListener(new View.OnLongClickListener() { + @Override + public boolean onLongClick(View v) { + BeginnerGuidePage.showBeginnerGuideThen(mTextView, new Runnable() { + @Override + public void run() { + onLongTextViewClick(); + } + }); + return true; + } + + private void onLongTextViewClick() { + mTextView.addOnAttachStateChangeListener(new View.OnAttachStateChangeListener() { + @Override + public void onViewAttachedToWindow(View v) { + } + + @Override + public void onViewDetachedFromWindow(View v) { + destroy(); + } + }); + + mOnPreDrawListener = new ViewTreeObserver.OnPreDrawListener() { + @Override + public boolean onPreDraw() { + if (isHideWhenScroll) { + isHideWhenScroll = false; + postShowSelectView(DEFAULT_SHOW_DURATION); + } + // 拿textView的x坐标 + if (0 == mTextViewMarginStart) { + int[] location = new int[2]; + mTextView.getLocationInWindow(location); + mTextViewMarginStart = location[0]; + } + return true; + } + }; + mTextView.getViewTreeObserver().addOnPreDrawListener(mOnPreDrawListener); + + // 根布局监听 + mRootTouchListener = new View.OnTouchListener() { + @Override + public boolean onTouch(View v, MotionEvent event) { + reset(); + mTextView.getRootView().setOnTouchListener(null); + return false; + } + }; + mTextView.getRootView().setOnTouchListener(mRootTouchListener); + + mOnScrollChangedListener = new ViewTreeObserver.OnScrollChangedListener() { + @Override + public void onScrollChanged() { + if (mScrollShow) { + if (!isHideWhenScroll && !isHide) { + isHideWhenScroll = true; + if (mStartHandle != null) { + mStartHandle.dismiss(); + } + if (mEndHandle != null) { + mEndHandle.dismiss(); + } + } + if (null != mSelectListener) { + mSelectListener.onScrolling(); + } + } else { + reset(); + } + } + }; + mTextView.getViewTreeObserver().addOnScrollChangedListener(mOnScrollChangedListener); + + if (mSelectAll) { + showAllView(); + } else { + showSelectView(mTouchX, mTouchY); + } + if (null != mSelectListener) { + mSelectListener.onLongClick(mTextView); + } + } + }); + // 此setMovementMethod可被修改 + mTextView.setMovementMethod(new LinkMovementMethodInterceptor()); + } + + private void postShowSelectView(int duration) { + mTextView.removeCallbacks(mShowSelectViewRunnable); + if (duration <= 0) { + mShowSelectViewRunnable.run(); + } else { + mTextView.postDelayed(mShowSelectViewRunnable, duration); + } + } + + private final Runnable mShowSelectViewRunnable = new Runnable() { + @Override + public void run() { + if (isHide) return; + if (mStartHandle != null) { + showCursorHandle(mStartHandle); + } + if (mEndHandle != null) { + showCursorHandle(mEndHandle); + } + } + }; + + private void hideSelectView() { + isHide = true; + usedClickListener = false; + if (mStartHandle != null) { + TIMCommonLog.d(TAG, "mStartHandle.dismiss();"); + mStartHandle.dismiss(); + } + if (mEndHandle != null) { + TIMCommonLog.d(TAG, "mEndHandle.dismiss();"); + mEndHandle.dismiss(); + } + } + + private void resetSelectionInfo() { + mSelectionInfo.mSelectionContent = null; + if (mSpannable != null && mSpan != null) { + TIMCommonLog.d(TAG, "mSpannable.removeSpan(mSpan);"); + mSpannable.removeSpan(mSpan); + mSpan = null; + } + } + + private void showSelectView(int x, int y) { + reset(); + isHide = false; + if (mStartHandle == null) mStartHandle = new CursorHandle(true); + if (mEndHandle == null) mEndHandle = new CursorHandle(false); + + int startOffset = getPreciseOffset(mTextView, x, y); + int endOffset = startOffset + DEFAULT_SELECTION_LENGTH; + if (mTextView.getText() instanceof Spannable) { + mSpannable = (Spannable) mTextView.getText(); + } + if (mSpannable == null || endOffset - 1 >= mTextView.getText().length()) { + return; + } + selectText(startOffset, endOffset); + showCursorHandle(mStartHandle); + showCursorHandle(mEndHandle); + } + + /** + * 全选 + * Select all + */ + private void showAllView() { + reset(); + isHide = false; + if (mStartHandle == null) mStartHandle = new CursorHandle(true); + if (mEndHandle == null) mEndHandle = new CursorHandle(false); + + if (mTextView.getText() instanceof Spannable) { + mSpannable = (Spannable) mTextView.getText(); + } + if (mSpannable == null) { + return; + } + selectText(0, mTextView.getText().length()); + showCursorHandle(mStartHandle); + showCursorHandle(mEndHandle); + } + + private void showCursorHandle(CursorHandle cursorHandle) { + Layout layout = mTextView.getLayout(); + if (layout == null) { + return; + } + int offset = cursorHandle.isLeft ? mSelectionInfo.mStart : mSelectionInfo.mEnd; + cursorHandle.show((int) layout.getPrimaryHorizontal(offset), layout.getLineBottom(layout.getLineForOffset(offset))); + } + + private void selectText(int startPos, int endPos) { + if (startPos != -1) { + mSelectionInfo.mStart = startPos; + } + if (endPos != -1) { + mSelectionInfo.mEnd = endPos; + } + if (mSelectionInfo.mStart > mSelectionInfo.mEnd) { + int temp = mSelectionInfo.mStart; + mSelectionInfo.mStart = mSelectionInfo.mEnd; + mSelectionInfo.mEnd = temp; + } + + if (mSpannable != null) { + if (mSpan == null) { + mSpan = new BackgroundColorSpan(mSelectedColor); + } + mSelectionInfo.mSelectionContent = mSpannable.subSequence(mSelectionInfo.mStart, mSelectionInfo.mEnd).toString(); + mSpannable.setSpan(mSpan, mSelectionInfo.mStart, mSelectionInfo.mEnd, Spanned.SPAN_INCLUSIVE_EXCLUSIVE); + if (mSelectListener != null) { + mSelectListener.onTextSelected(mSelectionInfo.mSelectionContent); + } + + if (mIsEmoji) { + //handlerEmojiSelectText(); + } + } + } + + private void handlerEmojiSelectText() { + String regex = "\\[(\\S+?)\\]"; + Pattern p = Pattern.compile(regex); + Matcher m = p.matcher(mSelectionInfo.mSelectionContent); + + while (m.find()) { + String emojiName = m.group(); + Bitmap bitmap = FaceManager.getEmoji(emojiName); + if (bitmap != null) { + + Drawable drawable = new BitmapDrawable(bitmap); + ShapeDrawable background = new ShapeDrawable(); + background.getPaint().setColor(mTextView.getContext().getResources().getColor(com.tencent.qcloud.tuikit.timcommon.R.color.text_select_color)); + LayerDrawable layerDrawable = new LayerDrawable(new Drawable[]{background, drawable}); + layerDrawable.setBounds(0, 0, 64, 64); + ImageSpan image = new ImageSpan(layerDrawable, ImageSpan.ALIGN_BASELINE); + mSpannable.setSpan(image, mSelectionInfo.mStart, mSelectionInfo.mEnd, Spanned.SPAN_INCLUSIVE_EXCLUSIVE); + } + } + } + + /** + * 游标 + */ + private class CursorHandle extends View { + + private PopupWindow mPopupWindow; + private Paint mPaint; + + private int mCircleRadius = mCursorHandleSize / 2; + private int mWidth = mCursorHandleSize; + private int mHeight = mCursorHandleSize; + private int mPadding = 32;// 游标padding + private boolean isLeft; + + public CursorHandle(boolean isLeft) { + super(mContext); + this.isLeft = isLeft; + mPaint = new Paint(Paint.ANTI_ALIAS_FLAG); + mPaint.setColor(mCursorHandleColor); + + mPopupWindow = new PopupWindow(this); + mPopupWindow.setClippingEnabled(false); + mPopupWindow.setWidth(mWidth + mPadding * 2); + mPopupWindow.setHeight(mHeight + mPadding / 2); + invalidate(); + } + + @Override + protected void onDraw(Canvas canvas) { + canvas.drawCircle(mCircleRadius + mPadding, mCircleRadius, mCircleRadius, mPaint); + if (isLeft) { + canvas.drawRect(mCircleRadius + mPadding, 0, mCircleRadius * 2 + mPadding, mCircleRadius, mPaint); + } else { + canvas.drawRect(mPadding, 0, mCircleRadius + mPadding, mCircleRadius, mPaint); + } + } + + private int mAdjustX; + private int mAdjustY; + + private int mBeforeDragStart; + private int mBeforeDragEnd; + + @Override + public boolean onTouchEvent(MotionEvent event) { + switch (event.getAction()) { + case MotionEvent.ACTION_DOWN: + mBeforeDragStart = mSelectionInfo.mStart; + mBeforeDragEnd = mSelectionInfo.mEnd; + mAdjustX = (int) event.getX(); + mAdjustY = (int) event.getY(); + break; + case MotionEvent.ACTION_UP: + case MotionEvent.ACTION_CANCEL: + if (mMagnifierShow) { + // android 9 放大镜 + if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.P + && null != mMagnifier) { + mMagnifier.dismiss(); + } + } + break; + case MotionEvent.ACTION_MOVE: + if (null != mSelectListener) { + mSelectListener.onDismissCustomPop(); + } + int rawX = (int) event.getRawX(); + int rawY = (int) event.getRawY(); + // x y不准 x 减去textView距离x轴距离值 y减去字体大小的像素值 + update(rawX + mAdjustX - mWidth - mTextViewMarginStart, + rawY + mAdjustY - mHeight - (int) mTextView.getTextSize()); + if (mMagnifierShow) { + // android 9 放大镜功能 + if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.P) { + if (null == mMagnifier) { + mMagnifier = new Magnifier(mTextView); + mMagnifier.getWidth(); + } + final int[] viewPosition = new int[2]; + mTextView.getLocationOnScreen(viewPosition); + int magnifierX = rawX - viewPosition[0]; + int magnifierY = rawY - viewPosition[1] - dp2px(32); + mMagnifier.show(magnifierX, Math.max(magnifierY, 0)); + } + } + break; + } + return true; + } + + private void changeDirection() { + isLeft = !isLeft; + invalidate(); + } + + public void dismiss() { + mPopupWindow.dismiss(); + } + + private int[] mTempCoors = new int[2]; + + public void update(int x, int y) { + mTextView.getLocationInWindow(mTempCoors); + int oldOffset; + if (isLeft) { + oldOffset = mSelectionInfo.mStart; + } else { + oldOffset = mSelectionInfo.mEnd; + } + + y -= mTempCoors[1]; + + int offset = getHysteresisOffset(mTextView, x, y, oldOffset); + + if (offset != oldOffset) { + resetSelectionInfo(); + if (isLeft) { + if (offset > mBeforeDragEnd) { + CursorHandle handle = getCursorHandle(false); + changeDirection(); + handle.changeDirection(); + mBeforeDragStart = mBeforeDragEnd; + selectText(mBeforeDragEnd, offset); + handle.updateCursorHandle(); + } else { + selectText(offset, -1); + } + updateCursorHandle(); + } else { + if (offset < mBeforeDragStart) { + CursorHandle handle = getCursorHandle(true); + handle.changeDirection(); + changeDirection(); + mBeforeDragEnd = mBeforeDragStart; + selectText(offset, mBeforeDragStart); + handle.updateCursorHandle(); + } else { + selectText(mBeforeDragStart, offset); + } + updateCursorHandle(); + } + } + } + + private void updateCursorHandle() { + mTextView.getLocationInWindow(mTempCoors); + Layout layout = mTextView.getLayout(); + if (isLeft) { + mPopupWindow.update((int) layout.getPrimaryHorizontal(mSelectionInfo.mStart) - mWidth + getExtraX(), + layout.getLineBottom(layout.getLineForOffset(mSelectionInfo.mStart)) + getExtraY(), -1, -1); + } else { + mPopupWindow.update((int) layout.getPrimaryHorizontal(mSelectionInfo.mEnd) + getExtraX(), + layout.getLineBottom(layout.getLineForOffset(mSelectionInfo.mEnd)) + getExtraY(), -1, -1); + } + } + + public void show(int x, int y) { + mTextView.getLocationInWindow(mTempCoors); + int offset = isLeft ? mWidth : 0; + mPopupWindow.showAtLocation(mTextView, Gravity.NO_GRAVITY, x - offset + getExtraX(), y + getExtraY()); + } + + public int getExtraX() { + return mTempCoors[0] - mPadding + mTextView.getPaddingLeft(); + } + + public int getExtraY() { + return mTempCoors[1] + mTextView.getPaddingTop(); + } + } + + private CursorHandle getCursorHandle(boolean isLeft) { + if (mStartHandle.isLeft == isLeft) { + return mStartHandle; + } else { + return mEndHandle; + } + } + + private class SelectionInfo { + public int mStart; + public int mEnd; + public String mSelectionContent; + } + + /** + * 处理内容链接跳转 + */ + private class LinkMovementMethodInterceptor extends LinkMovementMethod { + + private long downLinkTime; + + @Override + public boolean onTouchEvent(TextView widget, Spannable buffer, MotionEvent event) { + int action = event.getAction(); + + if (action == MotionEvent.ACTION_UP || + action == MotionEvent.ACTION_DOWN) { + int x = (int) event.getX(); + int y = (int) event.getY(); + + x -= widget.getTotalPaddingLeft(); + y -= widget.getTotalPaddingTop(); + + x += widget.getScrollX(); + y += widget.getScrollY(); + + Layout layout = widget.getLayout(); + int line = layout.getLineForVertical(y); + int off = layout.getOffsetForHorizontal(line, x); + + ClickableSpan[] links = + buffer.getSpans(off, off, ClickableSpan.class); + + if (links.length != 0) { + if (action == MotionEvent.ACTION_UP) { + // 长按 + if (downLinkTime + ViewConfiguration.getLongPressTimeout() < System.currentTimeMillis()) { + return false; + } + // 点击 + if (links[0] instanceof URLSpan) { + URLSpan url = (URLSpan) links[0]; + if (!TextUtils.isEmpty(url.getURL())) { + if (null != mSelectListener) { + usedClickListener = true; + mSelectListener.onClickUrl(url.getURL()); + } + return true; + } else { + links[0].onClick(widget); + } + } + } else if (action == MotionEvent.ACTION_DOWN) { + downLinkTime = System.currentTimeMillis(); + Selection.setSelection(buffer, + buffer.getSpanStart(links[0]), + buffer.getSpanEnd(links[0])); + } + return true; + } else { + Selection.removeSelection(buffer); + } + } + + return super.onTouchEvent(widget, buffer, event); + } + + } + + // util + + public static int getPreciseOffset(TextView textView, int x, int y) { + Layout layout = textView.getLayout(); + if (layout != null) { + int topVisibleLine = layout.getLineForVertical(y); + int offset = layout.getOffsetForHorizontal(topVisibleLine, x); + + int offsetX = (int) layout.getPrimaryHorizontal(offset); + + if (offsetX > x) { + return layout.getOffsetToLeftOf(offset); + } else { + return offset; + } + } else { + return -1; + } + } + + public static int getHysteresisOffset(TextView textView, int x, int y, int previousOffset) { + final Layout layout = textView.getLayout(); + if (layout == null) return -1; + + int line = layout.getLineForVertical(y); + + // The "HACK BLOCK"S in this function is required because of how Android Layout for + // TextView works - if 'offset' equals to the last character of a line, then + // + // * getLineForOffset(offset) will result the NEXT line + // * getPrimaryHorizontal(offset) will return 0 because the next insertion point is on the next line + // * getOffsetForHorizontal(line, x) will not return the last offset of a line no matter where x is + // These are highly undesired and is worked around with the HACK BLOCK + // + // @see Moon+ Reader/Color Note - see how it can't select the last character of a line unless you move + // the cursor to the beginning of the next line. + // + ////////////////////HACK BLOCK//////////////////////////////////////////////////// + + if (isEndOfLineOffset(layout, previousOffset)) { + // we have to minus one from the offset so that the code below to find + // the previous line can work correctly. + int left = (int) layout.getPrimaryHorizontal(previousOffset - 1); + int right = (int) layout.getLineRight(line); + int threshold = (right - left) / 2; // half the width of the last character + if (x > right - threshold) { + previousOffset -= 1; + } + } + /////////////////////////////////////////////////////////////////////////////////// + + final int previousLine = layout.getLineForOffset(previousOffset); + final int previousLineTop = layout.getLineTop(previousLine); + final int previousLineBottom = layout.getLineBottom(previousLine); + final int hysteresisThreshold = (previousLineBottom - previousLineTop) / 2; + + // If new line is just before or after previous line and y position is less than + // hysteresisThreshold away from previous line, keep cursor on previous line. + if (((line == previousLine + 1) && ((y - previousLineBottom) < hysteresisThreshold)) || ((line == previousLine - 1) && (( + previousLineTop + - y) < hysteresisThreshold))) { + line = previousLine; + } + + int offset = layout.getOffsetForHorizontal(line, x); + + // This allow the user to select the last character of a line without moving the + // cursor to the next line. (As Layout.getOffsetForHorizontal does not return the + // offset of the last character of the specified line) + // + // But this function will probably get called again immediately, must decrement the offset + // by 1 to compensate for the change made below. (see previous HACK BLOCK) + /////////////////////HACK BLOCK/////////////////////////////////////////////////// + if (offset < textView.getText().length() - 1) { + if (isEndOfLineOffset(layout, offset + 1)) { + int left = (int) layout.getPrimaryHorizontal(offset); + int right = (int) layout.getLineRight(line); + int threshold = (right - left) / 2; // half the width of the last character + if (x > right - threshold) { + offset += 1; + } + } + } + ////////////////////////////////////////////////////////////////////////////////// + + return offset; + } + + private static boolean isEndOfLineOffset(Layout layout, int offset) { + return offset > 0 && layout.getLineForOffset(offset) == layout.getLineForOffset(offset - 1) + 1; + } + + public static int getDisplayWidth() { + return Resources.getSystem().getDisplayMetrics().widthPixels; + } + + public static int getDisplayHeight() { + return Resources.getSystem().getDisplayMetrics().heightPixels; + } + + public static int dp2px(float dpValue) { + return (int) (dpValue * Resources.getSystem().getDisplayMetrics().density + 0.5f); + } + + /** + * 设置宽高 + * + * @param v + * @param w + * @param h + */ + public static void setWidthHeight(View v, int w, int h) { + ViewGroup.LayoutParams params = v.getLayoutParams(); + params.width = w; + params.height = h; + v.setLayoutParams(params); + } + + /** + * 通知栏的高度 + */ + private static int STATUS_HEIGHT = 0; + + /** + * 获取通知栏的高度 + */ + public static int getStatusHeight() { + if (0 != STATUS_HEIGHT) { + return STATUS_HEIGHT; + } + int resid = Resources.getSystem().getIdentifier("status_bar_height", "dimen", "android"); + if (resid > 0) { + STATUS_HEIGHT = Resources.getSystem().getDimensionPixelSize(resid); + return STATUS_HEIGHT; + } + return -1; + } +} \ No newline at end of file diff --git a/TIMCommon/timcommon/src/main/java/com/tencent/qcloud/tuikit/timcommon/classicui/widget/message/TUIReplyQuoteView.java b/TIMCommon/timcommon/src/main/java/com/tencent/qcloud/tuikit/timcommon/classicui/widget/message/TUIReplyQuoteView.java new file mode 100644 index 0000000..b1ba62b --- /dev/null +++ b/TIMCommon/timcommon/src/main/java/com/tencent/qcloud/tuikit/timcommon/classicui/widget/message/TUIReplyQuoteView.java @@ -0,0 +1,32 @@ +package com.tencent.qcloud.tuikit.timcommon.classicui.widget.message; + +import android.content.Context; +import android.view.LayoutInflater; +import android.widget.FrameLayout; + +import com.tencent.qcloud.tuikit.timcommon.bean.TUIReplyQuoteBean; + +public abstract class TUIReplyQuoteView> extends FrameLayout { + + public abstract int getLayoutResourceId(); + + public TUIReplyQuoteView(Context context) { + super(context); + int resId = getLayoutResourceId(); + if (resId != 0) { + LayoutInflater.from(context).inflate(resId, this, true); + } + } + + public abstract void onDrawReplyQuote(T quoteBean); + + /** + * 原始消息发送者是否为自己 , 用于不同 UI 展示 + * + * Whether the original message sender is himself, used for different UI displays + * + * @param isSelf + */ + public void setSelf(boolean isSelf) {} + +} diff --git a/TIMCommon/timcommon/src/main/java/com/tencent/qcloud/tuikit/timcommon/component/CustomLinearLayoutManager.java b/TIMCommon/timcommon/src/main/java/com/tencent/qcloud/tuikit/timcommon/component/CustomLinearLayoutManager.java new file mode 100644 index 0000000..381e2d1 --- /dev/null +++ b/TIMCommon/timcommon/src/main/java/com/tencent/qcloud/tuikit/timcommon/component/CustomLinearLayoutManager.java @@ -0,0 +1,36 @@ +package com.tencent.qcloud.tuikit.timcommon.component; + +import android.content.Context; +import android.util.AttributeSet; +import android.util.Log; + +import androidx.recyclerview.widget.LinearLayoutManager; +import androidx.recyclerview.widget.RecyclerView; + +/** + * https://stackoverflow.com/questions/30458640/recyclerview-java-lang-indexoutofboundsexception-inconsistency-detected-inval + */ +public class CustomLinearLayoutManager extends LinearLayoutManager { + + public CustomLinearLayoutManager(Context context) { + super(context); + } + + public CustomLinearLayoutManager(Context context, int orientation, boolean reverseLayout) { + super(context, orientation, reverseLayout); + } + + public CustomLinearLayoutManager(Context context, AttributeSet attrs, int defStyleAttr, int defStyleRes) { + super(context, attrs, defStyleAttr, defStyleRes); + } + + @Override + public void onLayoutChildren(RecyclerView.Recycler recycler, RecyclerView.State state) { + try { + super.onLayoutChildren(recycler, state); + } catch (IndexOutOfBoundsException e) { + Log.w("CustomLinearLayoutManager", e.getLocalizedMessage()); + } + + } +} diff --git a/TIMCommon/timcommon/src/main/java/com/tencent/qcloud/tuikit/timcommon/component/LineControllerView.java b/TIMCommon/timcommon/src/main/java/com/tencent/qcloud/tuikit/timcommon/component/LineControllerView.java new file mode 100644 index 0000000..52edcc2 --- /dev/null +++ b/TIMCommon/timcommon/src/main/java/com/tencent/qcloud/tuikit/timcommon/component/LineControllerView.java @@ -0,0 +1,138 @@ +package com.tencent.qcloud.tuikit.timcommon.component; + +import android.content.Context; +import android.content.res.TypedArray; +import android.graphics.drawable.Drawable; +import android.util.AttributeSet; +import android.view.LayoutInflater; +import android.view.View; +import android.view.ViewGroup; +import android.widget.CompoundButton; +import android.widget.ImageView; +import android.widget.RelativeLayout; +import android.widget.Switch; +import android.widget.TextView; + +import com.tencent.qcloud.tuikit.timcommon.R; +import com.tencent.qcloud.tuikit.timcommon.util.ScreenUtil; + +/** + * Custom LineControllerView + */ +public class LineControllerView extends RelativeLayout { + + private String mName; + private boolean mIsBottom; + private boolean mIsTop; + private String mContent; + private boolean mIsJump; + private boolean mIsSwitch; + + protected TextView mNameText; + protected TextView mContentText; + private ImageView mNavArrowView; + protected Switch mSwitchView; + protected View bottomLine; + private View mMask; + private View container; + + public LineControllerView(Context context, AttributeSet attrs) { + super(context, attrs); + LayoutInflater.from(context).inflate(R.layout.line_controller_view, this); + TypedArray ta = context.obtainStyledAttributes(attrs, R.styleable.LineControllerView, 0, 0); + try { + mName = ta.getString(R.styleable.LineControllerView_name); + mContent = ta.getString(R.styleable.LineControllerView_subject); + mIsBottom = ta.getBoolean(R.styleable.LineControllerView_isBottom, false); + mIsTop = ta.getBoolean(R.styleable.LineControllerView_isTop, false); + mIsJump = ta.getBoolean(R.styleable.LineControllerView_canNav, false); + mIsSwitch = ta.getBoolean(R.styleable.LineControllerView_isSwitch, false); + setUpView(); + } finally { + ta.recycle(); + } + } + + private void setUpView() { + mNameText = findViewById(R.id.name); + mNameText.setText(mName); + mContentText = findViewById(R.id.content); + mContentText.setText(mContent); + bottomLine = findViewById(R.id.bottom_line); + View topLine = findViewById(R.id.top_line); + bottomLine.setVisibility(mIsBottom ? VISIBLE : GONE); + topLine.setVisibility(mIsTop ? VISIBLE : GONE); + mNavArrowView = findViewById(R.id.rightArrow); + mNavArrowView.setVisibility(mIsJump ? VISIBLE : GONE); + RelativeLayout contentLayout = findViewById(R.id.contentText); + contentLayout.setVisibility(mIsSwitch ? GONE : VISIBLE); + mSwitchView = findViewById(R.id.btnSwitch); + mSwitchView.setVisibility(mIsSwitch ? VISIBLE : GONE); + mMask = findViewById(R.id.disable_mask); + container = findViewById(R.id.view_container); + } + + public void setBackground(Drawable drawable) { + super.setBackground(drawable); + container.setBackground(drawable); + } + + public String getContent() { + return mContentText.getText().toString(); + } + + public void setContent(String content) { + this.mContent = content; + mContentText.setText(content); + } + + public void setSingleLine(boolean singleLine) { + mContentText.setSingleLine(singleLine); + } + + /** + * Set whether to jump + * + * @param canNav + */ + public void setCanNav(boolean canNav) { + this.mIsJump = canNav; + mNavArrowView.setVisibility(canNav ? VISIBLE : GONE); + if (canNav) { + ViewGroup.LayoutParams params = mContentText.getLayoutParams(); + params.width = ScreenUtil.getPxByDp(120); + params.height = ViewGroup.LayoutParams.WRAP_CONTENT; + mContentText.setLayoutParams(params); + mContentText.setTextIsSelectable(false); + } else { + ViewGroup.LayoutParams params = mContentText.getLayoutParams(); + params.width = ViewGroup.LayoutParams.WRAP_CONTENT; + params.height = ViewGroup.LayoutParams.WRAP_CONTENT; + mContentText.setLayoutParams(params); + mContentText.setTextIsSelectable(true); + } + } + + public boolean isChecked() { + return mSwitchView.isChecked(); + } + + public void setChecked(boolean on) { + mSwitchView.setChecked(on); + } + + public void setCheckListener(CompoundButton.OnCheckedChangeListener listener) { + mSwitchView.setOnCheckedChangeListener(listener); + } + + public void setMask(boolean enableMask) { + if (enableMask) { + mMask.setVisibility(View.VISIBLE); + mSwitchView.setEnabled(false); + } else { + mMask.setVisibility(View.GONE); + mSwitchView.setEnabled(true); + } + } + +} diff --git a/TIMCommon/timcommon/src/main/java/com/tencent/qcloud/tuikit/timcommon/component/MaxWidthFrameLayout.java b/TIMCommon/timcommon/src/main/java/com/tencent/qcloud/tuikit/timcommon/component/MaxWidthFrameLayout.java new file mode 100644 index 0000000..7962017 --- /dev/null +++ b/TIMCommon/timcommon/src/main/java/com/tencent/qcloud/tuikit/timcommon/component/MaxWidthFrameLayout.java @@ -0,0 +1,45 @@ +package com.tencent.qcloud.tuikit.timcommon.component; + +import android.content.Context; +import android.content.res.TypedArray; +import android.util.AttributeSet; +import android.widget.FrameLayout; + +import androidx.annotation.NonNull; +import androidx.annotation.Nullable; + +import com.tencent.qcloud.tuikit.timcommon.R; + +public class MaxWidthFrameLayout extends FrameLayout { + + int maxWidthPx; + + public MaxWidthFrameLayout(@NonNull Context context) { + super(context); + } + + public MaxWidthFrameLayout(@NonNull Context context, @Nullable AttributeSet attrs) { + super(context, attrs); + init(context, attrs); + } + + public MaxWidthFrameLayout(@NonNull Context context, @Nullable AttributeSet attrs, int defStyleAttr) { + super(context, attrs, defStyleAttr); + init(context, attrs); + } + + private void init(Context context, AttributeSet attributeSet) { + TypedArray array = context.obtainStyledAttributes(attributeSet, R.styleable.max_width_style); + maxWidthPx = array.getDimensionPixelSize(R.styleable.max_width_style_maxWidth, 0); + array.recycle(); + } + + @Override + protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) { + int measuredWidth = MeasureSpec.getSize(widthMeasureSpec); + if(maxWidthPx > 0 && maxWidthPx < measuredWidth) { + widthMeasureSpec = MeasureSpec.makeMeasureSpec(maxWidthPx, MeasureSpec.AT_MOST); + } + super.onMeasure(widthMeasureSpec, heightMeasureSpec); + } +} diff --git a/TIMCommon/timcommon/src/main/java/com/tencent/qcloud/tuikit/timcommon/component/MaxWidthLinearLayout.java b/TIMCommon/timcommon/src/main/java/com/tencent/qcloud/tuikit/timcommon/component/MaxWidthLinearLayout.java new file mode 100644 index 0000000..a39ec25 --- /dev/null +++ b/TIMCommon/timcommon/src/main/java/com/tencent/qcloud/tuikit/timcommon/component/MaxWidthLinearLayout.java @@ -0,0 +1,45 @@ +package com.tencent.qcloud.tuikit.timcommon.component; + +import android.content.Context; +import android.content.res.TypedArray; +import android.util.AttributeSet; +import android.widget.LinearLayout; + +import androidx.annotation.NonNull; +import androidx.annotation.Nullable; + +import com.tencent.qcloud.tuikit.timcommon.R; + +public class MaxWidthLinearLayout extends LinearLayout { + + int maxWidthPx; + + public MaxWidthLinearLayout(@NonNull Context context) { + super(context); + } + + public MaxWidthLinearLayout(@NonNull Context context, @Nullable AttributeSet attrs) { + super(context, attrs); + init(context, attrs); + } + + public MaxWidthLinearLayout(@NonNull Context context, @Nullable AttributeSet attrs, int defStyleAttr) { + super(context, attrs, defStyleAttr); + init(context, attrs); + } + + private void init(Context context, AttributeSet attributeSet) { + TypedArray array = context.obtainStyledAttributes(attributeSet, R.styleable.max_width_style); + maxWidthPx = array.getDimensionPixelSize(R.styleable.max_width_style_maxWidth, 0); + array.recycle(); + } + + @Override + protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) { + int measuredWidth = MeasureSpec.getSize(widthMeasureSpec); + if(maxWidthPx > 0 && maxWidthPx < measuredWidth) { + widthMeasureSpec = MeasureSpec.makeMeasureSpec(maxWidthPx, MeasureSpec.AT_MOST); + } + super.onMeasure(widthMeasureSpec, heightMeasureSpec); + } +} diff --git a/TIMCommon/timcommon/src/main/java/com/tencent/qcloud/tuikit/timcommon/component/MessageProperties.java b/TIMCommon/timcommon/src/main/java/com/tencent/qcloud/tuikit/timcommon/component/MessageProperties.java new file mode 100644 index 0000000..8b2035f --- /dev/null +++ b/TIMCommon/timcommon/src/main/java/com/tencent/qcloud/tuikit/timcommon/component/MessageProperties.java @@ -0,0 +1,224 @@ +package com.tencent.qcloud.tuikit.timcommon.component; + +import android.graphics.drawable.Drawable; + +import com.tencent.qcloud.tuikit.timcommon.interfaces.IMessageProperties; +import com.tencent.qcloud.tuikit.timcommon.util.ScreenUtil; + +public class MessageProperties implements IMessageProperties { + private static MessageProperties sP = new MessageProperties(); + private int mAvatarId; + private int mAvatarRadius; + private int[] avatarSize = null; + private int mNameFontSize; + private int mNameFontColor; + private int mLeftNameVisibility; + private int mRightNameVisibility; + private int mChatContextFontSize; + private int mMyChatContentFontColor; + private Drawable mMyBubble; + private int mFriendChatContentFontColor; + private Drawable mFriendBubble; + private int mTipsMessageFontSize; + private int mTipsMessageFontColor; + private Drawable mTipsMessageBubble; + private int mChatTimeFontSize; + private int mChatTimeFontColor; + private Drawable mChatTimeBubble; + + private MessageProperties() { + + } + + public static MessageProperties getInstance() { + if (sP == null) { + sP = new MessageProperties(); + } + return sP; + } + + @Override + public int getAvatarRadius() { + return mAvatarRadius; + } + + @Override + public void setAvatarRadius(int radius) { + mAvatarRadius = ScreenUtil.getPxByDp(radius); + } + + @Override + public int[] getAvatarSize() { + return avatarSize; + } + + @Override + public void setAvatarSize(int[] size) { + if (size != null && size.length == 2) { + avatarSize = new int[2]; + avatarSize[0] = ScreenUtil.getPxByDp(size[0]); + avatarSize[1] = ScreenUtil.getPxByDp(size[1]); + } + } + + @Override + public int getAvatar() { + return mAvatarId; + } + + @Override + public void setAvatar(int resId) { + this.mAvatarId = resId; + } + + @Override + public Drawable getRightBubble() { + return mMyBubble; + } + + @Override + public void setRightBubble(Drawable bubble) { + this.mMyBubble = bubble; + } + + @Override + public Drawable getLeftBubble() { + return mFriendBubble; + } + + @Override + public void setLeftBubble(Drawable bubble) { + this.mFriendBubble = bubble; + } + + @Override + public int getNameFontSize() { + return mNameFontSize; + } + + @Override + public void setNameFontSize(int size) { + this.mNameFontSize = size; + } + + @Override + public int getNameFontColor() { + return mNameFontColor; + } + + @Override + public void setNameFontColor(int color) { + this.mNameFontColor = color; + } + + @Override + public int getLeftNameVisibility() { + return mLeftNameVisibility; + } + + @Override + public void setLeftNameVisibility(int visibility) { + mLeftNameVisibility = visibility; + } + + @Override + public int getRightNameVisibility() { + return mRightNameVisibility; + } + + @Override + public void setRightNameVisibility(int visibility) { + mRightNameVisibility = visibility; + } + + @Override + public int getChatContextFontSize() { + return mChatContextFontSize; + } + + @Override + public void setChatContextFontSize(int size) { + this.mChatContextFontSize = size; + } + + @Override + public int getRightChatContentFontColor() { + return mMyChatContentFontColor; + } + + @Override + public void setRightChatContentFontColor(int color) { + this.mMyChatContentFontColor = color; + } + + @Override + public int getLeftChatContentFontColor() { + return mFriendChatContentFontColor; + } + + @Override + public void setLeftChatContentFontColor(int color) { + this.mFriendChatContentFontColor = color; + } + + @Override + public Drawable getTipsMessageBubble() { + return mTipsMessageBubble; + } + + @Override + public void setTipsMessageBubble(Drawable bubble) { + this.mTipsMessageBubble = bubble; + } + + @Override + public int getTipsMessageFontSize() { + return mTipsMessageFontSize; + } + + @Override + public void setTipsMessageFontSize(int size) { + this.mTipsMessageFontSize = size; + } + + @Override + public int getTipsMessageFontColor() { + return mTipsMessageFontColor; + } + + @Override + public void setTipsMessageFontColor(int color) { + this.mTipsMessageFontColor = color; + } + + @Override + public Drawable getChatTimeBubble() { + return mChatTimeBubble; + } + + @Override + public void setChatTimeBubble(Drawable bubble) { + this.mChatTimeBubble = bubble; + } + + @Override + public int getChatTimeFontSize() { + return mChatTimeFontSize; + } + + @Override + public void setChatTimeFontSize(int size) { + this.mChatTimeFontSize = size; + } + + @Override + public int getChatTimeFontColor() { + return mChatTimeFontColor; + } + + @Override + public void setChatTimeFontColor(int color) { + this.mChatTimeFontColor = color; + } + +} diff --git a/TIMCommon/timcommon/src/main/java/com/tencent/qcloud/tuikit/timcommon/component/MinimalistLineControllerView.java b/TIMCommon/timcommon/src/main/java/com/tencent/qcloud/tuikit/timcommon/component/MinimalistLineControllerView.java new file mode 100644 index 0000000..4ec6261 --- /dev/null +++ b/TIMCommon/timcommon/src/main/java/com/tencent/qcloud/tuikit/timcommon/component/MinimalistLineControllerView.java @@ -0,0 +1,161 @@ +package com.tencent.qcloud.tuikit.timcommon.component; + +import android.content.Context; +import android.content.res.TypedArray; +import android.graphics.drawable.Drawable; +import android.util.AttributeSet; +import android.view.LayoutInflater; +import android.view.View; +import android.view.ViewGroup; +import android.widget.CompoundButton; +import android.widget.ImageView; +import android.widget.RelativeLayout; +import android.widget.TextView; + +import androidx.appcompat.widget.SwitchCompat; + +import com.tencent.qcloud.tuikit.timcommon.R; +import com.tencent.qcloud.tuikit.timcommon.util.ScreenUtil; + +/** + * Custom LineControllerView + */ +public class MinimalistLineControllerView extends RelativeLayout { + + private String mName; + private boolean mIsBottom; + private boolean mIsTop; + private String mContent; + private boolean mIsJump; + private boolean mIsSwitch; + + protected TextView mNameText; + protected TextView mContentText; + private ImageView mNavArrowView; + protected SwitchCompat mSwitchView; + protected View bottomLine; + private View mMask; + private View container; + + public MinimalistLineControllerView(Context context, AttributeSet attrs) { + super(context, attrs); + LayoutInflater.from(context).inflate(R.layout.minimalist_line_controller_view, this); + TypedArray ta = context.obtainStyledAttributes(attrs, R.styleable.LineControllerView, 0, 0); + try { + mName = ta.getString(R.styleable.LineControllerView_name); + mContent = ta.getString(R.styleable.LineControllerView_subject); + mIsBottom = ta.getBoolean(R.styleable.LineControllerView_isBottom, false); + mIsTop = ta.getBoolean(R.styleable.LineControllerView_isTop, false); + mIsJump = ta.getBoolean(R.styleable.LineControllerView_canNav, false); + mIsSwitch = ta.getBoolean(R.styleable.LineControllerView_isSwitch, false); + setUpView(); + } finally { + ta.recycle(); + } + } + + private void setUpView() { + mNameText = findViewById(R.id.name); + mNameText.setText(mName); + mContentText = findViewById(R.id.content); + mContentText.setText(mContent); + mContentText.setOnClickListener(new OnClickListener() { + @Override + public void onClick(View v) { + MinimalistLineControllerView.this.performClick(); + } + }); + bottomLine = findViewById(R.id.bottom_line); + View topLine = findViewById(R.id.top_line); + bottomLine.setVisibility(mIsBottom ? VISIBLE : GONE); + topLine.setVisibility(mIsTop ? VISIBLE : GONE); + mNavArrowView = findViewById(R.id.rightArrow); + mNavArrowView.setVisibility(mIsJump ? VISIBLE : GONE); + RelativeLayout contentLayout = findViewById(R.id.contentText); + contentLayout.setVisibility(mIsSwitch ? GONE : VISIBLE); + mSwitchView = findViewById(R.id.btnSwitch); + mSwitchView.setVisibility(mIsSwitch ? VISIBLE : GONE); + mMask = findViewById(R.id.disable_mask); + container = findViewById(R.id.view_container); + } + + public void setBackground(Drawable drawable) { + super.setBackground(drawable); + if (container != null) { + container.setBackground(drawable); + } + } + + public void setBackgroundColor(int color) { + super.setBackgroundColor(color); + if (container != null) { + container.setBackgroundColor(color); + } + } + + public String getContent() { + return mContentText.getText().toString(); + } + + public void setContent(String content) { + this.mContent = content; + mContentText.setText(content); + } + + public void setSingleLine(boolean singleLine) { + mContentText.setSingleLine(singleLine); + } + + /** + * Set whether to jump + * + * @param canNav + */ + public void setCanNav(boolean canNav) { + this.mIsJump = canNav; + mNavArrowView.setVisibility(canNav ? VISIBLE : GONE); + if (canNav) { + ViewGroup.LayoutParams params = mContentText.getLayoutParams(); + params.width = ScreenUtil.getPxByDp(120); + params.height = ViewGroup.LayoutParams.WRAP_CONTENT; + mContentText.setLayoutParams(params); + } else { + ViewGroup.LayoutParams params = mContentText.getLayoutParams(); + params.width = ViewGroup.LayoutParams.WRAP_CONTENT; + params.height = ViewGroup.LayoutParams.WRAP_CONTENT; + mContentText.setLayoutParams(params); + } + } + + public boolean isChecked() { + return mSwitchView.isChecked(); + } + + public void setChecked(boolean on) { + mSwitchView.setChecked(on); + } + + public void setCheckListener(CompoundButton.OnCheckedChangeListener listener) { + mSwitchView.setOnCheckedChangeListener(listener); + } + + public void setMask(boolean enableMask) { + if (enableMask) { + mMask.setVisibility(View.VISIBLE); + mSwitchView.setEnabled(false); + } else { + mMask.setVisibility(View.GONE); + mSwitchView.setEnabled(true); + } + } + + public void setNameColor(int color) { + mNameText.setTextColor(color); + } + + public void setName(String name) { + this.mName = name; + mNameText.setText(name); + } + +} diff --git a/TIMCommon/timcommon/src/main/java/com/tencent/qcloud/tuikit/timcommon/component/MinimalistTitleBar.java b/TIMCommon/timcommon/src/main/java/com/tencent/qcloud/tuikit/timcommon/component/MinimalistTitleBar.java new file mode 100644 index 0000000..8374a00 --- /dev/null +++ b/TIMCommon/timcommon/src/main/java/com/tencent/qcloud/tuikit/timcommon/component/MinimalistTitleBar.java @@ -0,0 +1,35 @@ +package com.tencent.qcloud.tuikit.timcommon.component; + +import android.content.Context; +import android.graphics.Color; +import android.util.AttributeSet; + +import androidx.annotation.Nullable; + +import com.tencent.qcloud.tuikit.timcommon.R; + +public class MinimalistTitleBar extends TitleBarLayout { + + public MinimalistTitleBar(Context context) { + super(context); + initView(context); + } + + public MinimalistTitleBar(Context context, @Nullable AttributeSet attrs) { + super(context, attrs); + initView(context); + } + + public MinimalistTitleBar(Context context, @Nullable AttributeSet attrs, int defStyleAttr) { + super(context, attrs, defStyleAttr); + initView(context); + } + + private void initView(Context context) { + setLeftReturnListener(context); + setBackgroundColor(Color.WHITE); + getLeftIcon().setBackgroundResource(R.drawable.core_minimalist_back_icon); + getLeftTitle().setTextColor(0xFF0365F9); + getRightTitle().setTextColor(0xFF0365F9); + } +} diff --git a/TIMCommon/timcommon/src/main/java/com/tencent/qcloud/tuikit/timcommon/component/PopupInputCard.java b/TIMCommon/timcommon/src/main/java/com/tencent/qcloud/tuikit/timcommon/component/PopupInputCard.java new file mode 100644 index 0000000..23f6f7c --- /dev/null +++ b/TIMCommon/timcommon/src/main/java/com/tencent/qcloud/tuikit/timcommon/component/PopupInputCard.java @@ -0,0 +1,285 @@ +package com.tencent.qcloud.tuikit.timcommon.component; + +import android.animation.ValueAnimator; +import android.app.Activity; +import android.graphics.drawable.ColorDrawable; +import android.text.Editable; +import android.text.InputFilter; +import android.text.Spanned; +import android.text.TextUtils; +import android.text.TextWatcher; +import android.view.LayoutInflater; +import android.view.View; +import android.view.Window; +import android.view.WindowManager; +import android.view.animation.LinearInterpolator; +import android.widget.Button; +import android.widget.EditText; +import android.widget.PopupWindow; +import android.widget.TextView; + +import com.tencent.qcloud.tuikit.timcommon.R; +import com.tencent.qcloud.tuicore.util.ToastUtil; +import com.tencent.qcloud.tuikit.timcommon.util.SoftKeyBoardUtil; + +import java.util.regex.Pattern; + +public class PopupInputCard { + private PopupWindow popupWindow; + + private TextView titleTv; + private EditText editText; + private TextView descriptionTv; + private Button positiveBtn; + private View closeBtn; + private OnClickListener positiveOnClickListener; + private OnTextExceedListener textExceedListener; + + private int minLimit = 0; + private int maxLimit = Integer.MAX_VALUE; + private String rule; + private String notMachRuleTip; + private ByteLengthFilter lengthFilter = new ByteLengthFilter(); + public PopupInputCard(Activity activity) { + View popupView = LayoutInflater.from(activity).inflate(R.layout.layout_popup_card, null); + titleTv = popupView.findViewById(R.id.popup_card_title); + editText = popupView.findViewById(R.id.popup_card_edit); + descriptionTv = popupView.findViewById(R.id.popup_card_description); + positiveBtn = popupView.findViewById(R.id.popup_card_positive_btn); + closeBtn = popupView.findViewById(R.id.close_btn); + + popupWindow = new PopupWindow(popupView, WindowManager.LayoutParams.MATCH_PARENT, WindowManager.LayoutParams.WRAP_CONTENT, true) { + @Override + public void showAtLocation(View anchor, int gravity, int x, int y) { + if (activity != null && !activity.isFinishing()) { + Window dialogWindow = activity.getWindow(); + startAnimation(dialogWindow, true); + } + editText.requestFocus(); + if (activity.getWindow() != null) { + SoftKeyBoardUtil.showKeyBoard(activity.getWindow()); + } + super.showAtLocation(anchor, gravity, x, y); + } + + @Override + public void dismiss() { + if (activity != null && !activity.isFinishing()) { + Window dialogWindow = activity.getWindow(); + startAnimation(dialogWindow, false); + } + + super.dismiss(); + } + }; + popupWindow.setBackgroundDrawable(new ColorDrawable()); + popupWindow.setTouchable(true); + popupWindow.setOutsideTouchable(false); + popupWindow.setAnimationStyle(R.style.PopupInputCardAnim); + popupWindow.setInputMethodMode(PopupWindow.INPUT_METHOD_NEEDED); + popupWindow.setSoftInputMode(WindowManager.LayoutParams.SOFT_INPUT_ADJUST_RESIZE); + popupWindow.setOnDismissListener(new PopupWindow.OnDismissListener() { + @Override + public void onDismiss() { + if (activity.getWindow() != null) { + SoftKeyBoardUtil.hideKeyBoard(activity.getWindow()); + } + } + }); + + positiveBtn.setOnClickListener(new View.OnClickListener() { + @Override + public void onClick(View v) { + String result = editText.getText().toString(); + + if (result.length() < minLimit || result.length() > maxLimit) { + ToastUtil.toastShortMessage(notMachRuleTip); + return; + } + + if (!TextUtils.isEmpty(rule) && !Pattern.matches(rule, result)) { + ToastUtil.toastShortMessage(notMachRuleTip); + return; + } + + if (positiveOnClickListener != null) { + positiveOnClickListener.onClick(editText.getText().toString()); + } + popupWindow.dismiss(); + } + }); + + closeBtn.setOnClickListener(new View.OnClickListener() { + @Override + public void onClick(View v) { + popupWindow.dismiss(); + } + }); + editText.setFilters(new InputFilter[] {lengthFilter}); + editText.addTextChangedListener(new TextWatcher() { + @Override + public void beforeTextChanged(CharSequence s, int start, int count, int after) { + + } + + @Override + public void onTextChanged(CharSequence s, int start, int before, int count) { + + } + + @Override + public void afterTextChanged(Editable s) { + if (!TextUtils.isEmpty(rule)) { + if (!Pattern.matches(rule, s.toString())) { + positiveBtn.setEnabled(false); + } else { + positiveBtn.setEnabled(true); + } + } + } + }); + + } + private void startAnimation(Window window, boolean isShow) { + LinearInterpolator interpolator = new LinearInterpolator(); + ValueAnimator animator; + if (isShow) { + animator = ValueAnimator.ofFloat(1.0f, 0.5f); + } else { + animator = ValueAnimator.ofFloat(0.5f, 1.0f); + } + animator.addUpdateListener(new ValueAnimator.AnimatorUpdateListener() { + @Override + public void onAnimationUpdate(ValueAnimator animation) { + WindowManager.LayoutParams lp = window.getAttributes(); + lp.alpha = (float) animation.getAnimatedValue(); + window.setAttributes(lp); + } + }); + + animator.setDuration(200); + animator.setInterpolator(interpolator); + animator.start(); + } + + public void show(View rootView, int gravity) { + if (popupWindow != null) { + popupWindow.showAtLocation(rootView, gravity, 0, 0); + } + } + + public void setTitle(String title) { + titleTv.setText(title); + } + + public void setDescription(String description) { + if (!TextUtils.isEmpty(description)) { + descriptionTv.setVisibility(View.VISIBLE); + descriptionTv.setText(description); + } + } + + public void setContent(String content) { + editText.setText(content); + } + + public void setOnPositive(OnClickListener clickListener) { + positiveOnClickListener = clickListener; + } + + public void setTextExceedListener(OnTextExceedListener textExceedListener) { + this.textExceedListener = textExceedListener; + } + + public void setSingleLine(boolean isSingleLine) { + editText.setSingleLine(isSingleLine); + } + + public void setMaxLimit(int maxLimit) { + this.maxLimit = maxLimit; + lengthFilter.setLength(maxLimit); + } + + public void setMinLimit(int minLimit) { + this.minLimit = minLimit; + } + + public void setRule(String rule) { + if (TextUtils.isEmpty(rule)) { + this.rule = ""; + } else { + this.rule = rule; + } + } + + public void setNotMachRuleTip(String notMachRuleTip) { + this.notMachRuleTip = notMachRuleTip; + } + + class ByteLengthFilter implements InputFilter { + private int length = Integer.MAX_VALUE; + public ByteLengthFilter() { + } + + public void setLength(int length) { + this.length = length; + } + + @Override + public CharSequence filter(CharSequence source, int start, int end, Spanned dest, int dstart, int dend) { + int destLength = 0; + int destReplaceLength = 0; + int sourceLength = 0; + if (!TextUtils.isEmpty(dest)) { + destLength = dest.toString().getBytes().length; + destReplaceLength= dest.subSequence(dstart, dend).toString().getBytes().length; + } + if (!TextUtils.isEmpty(source)) { + sourceLength = source.subSequence(start, end).toString().getBytes().length; + } + int keepBytesLength = length - (destLength - destReplaceLength); + if (keepBytesLength <= 0) { + if (textExceedListener != null) { + textExceedListener.onTextExceedMax(); + } + return ""; + } else if (keepBytesLength >= sourceLength) { + return null; + } else { + if (textExceedListener != null) { + textExceedListener.onTextExceedMax(); + } + return getSource(source, start, keepBytesLength); + } + } + + private CharSequence getSource(CharSequence sequence, int start, int keepLength) { + int sequenceLength = sequence.length(); + int end = 0; + for (int i = 1; i <= sequenceLength; i++) { + if (sequence.subSequence(0, i).toString().getBytes().length <= keepLength) { + end = i; + } else { + break; + } + } + if (end > 0 && Character.isHighSurrogate(sequence.charAt(end - 1))) { + --end; + if (end == start) { + return ""; + } + } + return sequence.subSequence(start, end); + } + + } + + @FunctionalInterface + public interface OnClickListener { + void onClick(String result); + } + + public interface OnTextExceedListener { + void onTextExceedMax(); + } +} diff --git a/TIMCommon/timcommon/src/main/java/com/tencent/qcloud/tuikit/timcommon/component/RoundCornerImageView.java b/TIMCommon/timcommon/src/main/java/com/tencent/qcloud/tuikit/timcommon/component/RoundCornerImageView.java new file mode 100644 index 0000000..f3d140d --- /dev/null +++ b/TIMCommon/timcommon/src/main/java/com/tencent/qcloud/tuikit/timcommon/component/RoundCornerImageView.java @@ -0,0 +1,126 @@ +package com.tencent.qcloud.tuikit.timcommon.component; + +import android.content.Context; +import android.content.res.TypedArray; +import android.graphics.Canvas; +import android.graphics.Paint; +import android.graphics.PaintFlagsDrawFilter; +import android.graphics.Path; +import android.graphics.RectF; +import android.util.AttributeSet; +import android.view.View; + +import androidx.annotation.NonNull; +import androidx.annotation.Nullable; +import androidx.appcompat.widget.AppCompatImageView; + +import com.tencent.qcloud.tuikit.timcommon.R; + +public class RoundCornerImageView extends AppCompatImageView { + private final Path path = new Path(); + private final RectF rectF = new RectF(); + private final PaintFlagsDrawFilter aliasFilter = new PaintFlagsDrawFilter(0, Paint.ANTI_ALIAS_FLAG | Paint.FILTER_BITMAP_FLAG); + private int radius; + private int leftTopRadius; + private int rightTopRadius; + private int rightBottomRadius; + private int leftBottomRadius; + + public RoundCornerImageView(@NonNull Context context) { + super(context); + init(context, null); + } + + public RoundCornerImageView(@NonNull Context context, @Nullable AttributeSet attrs) { + super(context, attrs); + init(context, attrs); + } + + public RoundCornerImageView(@NonNull Context context, @Nullable AttributeSet attrs, int defStyleAttr) { + super(context, attrs, defStyleAttr); + } + + private void init(Context context, AttributeSet attrs) { + setLayerType(View.LAYER_TYPE_HARDWARE, null); + int defaultRadius = 0; + if (attrs != null) { + TypedArray array = context.obtainStyledAttributes(attrs, R.styleable.RoundCornerImageView); + radius = array.getDimensionPixelOffset(R.styleable.RoundCornerImageView_corner_radius, defaultRadius); + leftTopRadius = array.getDimensionPixelOffset(R.styleable.RoundCornerImageView_left_top_corner_radius, defaultRadius); + rightTopRadius = array.getDimensionPixelOffset(R.styleable.RoundCornerImageView_right_top_corner_radius, defaultRadius); + rightBottomRadius = array.getDimensionPixelOffset(R.styleable.RoundCornerImageView_right_bottom_corner_radius, defaultRadius); + leftBottomRadius = array.getDimensionPixelOffset(R.styleable.RoundCornerImageView_left_bottom_corner_radius, defaultRadius); + array.recycle(); + } + + if (defaultRadius == leftTopRadius) { + leftTopRadius = radius; + } + if (defaultRadius == rightTopRadius) { + rightTopRadius = radius; + } + if (defaultRadius == rightBottomRadius) { + rightBottomRadius = radius; + } + if (defaultRadius == leftBottomRadius) { + leftBottomRadius = radius; + } + } + + public void setLeftBottomRadius(int leftBottomRadius) { + this.leftBottomRadius = leftBottomRadius; + } + + public void setLeftTopRadius(int leftTopRadius) { + this.leftTopRadius = leftTopRadius; + } + + public void setRadius(int radius) { + this.radius = radius; + leftBottomRadius = radius; + rightBottomRadius = radius; + rightTopRadius = radius; + leftTopRadius = radius; + } + + public void setRightBottomRadius(int rightBottomRadius) { + this.rightBottomRadius = rightBottomRadius; + } + + public void setRightTopRadius(int rightTopRadius) { + this.rightTopRadius = rightTopRadius; + } + + public int getLeftBottomRadius() { + return leftBottomRadius; + } + + public int getLeftTopRadius() { + return leftTopRadius; + } + + public int getRadius() { + return radius; + } + + public int getRightBottomRadius() { + return rightBottomRadius; + } + + public int getRightTopRadius() { + return rightTopRadius; + } + + @Override + protected void onDraw(Canvas canvas) { + path.reset(); + canvas.setDrawFilter(aliasFilter); + rectF.set(0, 0, getMeasuredWidth(), getMeasuredHeight()); + // left-top -> right-top -> right-bottom -> left-bottom + float[] radius = {leftTopRadius, leftTopRadius, rightTopRadius, rightTopRadius, + rightBottomRadius, rightBottomRadius, leftBottomRadius, leftBottomRadius}; + path.addRoundRect(rectF, radius, Path.Direction.CW); + canvas.clipPath(path); + super.onDraw(canvas); + } +} diff --git a/TIMCommon/timcommon/src/main/java/com/tencent/qcloud/tuikit/timcommon/component/RoundFrameLayout.java b/TIMCommon/timcommon/src/main/java/com/tencent/qcloud/tuikit/timcommon/component/RoundFrameLayout.java new file mode 100644 index 0000000..310d1e5 --- /dev/null +++ b/TIMCommon/timcommon/src/main/java/com/tencent/qcloud/tuikit/timcommon/component/RoundFrameLayout.java @@ -0,0 +1,83 @@ +package com.tencent.qcloud.tuikit.timcommon.component; + +import android.content.Context; +import android.content.res.TypedArray; +import android.graphics.Canvas; +import android.graphics.Paint; +import android.graphics.PaintFlagsDrawFilter; +import android.graphics.Path; +import android.graphics.RectF; +import android.util.AttributeSet; +import android.view.View; +import android.widget.FrameLayout; + +import androidx.annotation.NonNull; +import androidx.annotation.Nullable; + +import com.tencent.qcloud.tuikit.timcommon.R; + +public class RoundFrameLayout extends FrameLayout { + + private final Path path = new Path(); + private final RectF rectF = new RectF(); + private final PaintFlagsDrawFilter aliasFilter = new PaintFlagsDrawFilter(0, Paint.ANTI_ALIAS_FLAG | Paint.FILTER_BITMAP_FLAG); + private int radius; + private int leftTopRadius; + private int rightTopRadius; + private int rightBottomRadius; + private int leftBottomRadius; + + public RoundFrameLayout(@NonNull Context context) { + super(context); + init(context, null); + } + + public RoundFrameLayout(@NonNull Context context, @Nullable AttributeSet attrs) { + super(context, attrs); + init(context, attrs); + } + + public RoundFrameLayout(@NonNull Context context, @Nullable AttributeSet attrs, int defStyleAttr) { + super(context, attrs, defStyleAttr); + } + + private void init(Context context, AttributeSet attrs) { + setLayerType(View.LAYER_TYPE_HARDWARE, null); + int defaultRadius = 0; + if (attrs != null) { + TypedArray array = context.obtainStyledAttributes(attrs, R.styleable.RoundFrameLayout); + radius = array.getDimensionPixelOffset(R.styleable.RoundFrameLayout_corner_radius, defaultRadius); + leftTopRadius = array.getDimensionPixelOffset(R.styleable.RoundFrameLayout_left_top_corner_radius, defaultRadius); + rightTopRadius = array.getDimensionPixelOffset(R.styleable.RoundFrameLayout_right_top_corner_radius, defaultRadius); + rightBottomRadius = array.getDimensionPixelOffset(R.styleable.RoundFrameLayout_right_bottom_corner_radius, defaultRadius); + leftBottomRadius = array.getDimensionPixelOffset(R.styleable.RoundFrameLayout_left_bottom_corner_radius, defaultRadius); + array.recycle(); + } + + if (defaultRadius == leftTopRadius) { + leftTopRadius = radius; + } + if (defaultRadius == rightTopRadius) { + rightTopRadius = radius; + } + if (defaultRadius == rightBottomRadius) { + rightBottomRadius = radius; + } + if (defaultRadius == leftBottomRadius) { + leftBottomRadius = radius; + } + } + + @Override + protected void dispatchDraw(Canvas canvas) { + path.reset(); + canvas.setDrawFilter(aliasFilter); + rectF.set(0, 0, getMeasuredWidth(), getMeasuredHeight()); + // left-top -> right-top -> right-bottom -> left-bottom + float[] radius = {leftTopRadius, leftTopRadius, rightTopRadius, rightTopRadius, + rightBottomRadius, rightBottomRadius, leftBottomRadius, leftBottomRadius}; + path.addRoundRect(rectF, radius, Path.Direction.CW); + canvas.clipPath(path); + super.dispatchDraw(canvas); + } +} diff --git a/TIMCommon/timcommon/src/main/java/com/tencent/qcloud/tuikit/timcommon/component/SwitchCustomWidth.java b/TIMCommon/timcommon/src/main/java/com/tencent/qcloud/tuikit/timcommon/component/SwitchCustomWidth.java new file mode 100644 index 0000000..653d34b --- /dev/null +++ b/TIMCommon/timcommon/src/main/java/com/tencent/qcloud/tuikit/timcommon/component/SwitchCustomWidth.java @@ -0,0 +1,59 @@ +package com.tencent.qcloud.tuikit.timcommon.component; + +import android.content.Context; +import android.content.res.TypedArray; +import android.util.AttributeSet; +import android.util.Log; + +import androidx.annotation.NonNull; +import androidx.annotation.Nullable; +import androidx.appcompat.widget.SwitchCompat; + +import com.tencent.qcloud.tuikit.timcommon.R; + +import java.lang.reflect.Field; + +public class SwitchCustomWidth extends SwitchCompat { + private static final String TAG = "SwitchCustomWidth"; + + private int customSwitchWidth; + + public SwitchCustomWidth(@NonNull Context context) { + super(context); + initCustomAttr(context, null); + } + + public SwitchCustomWidth(@NonNull Context context, @Nullable AttributeSet attrs) { + super(context, attrs); + initCustomAttr(context, attrs); + } + + public SwitchCustomWidth(@NonNull Context context, @Nullable AttributeSet attrs, int defStyleAttr) { + super(context, attrs, defStyleAttr); + initCustomAttr(context, attrs); + } + + public void initCustomAttr(Context context, AttributeSet attributeSet) { + if (attributeSet != null) { + TypedArray array = context.obtainStyledAttributes(attributeSet, R.styleable.SwitchCustomWidth); + customSwitchWidth = array.getDimensionPixelSize(R.styleable.SwitchCustomWidth_custom_width, 0); + array.recycle(); + } + } + + @Override + public void onMeasure(int widthMeasureSpec, int heightMeasureSpec) { + super.onMeasure(widthMeasureSpec, heightMeasureSpec); + try { + if(customSwitchWidth == 0){ + return; + } + Class clazz = SwitchCompat.class; + Field mSwitchWidthFiled = clazz.getDeclaredField("mSwitchWidth"); + mSwitchWidthFiled.setAccessible(true); + mSwitchWidthFiled.set(this, customSwitchWidth); + } catch (Exception e) { + Log.w(TAG, e.getMessage()); + } + } +} diff --git a/TIMCommon/timcommon/src/main/java/com/tencent/qcloud/tuikit/timcommon/component/TitleBarLayout.java b/TIMCommon/timcommon/src/main/java/com/tencent/qcloud/tuikit/timcommon/component/TitleBarLayout.java new file mode 100644 index 0000000..bf00bdf --- /dev/null +++ b/TIMCommon/timcommon/src/main/java/com/tencent/qcloud/tuikit/timcommon/component/TitleBarLayout.java @@ -0,0 +1,180 @@ +package com.tencent.qcloud.tuikit.timcommon.component; + +import android.app.Activity; +import android.content.Context; +import android.content.res.TypedArray; +import android.text.TextUtils; +import android.util.AttributeSet; +import android.view.View; +import android.view.ViewGroup; +import android.view.inputmethod.InputMethodManager; +import android.widget.ImageView; +import android.widget.LinearLayout; +import android.widget.RelativeLayout; +import android.widget.TextView; + +import androidx.annotation.Nullable; + +import com.tencent.qcloud.tuicore.TUIThemeManager; +import com.tencent.qcloud.tuikit.timcommon.R; +import com.tencent.qcloud.tuikit.timcommon.component.interfaces.ITitleBarLayout; +import com.tencent.qcloud.tuikit.timcommon.util.ScreenUtil; + +public class TitleBarLayout extends LinearLayout implements ITitleBarLayout { + + private LinearLayout mLeftGroup; + private LinearLayout mRightGroup; + private TextView mLeftTitle; + private TextView mCenterTitle; + private TextView mRightTitle; + private ImageView mLeftIcon; + private ImageView mRightIcon; + private RelativeLayout mTitleLayout; + private UnreadCountTextView unreadCountTextView; + + public TitleBarLayout(Context context) { + super(context); + init(context, null); + } + + public TitleBarLayout(Context context, @Nullable AttributeSet attrs) { + super(context, attrs); + init(context, attrs); + } + + public TitleBarLayout(Context context, @Nullable AttributeSet attrs, int defStyleAttr) { + super(context, attrs, defStyleAttr); + init(context, attrs); + } + + private void init(Context context, @Nullable AttributeSet attrs) { + String middleTitle = null; + boolean canReturn = false; + if (attrs != null) { + TypedArray array = context.obtainStyledAttributes(attrs, R.styleable.TitleBarLayout); + middleTitle = array.getString(R.styleable.TitleBarLayout_title_bar_middle_title); + canReturn = array.getBoolean(R.styleable.TitleBarLayout_title_bar_can_return, false); + array.recycle(); + } + inflate(context, R.layout.title_bar_layout, this); + mTitleLayout = findViewById(R.id.page_title_layout); + mLeftGroup = findViewById(R.id.page_title_left_group); + mRightGroup = findViewById(R.id.page_title_right_group); + mLeftTitle = findViewById(R.id.page_title_left_text); + mRightTitle = findViewById(R.id.page_title_right_text); + mCenterTitle = findViewById(R.id.page_title); + mLeftIcon = findViewById(R.id.page_title_left_icon); + mRightIcon = findViewById(R.id.page_title_right_icon); + unreadCountTextView = findViewById(R.id.new_message_total_unread); + + LayoutParams params = (LayoutParams) mTitleLayout.getLayoutParams(); + params.height = ScreenUtil.getPxByDp(50); + mTitleLayout.setLayoutParams(params); + setBackgroundResource(TUIThemeManager.getAttrResId(getContext(), R.attr.core_title_bar_bg)); + + int iconSize = ScreenUtil.dip2px(20); + ViewGroup.LayoutParams iconParams = mLeftIcon.getLayoutParams(); + iconParams.width = iconSize; + iconParams.height = iconSize; + mLeftIcon.setLayoutParams(iconParams); + iconParams = mRightIcon.getLayoutParams(); + iconParams.width = iconSize; + iconParams.height = iconSize; + + mRightIcon.setLayoutParams(iconParams); + + if (canReturn) { + setLeftReturnListener(context); + } + if (!TextUtils.isEmpty(middleTitle)) { + mCenterTitle.setText(middleTitle); + } + } + + public void setLeftReturnListener(Context context) { + mLeftGroup.setOnClickListener(new OnClickListener() { + @Override + public void onClick(View v) { + if (context instanceof Activity) { + InputMethodManager imm = (InputMethodManager) getContext().getSystemService(Context.INPUT_METHOD_SERVICE); + imm.hideSoftInputFromWindow(TitleBarLayout.this.getWindowToken(), 0); + ((Activity) context).finish(); + } + } + }); + } + + @Override + public void setOnLeftClickListener(OnClickListener listener) { + mLeftGroup.setOnClickListener(listener); + } + + @Override + public void setOnRightClickListener(OnClickListener listener) { + mRightGroup.setOnClickListener(listener); + } + + @Override + public void setTitle(String title, Position position) { + switch (position) { + case LEFT: + mLeftTitle.setText(title); + break; + case RIGHT: + mRightTitle.setText(title); + break; + case MIDDLE: + mCenterTitle.setText(title); + break; + } + } + + @Override + public LinearLayout getLeftGroup() { + return mLeftGroup; + } + + @Override + public LinearLayout getRightGroup() { + return mRightGroup; + } + + @Override + public ImageView getLeftIcon() { + return mLeftIcon; + } + + @Override + public void setLeftIcon(int resId) { + mLeftIcon.setBackgroundResource(resId); + } + + @Override + public ImageView getRightIcon() { + return mRightIcon; + } + + @Override + public void setRightIcon(int resId) { + mRightIcon.setBackgroundResource(resId); + } + + @Override + public TextView getLeftTitle() { + return mLeftTitle; + } + + @Override + public TextView getMiddleTitle() { + return mCenterTitle; + } + + @Override + public TextView getRightTitle() { + return mRightTitle; + } + + public UnreadCountTextView getUnreadCountTextView() { + return unreadCountTextView; + } +} diff --git a/TIMCommon/timcommon/src/main/java/com/tencent/qcloud/tuikit/timcommon/component/UnreadCountTextView.java b/TIMCommon/timcommon/src/main/java/com/tencent/qcloud/tuikit/timcommon/component/UnreadCountTextView.java new file mode 100644 index 0000000..4c49821 --- /dev/null +++ b/TIMCommon/timcommon/src/main/java/com/tencent/qcloud/tuikit/timcommon/component/UnreadCountTextView.java @@ -0,0 +1,85 @@ +package com.tencent.qcloud.tuikit.timcommon.component; + +import android.content.Context; +import android.content.res.TypedArray; +import android.graphics.Canvas; +import android.graphics.Paint; +import android.graphics.RectF; +import android.util.AttributeSet; +import android.util.DisplayMetrics; +import android.util.TypedValue; + +import androidx.appcompat.widget.AppCompatTextView; + +import com.tencent.qcloud.tuikit.timcommon.R; + + +public class UnreadCountTextView extends AppCompatTextView { + + private int mNormalSize; + private Paint mPaint; + + public UnreadCountTextView(Context context) { + super(context); + init(context, null); + } + + public UnreadCountTextView(Context context, AttributeSet attrs) { + super(context, attrs); + init(context, attrs); + } + + public UnreadCountTextView(Context context, AttributeSet attrs, int defStyleAttr) { + super(context, attrs, defStyleAttr); + init(context, attrs); + } + + private void init(Context context, AttributeSet attrs) { + mNormalSize = dp2px(18.4f); + TypedArray typedArray = context.obtainStyledAttributes(attrs, R.styleable.UnreadCountTextView); + int paintColor = typedArray.getColor(R.styleable.UnreadCountTextView_paint_color, getResources().getColor(R.color.read_dot_bg)); + typedArray.recycle(); + + mPaint = new Paint(); + mPaint.setColor(paintColor); + mPaint.setAntiAlias(true); + } + + public void setPaintColor(int color) { + if (mPaint != null) { + mPaint.setColor(color); + } + } + + @Override + protected void onDraw(Canvas canvas) { + if (getText().length() == 0) { + int l = (getMeasuredWidth() - dp2px(6)) / 2; + int t = l; + int r = getMeasuredWidth() - l; + int b = r; + canvas.drawOval(new RectF(l, t, r, b), mPaint); + } else if (getText().length() == 1) { + canvas.drawOval(new RectF(0, 0, mNormalSize, mNormalSize), mPaint); + } else if (getText().length() > 1) { + canvas.drawRoundRect(new RectF(0, 0, getMeasuredWidth(), getMeasuredHeight()), getMeasuredHeight() / 2, getMeasuredHeight() / 2, mPaint); + } + super.onDraw(canvas); + + } + + @Override + protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) { + int width = mNormalSize; + int height = mNormalSize; + if (getText().length() > 1) { + width = mNormalSize + dp2px((getText().length() - 1) * 10); + } + setMeasuredDimension(width, height); + } + + private int dp2px(float dp) { + DisplayMetrics displayMetrics = getResources().getDisplayMetrics(); + return (int) TypedValue.applyDimension(TypedValue.COMPLEX_UNIT_DIP, dp, displayMetrics); + } +} diff --git a/TIMCommon/timcommon/src/main/java/com/tencent/qcloud/tuikit/timcommon/component/action/PopActionClickListener.java b/TIMCommon/timcommon/src/main/java/com/tencent/qcloud/tuikit/timcommon/component/action/PopActionClickListener.java new file mode 100644 index 0000000..dc851ff --- /dev/null +++ b/TIMCommon/timcommon/src/main/java/com/tencent/qcloud/tuikit/timcommon/component/action/PopActionClickListener.java @@ -0,0 +1,6 @@ +package com.tencent.qcloud.tuikit.timcommon.component.action; + + +public interface PopActionClickListener { + void onActionClick(int index, Object data); +} diff --git a/TIMCommon/timcommon/src/main/java/com/tencent/qcloud/tuikit/timcommon/component/action/PopDialogAdapter.java b/TIMCommon/timcommon/src/main/java/com/tencent/qcloud/tuikit/timcommon/component/action/PopDialogAdapter.java new file mode 100644 index 0000000..38c0c4a --- /dev/null +++ b/TIMCommon/timcommon/src/main/java/com/tencent/qcloud/tuikit/timcommon/component/action/PopDialogAdapter.java @@ -0,0 +1,65 @@ +package com.tencent.qcloud.tuikit.timcommon.component.action; + +import android.view.LayoutInflater; +import android.view.View; +import android.view.ViewGroup; +import android.widget.BaseAdapter; +import android.widget.TextView; + +import com.tencent.qcloud.tuikit.timcommon.R; +import com.tencent.qcloud.tuicore.TUIConfig; +import com.tencent.qcloud.tuikit.timcommon.util.ThreadUtils; + +import java.util.ArrayList; +import java.util.List; + +public class PopDialogAdapter extends BaseAdapter { + + private List dataSource = new ArrayList<>(); + + public void setDataSource(final List datas) { + dataSource = datas; + ThreadUtils.postOnUiThread(new Runnable() { + @Override + public void run() { + notifyDataSetChanged(); + } + }); + } + + @Override + public int getCount() { + return dataSource.size(); + } + + @Override + public Object getItem(int position) { + return dataSource.get(position); + } + + @Override + public long getItemId(int position) { + return position; + } + + @Override + public View getView(final int position, View convertView, ViewGroup parent) { + + ViewHolder holder; + if (convertView == null) { + convertView = LayoutInflater.from(TUIConfig.getAppContext()).inflate(R.layout.pop_dialog_adapter, parent, false); + holder = new ViewHolder(); + holder.text = convertView.findViewById(R.id.pop_dialog_text); + convertView.setTag(holder); + } else { + holder = (ViewHolder) convertView.getTag(); + } + PopMenuAction action = (PopMenuAction) getItem(position); + holder.text.setText(action.getActionName()); + return convertView; + } + + static class ViewHolder { + TextView text; + } +} diff --git a/TIMCommon/timcommon/src/main/java/com/tencent/qcloud/tuikit/timcommon/component/action/PopMenuAction.java b/TIMCommon/timcommon/src/main/java/com/tencent/qcloud/tuikit/timcommon/component/action/PopMenuAction.java new file mode 100644 index 0000000..ca16397 --- /dev/null +++ b/TIMCommon/timcommon/src/main/java/com/tencent/qcloud/tuikit/timcommon/component/action/PopMenuAction.java @@ -0,0 +1,44 @@ +package com.tencent.qcloud.tuikit.timcommon.component.action; + +import android.graphics.Bitmap; + +public class PopMenuAction { + + private String actionName; + private Bitmap icon; + private int iconResId; + private PopActionClickListener actionClickListener; + + public String getActionName() { + return actionName; + } + + public void setActionName(String actionName) { + this.actionName = actionName; + } + + public Bitmap getIcon() { + return icon; + } + + public void setIcon(Bitmap mIcon) { + this.icon = mIcon; + } + + + public int getIconResId() { + return iconResId; + } + + public void setIconResId(int iconResId) { + this.iconResId = iconResId; + } + + public PopActionClickListener getActionClickListener() { + return actionClickListener; + } + + public void setActionClickListener(PopActionClickListener actionClickListener) { + this.actionClickListener = actionClickListener; + } +} diff --git a/TIMCommon/timcommon/src/main/java/com/tencent/qcloud/tuikit/timcommon/component/action/PopMenuAdapter.java b/TIMCommon/timcommon/src/main/java/com/tencent/qcloud/tuikit/timcommon/component/action/PopMenuAdapter.java new file mode 100644 index 0000000..4473d24 --- /dev/null +++ b/TIMCommon/timcommon/src/main/java/com/tencent/qcloud/tuikit/timcommon/component/action/PopMenuAdapter.java @@ -0,0 +1,87 @@ +package com.tencent.qcloud.tuikit.timcommon.component.action; + +import android.view.LayoutInflater; +import android.view.View; +import android.view.ViewGroup; +import android.widget.BaseAdapter; +import android.widget.ImageView; +import android.widget.TextView; + +import com.tencent.qcloud.tuikit.timcommon.R; +import com.tencent.qcloud.tuicore.TUIConfig; +import com.tencent.qcloud.tuikit.timcommon.util.ThreadUtils; + +import java.util.ArrayList; +import java.util.List; + +public class PopMenuAdapter extends BaseAdapter { + + private List dataSource = new ArrayList<>(); + + public PopMenuAdapter() { + + } + + public void setDataSource(final List datas) { + dataSource = datas; + ThreadUtils.postOnUiThread(new Runnable() { + @Override + public void run() { + notifyDataSetChanged(); + } + }); + } + + @Override + public int getCount() { + return dataSource.size(); + } + + @Override + public Object getItem(int position) { + return dataSource.get(position); + } + + @Override + public long getItemId(int position) { + return position; + } + + @Override + public View getView(final int position, View convertView, ViewGroup parent) { + + ViewHolder holder; + if (convertView == null) { + convertView = LayoutInflater.from(TUIConfig.getAppContext()).inflate(R.layout.pop_menu_adapter, parent, false); + holder = new ViewHolder(); + holder.menu_icon = convertView.findViewById(R.id.pop_menu_icon); + + int iconSize = convertView.getResources().getDimensionPixelSize(R.dimen.core_pop_menu_icon_size); + ViewGroup.LayoutParams params = holder.menu_icon.getLayoutParams(); + params.width = iconSize; + params.height = iconSize; + holder.menu_icon.setLayoutParams(params); + + holder.menu_lable = convertView.findViewById(R.id.pop_menu_label); + convertView.setTag(holder); + } else { + holder = (ViewHolder) convertView.getTag(); + } + PopMenuAction action = (PopMenuAction) getItem(position); + holder.menu_icon.setVisibility(View.VISIBLE); + if (action.getIcon() != null) { + holder.menu_icon.setImageBitmap(action.getIcon()); + } else if (action.getIconResId() > 0) { + holder.menu_icon.setImageResource(action.getIconResId()); + } else { + holder.menu_icon.setVisibility(View.GONE); + } + holder.menu_lable.setText(action.getActionName()); + return convertView; + } + + static class ViewHolder { + TextView menu_lable; + ImageView menu_icon; + } +} diff --git a/TIMCommon/timcommon/src/main/java/com/tencent/qcloud/tuikit/timcommon/component/activities/BaseLightActivity.java b/TIMCommon/timcommon/src/main/java/com/tencent/qcloud/tuikit/timcommon/component/activities/BaseLightActivity.java new file mode 100644 index 0000000..f4c2a14 --- /dev/null +++ b/TIMCommon/timcommon/src/main/java/com/tencent/qcloud/tuikit/timcommon/component/activities/BaseLightActivity.java @@ -0,0 +1,48 @@ +package com.tencent.qcloud.tuikit.timcommon.component.activities; + +import android.content.Context; +import android.os.Build; +import android.os.Bundle; +import android.view.View; +import android.view.Window; +import android.view.WindowManager; +import android.view.inputmethod.InputMethodManager; + +import androidx.annotation.Nullable; +import androidx.appcompat.app.AppCompatActivity; + +import com.tencent.qcloud.tuicore.TUIThemeManager; +import com.tencent.qcloud.tuikit.timcommon.R; + + +public class BaseLightActivity extends AppCompatActivity { + @Override + protected void onCreate(@Nullable Bundle savedInstanceState) { + super.onCreate(savedInstanceState); + + if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.LOLLIPOP) { + getWindow().clearFlags(WindowManager.LayoutParams.FLAG_TRANSLUCENT_NAVIGATION); + getWindow().addFlags(WindowManager.LayoutParams.FLAG_DRAWS_SYSTEM_BAR_BACKGROUNDS); + getWindow().setStatusBarColor(getResources().getColor(TUIThemeManager.getAttrResId(this, com.tencent.qcloud.tuicore.R.attr.core_header_start_color))); + getWindow().setNavigationBarColor(getResources().getColor(R.color.navigation_bar_color)); + int vis = getWindow().getDecorView().getSystemUiVisibility(); + vis |= View.SYSTEM_UI_FLAG_LIGHT_STATUS_BAR; + vis |= View.SYSTEM_UI_FLAG_LIGHT_NAVIGATION_BAR; + getWindow().getDecorView().setSystemUiVisibility(vis); + } + } + + @Override + public void finish() { + hideSoftInput(); + super.finish(); + } + + public void hideSoftInput() { + InputMethodManager imm = (InputMethodManager) getSystemService(Context.INPUT_METHOD_SERVICE); + Window window = getWindow(); + if (window != null) { + imm.hideSoftInputFromWindow(window.getDecorView().getWindowToken(), 0); + } + } +} diff --git a/TIMCommon/timcommon/src/main/java/com/tencent/qcloud/tuikit/timcommon/component/activities/BaseMinimalistLightActivity.java b/TIMCommon/timcommon/src/main/java/com/tencent/qcloud/tuikit/timcommon/component/activities/BaseMinimalistLightActivity.java new file mode 100644 index 0000000..aea4dd2 --- /dev/null +++ b/TIMCommon/timcommon/src/main/java/com/tencent/qcloud/tuikit/timcommon/component/activities/BaseMinimalistLightActivity.java @@ -0,0 +1,45 @@ +package com.tencent.qcloud.tuikit.timcommon.component.activities; + +import android.content.Context; +import android.os.Build; +import android.os.Bundle; +import android.view.View; +import android.view.Window; +import android.view.WindowManager; +import android.view.inputmethod.InputMethodManager; + +import androidx.annotation.Nullable; +import androidx.appcompat.app.AppCompatActivity; + + +public class BaseMinimalistLightActivity extends AppCompatActivity { + @Override + protected void onCreate(@Nullable Bundle savedInstanceState) { + super.onCreate(savedInstanceState); + + if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.LOLLIPOP) { + getWindow().clearFlags(WindowManager.LayoutParams.FLAG_TRANSLUCENT_NAVIGATION); + getWindow().addFlags(WindowManager.LayoutParams.FLAG_DRAWS_SYSTEM_BAR_BACKGROUNDS); + getWindow().setStatusBarColor(0xFFFFFFFF); + getWindow().setNavigationBarColor(0xFFFFFFFF); + int vis = getWindow().getDecorView().getSystemUiVisibility(); + vis |= View.SYSTEM_UI_FLAG_LIGHT_STATUS_BAR; + vis |= View.SYSTEM_UI_FLAG_LIGHT_NAVIGATION_BAR; + getWindow().getDecorView().setSystemUiVisibility(vis); + } + } + + @Override + public void finish() { + hideSoftInput(); + super.finish(); + } + + public void hideSoftInput() { + InputMethodManager imm = (InputMethodManager) getSystemService(Context.INPUT_METHOD_SERVICE); + Window window = getWindow(); + if (window != null) { + imm.hideSoftInputFromWindow(window.getDecorView().getWindowToken(), 0); + } + } +} diff --git a/TIMCommon/timcommon/src/main/java/com/tencent/qcloud/tuikit/timcommon/component/activities/ImageSelectActivity.java b/TIMCommon/timcommon/src/main/java/com/tencent/qcloud/tuikit/timcommon/component/activities/ImageSelectActivity.java new file mode 100644 index 0000000..c92305f --- /dev/null +++ b/TIMCommon/timcommon/src/main/java/com/tencent/qcloud/tuikit/timcommon/component/activities/ImageSelectActivity.java @@ -0,0 +1,444 @@ +package com.tencent.qcloud.tuikit.timcommon.component.activities; + + +import android.app.ProgressDialog; +import android.content.DialogInterface; +import android.content.Intent; +import android.graphics.Rect; +import android.os.Bundle; +import android.text.TextUtils; +import android.util.Log; +import android.view.LayoutInflater; +import android.view.View; +import android.view.ViewGroup; +import android.widget.Button; +import android.widget.ImageView; +import android.widget.RelativeLayout; + +import androidx.annotation.NonNull; +import androidx.annotation.Nullable; +import androidx.recyclerview.widget.GridLayoutManager; +import androidx.recyclerview.widget.RecyclerView; + +import com.bumptech.glide.Glide; +import com.bumptech.glide.load.DataSource; +import com.bumptech.glide.load.engine.GlideException; +import com.bumptech.glide.request.RequestListener; +import com.bumptech.glide.request.RequestOptions; +import com.bumptech.glide.request.target.Target; +import com.tencent.qcloud.tuicore.TUIConstants; +import com.tencent.qcloud.tuicore.TUIThemeManager; +import com.tencent.qcloud.tuicore.util.ToastUtil; +import com.tencent.qcloud.tuikit.timcommon.R; +import com.tencent.qcloud.tuikit.timcommon.component.TitleBarLayout; +import com.tencent.qcloud.tuikit.timcommon.component.gatherimage.SynthesizedImageView; +import com.tencent.qcloud.tuikit.timcommon.component.interfaces.ITitleBarLayout; +import com.tencent.qcloud.tuikit.timcommon.util.ScreenUtil; + +import java.io.File; +import java.io.Serializable; +import java.util.List; + + +public class ImageSelectActivity extends BaseLightActivity { + private static final String TAG = ImageSelectActivity.class.getSimpleName(); + + public static final int RESULT_CODE_ERROR = -1; + public static final int RESULT_CODE_SUCCESS = 0; + public static final String TITLE = "title"; + public static final String SPAN_COUNT = "spanCount"; + public static final String DATA = "data"; + public static final String ITEM_HEIGHT = "itemHeight"; + public static final String ITEM_WIDTH = "itemWidth"; + public static final String SELECTED = "selected"; + public static final String PLACEHOLDER = "placeholder"; + public static final String NEED_DOWLOAD_LOCAL = "needdowmload"; + + private int defaultSpacing; + + private List data; + private ImageBean selected; + private int placeHolder; + private int columnNum; + private RecyclerView imageGrid; + private GridLayoutManager gridLayoutManager; + private ImageGridAdapter gridAdapter; + private TitleBarLayout titleBarLayout; + private int itemHeight; + private int itemWidth; + + @Override + protected void onCreate(Bundle savedInstanceState) { + super.onCreate(savedInstanceState); + defaultSpacing = ScreenUtil.dip2px(12); + setContentView(R.layout.core_activity_image_select_layout); + Intent intent = getIntent(); + String title = intent.getStringExtra(TITLE); + boolean needDownload = intent.getBooleanExtra(NEED_DOWLOAD_LOCAL, false); + titleBarLayout = findViewById(R.id.image_select_title); + titleBarLayout.setTitle(title, ITitleBarLayout.Position.MIDDLE); + titleBarLayout.setTitle(getString(com.tencent.qcloud.tuicore.R.string.sure), ITitleBarLayout.Position.RIGHT); + titleBarLayout.getRightIcon().setVisibility(View.GONE); + titleBarLayout.getRightTitle().setTextColor(0xFF006EFF); + titleBarLayout.setOnLeftClickListener(new View.OnClickListener() { + @Override + public void onClick(View v) { + setResult(RESULT_CODE_ERROR); + finish(); + } + }); + titleBarLayout.setOnRightClickListener(new View.OnClickListener() { + @Override + public void onClick(View v) { + if (selected == null) { + return; + } + if (needDownload) { + DownloadUrl(); + } else { + Intent resultIntent = new Intent(); + resultIntent.putExtra(DATA, (Serializable) selected); + setResult(RESULT_CODE_SUCCESS, resultIntent); + finish(); + } + } + }); + + data = (List) intent.getSerializableExtra(DATA); + selected = (ImageBean) intent.getSerializableExtra(SELECTED); + placeHolder = intent.getIntExtra(PLACEHOLDER, 0); + itemHeight = intent.getIntExtra(ITEM_HEIGHT, 0); + itemWidth = intent.getIntExtra(ITEM_WIDTH, 0); + columnNum = intent.getIntExtra(SPAN_COUNT, 2); + gridLayoutManager = new GridLayoutManager(this, columnNum); + imageGrid = findViewById(R.id.image_select_grid); + imageGrid.addItemDecoration(new GridDecoration(columnNum, defaultSpacing, defaultSpacing)); + imageGrid.setLayoutManager(gridLayoutManager); + imageGrid.setItemAnimator(null); + gridAdapter = new ImageGridAdapter(); + gridAdapter.setPlaceHolder(placeHolder); + gridAdapter.setSelected(selected); + gridAdapter.setOnItemClickListener(new OnItemClickListener() { + @Override + public void onClick(ImageBean obj) { + selected = obj; + setSelectedStatus(); + } + }); + gridAdapter.setItemWidth(itemWidth); + gridAdapter.setItemHeight(itemHeight); + imageGrid.setAdapter(gridAdapter); + gridAdapter.setData(data); + setSelectedStatus(); + gridAdapter.notifyDataSetChanged(); + } + + private void DownloadUrl() { + if (selected == null) { + return; + } + + if (selected.isDefault()) { + selected.setLocalPath(TUIConstants.TUIChat.CHAT_CONVERSATION_BACKGROUND_DEFAULT_URL); + setResult(selected); + ToastUtil.toastShortMessage(getResources().getString(R.string.setting_success)); + finish(); + return; + } + + String url = selected.getImageUri(); + if (TextUtils.isEmpty(url)) { + Log.d(TAG, "DownloadUrl is null"); + return; + } + + final ProgressDialog dialog = new ProgressDialog(this); + dialog.setProgressStyle(ProgressDialog.STYLE_SPINNER); + dialog.setCancelable(false); + dialog.setCanceledOnTouchOutside(false); + dialog.setOnDismissListener(new DialogInterface.OnDismissListener() { + + @Override + public void onDismiss(DialogInterface dialog) { + // TODO Auto-generated method stub + finish(); + } + }); + dialog.setMessage(getResources().getString(R.string.setting)); + dialog.show(); + + ImageBean finalBean = selected; + Glide.with(this) + .downloadOnly() + .load(url) + .listener(new RequestListener() { + @Override + public boolean onLoadFailed(@Nullable GlideException e, Object model, Target target, boolean isFirstResource) { + dialog.cancel(); + Log.e(TAG, "DownloadUrl onLoadFailed e = " + e); + ToastUtil.toastShortMessage(getResources().getString(R.string.setting_fail)); + return false; + } + + @Override + public boolean onResourceReady(File resource, Object model, Target target, DataSource dataSource, boolean isFirstResource) { + dialog.cancel(); + String path = resource.getAbsolutePath(); + Log.e(TAG, "DownloadUrl resource path = " + path); + finalBean.setLocalPath(path); + setResult(finalBean); + ToastUtil.toastShortMessage(getResources().getString(R.string.setting_success)); + return false; + } + }) + .preload(); + } + + private void setResult(ImageBean bean) { + Intent resultIntent = new Intent(); + resultIntent.putExtra(DATA, (Serializable) bean); + setResult(RESULT_CODE_SUCCESS, resultIntent); + finish(); + } + + private void setSelectedStatus() { + if (selected != null && data != null && data.contains(selected)) { + titleBarLayout.getRightTitle().setEnabled(true); + titleBarLayout.getRightTitle().setTextColor(getResources().getColor(TUIThemeManager.getAttrResId(this, com.tencent.qcloud.tuicore.R.attr.core_primary_color))); + } else { + titleBarLayout.getRightTitle().setEnabled(false); + titleBarLayout.getRightTitle().setTextColor(0xFF666666); + } + gridAdapter.setSelected(selected); + } + + public static class ImageGridAdapter extends RecyclerView.Adapter { + private int itemWidth; + private int itemHeight; + + private List data; + private ImageBean selected; + private int placeHolder; + private OnItemClickListener onItemClickListener; + + public void setData(List data) { + this.data = data; + } + + public void setSelected(ImageBean selected) { + if (data == null || data.isEmpty()) { + this.selected = selected; + } else { + this.selected = selected; + notifyDataSetChanged(); + } + } + + public void setPlaceHolder(int placeHolder) { + this.placeHolder = placeHolder; + } + + public void setItemHeight(int itemHeight) { + this.itemHeight = itemHeight; + } + + public void setItemWidth(int itemWidth) { + this.itemWidth = itemWidth; + } + + public void setOnItemClickListener(OnItemClickListener onItemClickListener) { + this.onItemClickListener = onItemClickListener; + } + + @NonNull + @Override + public ImageViewHolder onCreateViewHolder(@NonNull ViewGroup parent, int viewType) { + View view = LayoutInflater.from(parent.getContext()).inflate(R.layout.core_select_image_item_layout, parent, false); + return new ImageViewHolder(view); + } + + @Override + public void onBindViewHolder(@NonNull ImageViewHolder holder, int position) { + ImageView imageView = holder.imageView; + setItemLayoutParams(holder); + ImageBean imageBean = data.get(position); + if (selected != null && imageBean != null && TextUtils.equals(selected.getThumbnailUri(), imageBean.getThumbnailUri())) { + holder.selectBorderLayout.setVisibility(View.VISIBLE); + } else { + holder.selectBorderLayout.setVisibility(View.GONE); + } + + if (imageBean.getGroupGridAvatar() != null) { + holder.defaultLayout.setVisibility(View.GONE); + if (imageView instanceof SynthesizedImageView) { + SynthesizedImageView synthesizedImageView = ((SynthesizedImageView) (imageView)); + String imageId = imageBean.getImageId(); + synthesizedImageView.setImageId(imageId); + synthesizedImageView.displayImage(imageBean.getGroupGridAvatar()).load(imageId); + } + } else if (imageBean.isDefault()) { + holder.defaultLayout.setVisibility(View.VISIBLE); + imageView.setImageResource(android.R.color.transparent); + } else { + holder.defaultLayout.setVisibility(View.GONE); + Glide.with(holder.itemView.getContext()).asBitmap() + .load(imageBean.getThumbnailUri()) + .placeholder(placeHolder) + .apply(new RequestOptions().error(placeHolder)) + .into(imageView); + } + + holder.itemView.setOnClickListener(new View.OnClickListener() { + @Override + public void onClick(View v) { + if (onItemClickListener != null) { + onItemClickListener.onClick(imageBean); + } + } + }); + } + + private void setItemLayoutParams(ImageViewHolder holder) { + if (itemHeight > 0 && itemWidth > 0) { + ViewGroup.LayoutParams params = holder.imageView.getLayoutParams(); + params.width = itemWidth; + params.height = itemHeight; + holder.imageView.setLayoutParams(params); + + ViewGroup.LayoutParams borderLayoutParams = holder.selectBorderLayout.getLayoutParams(); + borderLayoutParams.width = itemWidth; + borderLayoutParams.height = itemHeight; + holder.selectBorderLayout.setLayoutParams(borderLayoutParams); + + ViewGroup.LayoutParams borderParams = holder.selectedBorder.getLayoutParams(); + borderParams.width = itemWidth; + borderParams.height = itemHeight; + holder.selectedBorder.setLayoutParams(borderParams); + } + } + + @Override + public int getItemCount() { + if (data == null || data.isEmpty()) { + return 0; + } + return data.size(); + } + + public static class ImageViewHolder extends RecyclerView.ViewHolder { + private final ImageView imageView; + private final ImageView selectedBorder; + private final RelativeLayout selectBorderLayout; + private final Button defaultLayout; + + public ImageViewHolder(@NonNull View itemView) { + super(itemView); + imageView = itemView.findViewById(R.id.content_image); + selectedBorder = itemView.findViewById(R.id.select_border); + selectBorderLayout = itemView.findViewById(R.id.selected_border_area); + defaultLayout = itemView.findViewById(R.id.default_image_layout); + } + } + } + + /** + * add spacing + */ + public static class GridDecoration extends RecyclerView.ItemDecoration { + + private final int columnNum; // span count + private final int leftRightSpace; // vertical spacing + private final int topBottomSpace; // horizontal spacing + + public GridDecoration(int columnNum, int leftRightSpace, int topBottomSpace) { + this.columnNum = columnNum; + this.leftRightSpace = leftRightSpace; + this.topBottomSpace = topBottomSpace; + } + + @Override + public void getItemOffsets(Rect outRect, View view, RecyclerView parent, RecyclerView.State state) { + int position = parent.getChildAdapterPosition(view); + int column = position % columnNum; + + outRect.left = column * leftRightSpace / columnNum; + outRect.right = leftRightSpace * (columnNum - 1 - column) / columnNum; + + // add top spacing + if (position >= columnNum) { + outRect.top = topBottomSpace; + } + } + } + + public interface OnItemClickListener { + void onClick(ImageBean obj); + } + + public static class ImageBean implements Serializable{ + String thumbnailUri; // for display + String imageUri; // for download + String localPath; // for local path + boolean isDefault = false; // for default display + List groupGridAvatar = null; // for group grid avatar + String imageId; + + public ImageBean() { + + } + + public ImageBean(String thumbnailUri, String imageUri, boolean isDefault) { + this.thumbnailUri = thumbnailUri; + this.imageUri = imageUri; + this.isDefault = isDefault; + } + + public String getImageUri() { + return imageUri; + } + + public String getThumbnailUri() { + return thumbnailUri; + } + + public void setImageUri(String imageUri) { + this.imageUri = imageUri; + } + + public void setThumbnailUri(String thumbnailUri) { + this.thumbnailUri = thumbnailUri; + } + + public String getLocalPath() { + return localPath; + } + + public void setLocalPath(String localPath) { + this.localPath = localPath; + } + + public boolean isDefault() { + return isDefault; + } + + public void setDefault(boolean aDefault) { + isDefault = aDefault; + } + + public List getGroupGridAvatar() { + return groupGridAvatar; + } + + public void setGroupGridAvatar(List groupGridAvatar) { + this.groupGridAvatar = groupGridAvatar; + } + + public String getImageId() { + return imageId; + } + + public void setImageId(String imageId) { + this.imageId = imageId; + } + } +} \ No newline at end of file diff --git a/TIMCommon/timcommon/src/main/java/com/tencent/qcloud/tuikit/timcommon/component/activities/ImageSelectMinimalistActivity.java b/TIMCommon/timcommon/src/main/java/com/tencent/qcloud/tuikit/timcommon/component/activities/ImageSelectMinimalistActivity.java new file mode 100644 index 0000000..9e19364 --- /dev/null +++ b/TIMCommon/timcommon/src/main/java/com/tencent/qcloud/tuikit/timcommon/component/activities/ImageSelectMinimalistActivity.java @@ -0,0 +1,478 @@ +package com.tencent.qcloud.tuikit.timcommon.component.activities; + + +import android.app.ProgressDialog; +import android.content.DialogInterface; +import android.content.Intent; +import android.graphics.Rect; +import android.os.Bundle; +import android.text.TextUtils; +import android.util.Log; +import android.view.LayoutInflater; +import android.view.View; +import android.view.ViewGroup; +import android.widget.Button; +import android.widget.ImageView; +import android.widget.RelativeLayout; + +import androidx.annotation.NonNull; +import androidx.annotation.Nullable; +import androidx.recyclerview.widget.GridLayoutManager; +import androidx.recyclerview.widget.RecyclerView; + +import com.bumptech.glide.Glide; +import com.bumptech.glide.load.DataSource; +import com.bumptech.glide.load.engine.GlideException; +import com.bumptech.glide.request.RequestListener; +import com.bumptech.glide.request.RequestOptions; +import com.bumptech.glide.request.target.Target; +import com.tencent.qcloud.tuicore.TUIConstants; +import com.tencent.qcloud.tuicore.TUIThemeManager; +import com.tencent.qcloud.tuicore.util.ToastUtil; +import com.tencent.qcloud.tuikit.timcommon.R; +import com.tencent.qcloud.tuikit.timcommon.component.TitleBarLayout; +import com.tencent.qcloud.tuikit.timcommon.component.gatherimage.SynthesizedImageView; +import com.tencent.qcloud.tuikit.timcommon.component.interfaces.ITitleBarLayout; +import com.tencent.qcloud.tuikit.timcommon.util.ScreenUtil; + +import java.io.File; +import java.io.Serializable; +import java.util.List; + + +public class ImageSelectMinimalistActivity extends BaseMinimalistLightActivity { + private static final String TAG = ImageSelectMinimalistActivity.class.getSimpleName(); + + public static final int RESULT_CODE_ERROR = -1; + public static final int RESULT_CODE_SUCCESS = 0; + public static final String TITLE = "title"; + public static final String SPAN_COUNT = "spanCount"; + public static final String DATA = "data"; + public static final String ITEM_HEIGHT = "itemHeight"; + public static final String ITEM_WIDTH = "itemWidth"; + public static final String SELECTED = "selected"; + public static final String PLACEHOLDER = "placeholder"; + public static final String NEED_DOWLOAD_LOCAL = "needdowmload"; + + private int defaultSpacing; + + private List data; + private ImageBean selected; + private int placeHolder; + private int columnNum; + private RecyclerView imageGrid; + private GridLayoutManager gridLayoutManager; + private ImageGridAdapter gridAdapter; + private TitleBarLayout titleBarLayout; + private int itemHeight; + private int itemWidth; + + @Override + protected void onCreate(Bundle savedInstanceState) { + super.onCreate(savedInstanceState); + defaultSpacing = ScreenUtil.dip2px(12); + setContentView(R.layout.core_minimalist_activity_image_select_layout); + Intent intent = getIntent(); + String title = intent.getStringExtra(TITLE); + boolean needDownload = intent.getBooleanExtra(NEED_DOWLOAD_LOCAL, false); + titleBarLayout = findViewById(R.id.image_select_title); + titleBarLayout.setTitle(title, ITitleBarLayout.Position.MIDDLE); + titleBarLayout.setTitle(getString(com.tencent.qcloud.tuicore.R.string.sure), ITitleBarLayout.Position.RIGHT); + titleBarLayout.getRightIcon().setVisibility(View.GONE); + titleBarLayout.getRightTitle().setTextColor(0xFF006EFF); + titleBarLayout.setOnLeftClickListener(new View.OnClickListener() { + @Override + public void onClick(View v) { + setResult(RESULT_CODE_ERROR); + finish(); + } + }); + titleBarLayout.setOnRightClickListener(new View.OnClickListener() { + @Override + public void onClick(View v) { + if (selected == null) { + return; + } + if (needDownload) { + downloadUrl(); + } else { + Intent resultIntent = new Intent(); + resultIntent.putExtra(DATA, (Serializable) selected); + setResult(RESULT_CODE_SUCCESS, resultIntent); + finish(); + } + } + }); + + data = (List) intent.getSerializableExtra(DATA); + selected = (ImageBean) intent.getSerializableExtra(SELECTED); + placeHolder = intent.getIntExtra(PLACEHOLDER, 0); + itemHeight = intent.getIntExtra(ITEM_HEIGHT, 0); + itemWidth = intent.getIntExtra(ITEM_WIDTH, 0); + columnNum = intent.getIntExtra(SPAN_COUNT, 2); + gridLayoutManager = new GridLayoutManager(this, columnNum); + imageGrid = findViewById(R.id.image_select_grid); + imageGrid.addItemDecoration(new GridDecoration(columnNum, defaultSpacing, defaultSpacing)); + imageGrid.setLayoutManager(gridLayoutManager); + imageGrid.setItemAnimator(null); + gridAdapter = new ImageGridAdapter(); + gridAdapter.setPlaceHolder(placeHolder); + gridAdapter.setSelected(selected); + gridAdapter.setOnItemClickListener(new OnItemClickListener() { + @Override + public void onClick(ImageBean obj) { + selected = obj; + setSelectedStatus(); + } + }); + gridAdapter.setItemWidth(itemWidth); + gridAdapter.setItemHeight(itemHeight); + gridAdapter.setData(data); + imageGrid.setAdapter(gridAdapter); + setSelectedStatus(); + } + + private void downloadUrl() { + if (selected == null) { + return; + } + + if (selected.isDefault()) { + selected.setLocalPath(TUIConstants.TUIChat.CHAT_CONVERSATION_BACKGROUND_DEFAULT_URL); + setResult(selected); + ToastUtil.toastShortMessage(getResources().getString(R.string.setting_success)); + finish(); + return; + } + + String url = selected.getImageUri(); + if (TextUtils.isEmpty(url)) { + Log.d(TAG, "DownloadUrl is null"); + return; + } + + final ProgressDialog dialog = new ProgressDialog(this); + dialog.setProgressStyle(ProgressDialog.STYLE_SPINNER); + dialog.setCancelable(false); + dialog.setCanceledOnTouchOutside(false); + dialog.setOnDismissListener(new DialogInterface.OnDismissListener() { + + @Override + public void onDismiss(DialogInterface dialog) { + // TODO Auto-generated method stub + finish(); + } + }); + dialog.setMessage(getResources().getString(R.string.setting)); + dialog.show(); + + ImageBean finalBean = selected; + Glide.with(this) + .downloadOnly() + .load(url) + .listener(new RequestListener() { + @Override + public boolean onLoadFailed(@Nullable GlideException e, Object model, Target target, boolean isFirstResource) { + dialog.cancel(); + Log.e(TAG, "DownloadUrl onLoadFailed e = " + e); + ToastUtil.toastShortMessage(getResources().getString(R.string.setting_fail)); + return false; + } + + @Override + public boolean onResourceReady(File resource, Object model, Target target, DataSource dataSource, boolean isFirstResource) { + dialog.cancel(); + String path = resource.getAbsolutePath(); + Log.e(TAG, "DownloadUrl resource path = " + path); + finalBean.setLocalPath(path); + setResult(finalBean); + ToastUtil.toastShortMessage(getResources().getString(R.string.setting_success)); + return false; + } + }) + .preload(); + } + + private void setResult(ImageBean bean) { + Intent resultIntent = new Intent(); + resultIntent.putExtra(DATA, (Serializable) bean); + setResult(RESULT_CODE_SUCCESS, resultIntent); + finish(); + } + + private void setSelectedStatus() { + if (selected != null && data != null && data.contains(selected)) { + titleBarLayout.getRightTitle().setEnabled(true); + titleBarLayout.getRightTitle().setTextColor(getResources().getColor(TUIThemeManager.getAttrResId(this, com.tencent.qcloud.tuicore.R.attr.core_primary_color))); + } else { + titleBarLayout.getRightTitle().setEnabled(false); + titleBarLayout.getRightTitle().setTextColor(0xFF666666); + } + gridAdapter.setSelected(selected); + } + + public static class ImageGridAdapter extends RecyclerView.Adapter { + private int itemWidth; + private int itemHeight; + + private List data; + private ImageBean selected; + private int placeHolder; + private OnItemClickListener onItemClickListener; + + public void setData(List data) { + this.data = data; + } + + public ImageBean getDataByPosition(int position) { + if (data != null) { + return data.get(position); + } else { + return null; + } + } + + public void setSelected(ImageBean selected) { + if (data == null || data.isEmpty()) { + this.selected = selected; + } else { + int index = indexOf(selected); + int index2 = indexOf(this.selected); + this.selected = selected; + notifyItemChanged(index, this.selected); + notifyItemChanged(index2, this.selected); + } + } + + private int indexOf(ImageBean imageBean) { + if (data == null || data.isEmpty()) { + return -1; + } else { + int originIndex = -1; + for (int i = 0; i < data.size(); i++) { + ImageBean item = data.get(i); + if (TextUtils.equals(item.thumbnailUri, imageBean.thumbnailUri)) { + originIndex = i; + break; + } + } + return originIndex; + } + } + + public void setPlaceHolder(int placeHolder) { + this.placeHolder = placeHolder; + } + + public void setItemHeight(int itemHeight) { + this.itemHeight = itemHeight; + } + + public void setItemWidth(int itemWidth) { + this.itemWidth = itemWidth; + } + + public void setOnItemClickListener(OnItemClickListener onItemClickListener) { + this.onItemClickListener = onItemClickListener; + } + + @NonNull + @Override + public ImageViewHolder onCreateViewHolder(@NonNull ViewGroup parent, int viewType) { + View view = LayoutInflater.from(parent.getContext()).inflate(R.layout.core_select_image_item_layout, parent, false); + return new ImageViewHolder(view); + } + + @Override + public void onBindViewHolder(@NonNull ImageViewHolder holder, int position) { + + } + + @Override + public void onBindViewHolder(@NonNull ImageViewHolder holder, int position, @NonNull List payload) { + ImageView imageView = holder.imageView; + setItemLayoutParams(holder); + ImageBean imageBean = data.get(position); + if (selected != null && imageBean != null && TextUtils.equals(selected.getThumbnailUri(), imageBean.getThumbnailUri())) { + holder.selectBorderLayout.setVisibility(View.VISIBLE); + } else { + holder.selectBorderLayout.setVisibility(View.GONE); + } + + if (imageBean.getGroupGridAvatar() != null) { + holder.defaultLayout.setVisibility(View.GONE); + if (!payload.isEmpty()) { + return; + } + if (imageView instanceof SynthesizedImageView) { + SynthesizedImageView synthesizedImageView = ((SynthesizedImageView) (imageView)); + String imageId = imageBean.getImageId(); + synthesizedImageView.setImageId(imageId); + synthesizedImageView.displayImage(imageBean.getGroupGridAvatar()).load(imageId); + } + } else if (imageBean.isDefault()) { + holder.defaultLayout.setVisibility(View.VISIBLE); + imageView.setImageResource(android.R.color.transparent); + } else { + holder.defaultLayout.setVisibility(View.GONE); + Glide.with(holder.itemView.getContext()).asBitmap() + .load(imageBean.getThumbnailUri()) + .placeholder(placeHolder) + .apply(new RequestOptions().error(placeHolder)) + .into(imageView); + } + + holder.itemView.setOnClickListener(new View.OnClickListener() { + @Override + public void onClick(View v) { + if (onItemClickListener != null) { + onItemClickListener.onClick(imageBean); + } + } + }); + } + + private void setItemLayoutParams(ImageViewHolder holder) { + if (itemHeight > 0 && itemWidth > 0) { + ViewGroup.LayoutParams params = holder.imageView.getLayoutParams(); + params.width = itemWidth; + params.height = itemHeight; + holder.imageView.setLayoutParams(params); + + ViewGroup.LayoutParams borderLayoutParams = holder.selectBorderLayout.getLayoutParams(); + borderLayoutParams.width = itemWidth; + borderLayoutParams.height = itemHeight; + holder.selectBorderLayout.setLayoutParams(borderLayoutParams); + + ViewGroup.LayoutParams borderParams = holder.selectedBorder.getLayoutParams(); + borderParams.width = itemWidth; + borderParams.height = itemHeight; + holder.selectedBorder.setLayoutParams(borderParams); + } + } + + @Override + public int getItemCount() { + if (data == null || data.isEmpty()) { + return 0; + } + return data.size(); + } + + public static class ImageViewHolder extends RecyclerView.ViewHolder { + private final ImageView imageView; + private final ImageView selectedBorder; + private final RelativeLayout selectBorderLayout; + private final Button defaultLayout; + + public ImageViewHolder(@NonNull View itemView) { + super(itemView); + imageView = itemView.findViewById(R.id.content_image); + selectedBorder = itemView.findViewById(R.id.select_border); + selectBorderLayout = itemView.findViewById(R.id.selected_border_area); + defaultLayout = itemView.findViewById(R.id.default_image_layout); + } + } + } + + /** + * add spacing + */ + public static class GridDecoration extends RecyclerView.ItemDecoration { + + private final int columnNum; // span count + private final int leftRightSpace; // vertical spacing + private final int topBottomSpace; // horizontal spacing + + public GridDecoration(int columnNum, int leftRightSpace, int topBottomSpace) { + this.columnNum = columnNum; + this.leftRightSpace = leftRightSpace; + this.topBottomSpace = topBottomSpace; + } + + @Override + public void getItemOffsets(Rect outRect, View view, RecyclerView parent, RecyclerView.State state) { + int position = parent.getChildAdapterPosition(view); + int column = position % columnNum; + + outRect.left = column * leftRightSpace / columnNum; + outRect.right = leftRightSpace * (columnNum - 1 - column) / columnNum; + + // add top spacing + if (position >= columnNum) { + outRect.top = topBottomSpace; + } + } + } + + public interface OnItemClickListener { + void onClick(ImageBean obj); + } + + public static class ImageBean implements Serializable { + String thumbnailUri; // for display + String imageUri; // for download + String localPath; // for local path + boolean isDefault = false; // for default display + List groupGridAvatar = null; // for group grid avatar + String imageId; + + public ImageBean() { + + } + + public ImageBean(String thumbnailUri, String imageUri, boolean isDefault) { + this.thumbnailUri = thumbnailUri; + this.imageUri = imageUri; + this.isDefault = isDefault; + } + + public String getImageUri() { + return imageUri; + } + + public String getThumbnailUri() { + return thumbnailUri; + } + + public void setImageUri(String imageUri) { + this.imageUri = imageUri; + } + + public void setThumbnailUri(String thumbnailUri) { + this.thumbnailUri = thumbnailUri; + } + + public String getLocalPath() { + return localPath; + } + + public void setLocalPath(String localPath) { + this.localPath = localPath; + } + + public boolean isDefault() { + return isDefault; + } + + public void setDefault(boolean aDefault) { + isDefault = aDefault; + } + + public List getGroupGridAvatar() { + return groupGridAvatar; + } + + public void setGroupGridAvatar(List groupGridAvatar) { + this.groupGridAvatar = groupGridAvatar; + } + + public String getImageId() { + return imageId; + } + + public void setImageId(String imageId) { + this.imageId = imageId; + } + } +} \ No newline at end of file diff --git a/TIMCommon/timcommon/src/main/java/com/tencent/qcloud/tuikit/timcommon/component/activities/SelectionActivity.java b/TIMCommon/timcommon/src/main/java/com/tencent/qcloud/tuikit/timcommon/component/activities/SelectionActivity.java new file mode 100644 index 0000000..f1ffe21 --- /dev/null +++ b/TIMCommon/timcommon/src/main/java/com/tencent/qcloud/tuikit/timcommon/component/activities/SelectionActivity.java @@ -0,0 +1,243 @@ +package com.tencent.qcloud.tuikit.timcommon.component.activities; + +import android.content.Context; +import android.content.Intent; +import android.os.Bundle; +import android.text.InputFilter; +import android.text.TextUtils; +import android.view.LayoutInflater; +import android.view.View; +import android.view.ViewGroup; +import android.widget.EditText; +import android.widget.ImageView; +import android.widget.TextView; + +import androidx.annotation.NonNull; +import androidx.recyclerview.widget.DividerItemDecoration; +import androidx.recyclerview.widget.RecyclerView; + +import com.tencent.qcloud.tuikit.timcommon.R; +import com.tencent.qcloud.tuikit.timcommon.component.CustomLinearLayoutManager; +import com.tencent.qcloud.tuikit.timcommon.component.TitleBarLayout; +import com.tencent.qcloud.tuikit.timcommon.component.interfaces.ITitleBarLayout; + +import java.util.ArrayList; + +public class SelectionActivity extends BaseLightActivity { + + private static OnResultReturnListener sOnResultReturnListener; + + private RecyclerView selectListView; + private SelectAdapter selectListAdapter; + private EditText input; + private int mSelectionType; + private ArrayList selectList = new ArrayList<>(); + private int selectedItem = -1; + private OnItemClickListener onItemClickListener; + private boolean needConfirm = true; + private boolean returnNow = true; + + public static void startTextSelection(Context context, Bundle bundle, OnResultReturnListener listener) { + bundle.putInt(Selection.TYPE, Selection.TYPE_TEXT); + startSelection(context, bundle, listener); + } + + public static void startListSelection(Context context, Bundle bundle, OnResultReturnListener listener) { + bundle.putInt(Selection.TYPE, Selection.TYPE_LIST); + startSelection(context, bundle, listener); + } + + private static void startSelection(Context context, Bundle bundle, OnResultReturnListener listener) { + Intent intent = new Intent(context, SelectionActivity.class); + intent.putExtra(Selection.CONTENT, bundle); + intent.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK); + context.startActivity(intent); + sOnResultReturnListener = listener; + } + + @Override + protected void onCreate(Bundle savedInstanceState) { + super.onCreate(savedInstanceState); + setContentView(R.layout.tuicore_selection_activity); + final TitleBarLayout titleBar = findViewById(R.id.edit_title_bar); + selectListView = findViewById(R.id.select_list); + selectListAdapter = new SelectAdapter(); + selectListView.setAdapter(selectListAdapter); + selectListView.setLayoutManager(new CustomLinearLayoutManager(this)); + DividerItemDecoration dividerItemDecoration = new DividerItemDecoration(this, DividerItemDecoration.VERTICAL); + dividerItemDecoration.setDrawable(getResources().getDrawable(R.drawable.core_list_divider)); + selectListView.addItemDecoration(dividerItemDecoration); + onItemClickListener = new OnItemClickListener() { + @Override + public void onClick(int position) { + selectedItem = position; + selectListAdapter.setSelectedItem(position); + selectListAdapter.notifyDataSetChanged(); + if (!needConfirm) { + echoClick(); + } + } + }; + input = findViewById(R.id.edit_content_et); + + Bundle bundle = getIntent().getBundleExtra(Selection.CONTENT); + switch (bundle.getInt(Selection.TYPE)) { + case Selection.TYPE_TEXT: + selectListView.setVisibility(View.GONE); + String defaultString = bundle.getString(Selection.INIT_CONTENT); + int limit = bundle.getInt(Selection.LIMIT); + if (!TextUtils.isEmpty(defaultString)) { + input.setText(defaultString); + input.setSelection(defaultString.length()); + } + if (limit > 0) { + input.setFilters(new InputFilter[]{new InputFilter.LengthFilter(limit)}); + } + break; + case Selection.TYPE_LIST: + input.setVisibility(View.GONE); + ArrayList list = bundle.getStringArrayList(Selection.LIST); + selectedItem = bundle.getInt(Selection.DEFAULT_SELECT_ITEM_INDEX); + if (list == null || list.size() == 0) { + return; + } + selectList.clear(); + selectList.addAll(list); + selectListAdapter.setSelectedItem(selectedItem); + selectListAdapter.setData(selectList); + selectListAdapter.notifyDataSetChanged(); + + break; + default: + finish(); + return; + } + mSelectionType = bundle.getInt(Selection.TYPE); + + final String title = bundle.getString(Selection.TITLE); + + needConfirm = bundle.getBoolean(Selection.NEED_CONFIRM, true); + returnNow = bundle.getBoolean(Selection.RETURN_NOW, true); + + titleBar.setTitle(title, ITitleBarLayout.Position.MIDDLE); + titleBar.setOnLeftClickListener(new View.OnClickListener() { + @Override + public void onClick(View v) { + finish(); + } + }); + titleBar.getRightIcon().setVisibility(View.GONE); + if (needConfirm) { + titleBar.getRightTitle().setText(getResources().getString(com.tencent.qcloud.tuicore.R.string.sure)); + titleBar.setOnRightClickListener(new View.OnClickListener() { + @Override + public void onClick(View v) { + echoClick(); + } + }); + } else { + titleBar.getRightGroup().setVisibility(View.GONE); + } + } + + private void echoClick() { + switch (mSelectionType) { + case Selection.TYPE_TEXT: + if (sOnResultReturnListener != null) { + sOnResultReturnListener.onReturn(input.getText().toString()); + } + break; + case Selection.TYPE_LIST: + if (sOnResultReturnListener != null) { + sOnResultReturnListener.onReturn(selectedItem); + } + break; + } + if (returnNow) { + finish(); + } + } + + @Override + protected void onStop() { + super.onStop(); + sOnResultReturnListener = null; + } + + class SelectAdapter extends RecyclerView.Adapter { + int selectedItem = -1; + ArrayList data = new ArrayList<>(); + + public void setData(ArrayList data) { + this.data.clear(); + this.data.addAll(data); + } + + public void setSelectedItem(int selectedItem) { + this.selectedItem = selectedItem; + } + + @NonNull + @Override + public SelectViewHolder onCreateViewHolder(@NonNull ViewGroup parent, int viewType) { + View view = LayoutInflater.from(SelectionActivity.this).inflate(R.layout.core_select_item_layout,parent, false); + return new SelectViewHolder(view); + } + + @Override + public void onBindViewHolder(@NonNull SelectViewHolder holder, int position) { + String nameStr = data.get(position); + holder.name.setText(nameStr); + if (selectedItem == position) { + holder.selectedIcon.setVisibility(View.VISIBLE); + } else { + holder.selectedIcon.setVisibility(View.GONE); + } + holder.itemView.setOnClickListener(new View.OnClickListener() { + @Override + public void onClick(View v) { + onItemClickListener.onClick(position); + } + }); + } + + @Override + public int getItemCount() { + return data.size(); + } + + class SelectViewHolder extends RecyclerView.ViewHolder{ + TextView name; + ImageView selectedIcon; + public SelectViewHolder(@NonNull View itemView) { + super(itemView); + name = itemView.findViewById(R.id.name); + selectedIcon = itemView.findViewById(R.id.selected_icon); + } + } + } + + public interface OnResultReturnListener { + void onReturn(Object res); + } + + public interface OnItemClickListener { + void onClick(int position); + } + + public static class Selection { + public static final String SELECT_ALL = "select_all"; + public static final String CONTENT = "content"; + public static final String TYPE = "type"; + public static final String TITLE = "title"; + public static final String INIT_CONTENT = "init_content"; + public static final String DEFAULT_SELECT_ITEM_INDEX = "default_select_item_index"; + public static final String LIST = "list"; + public static final String LIMIT = "limit"; + public static final String NEED_CONFIRM = "needConfirm"; + public static final String RETURN_NOW = "returnNow"; + public static final int TYPE_TEXT = 1; + public static final int TYPE_LIST = 2; + + } +} diff --git a/TIMCommon/timcommon/src/main/java/com/tencent/qcloud/tuikit/timcommon/component/activities/SelectionMinimalistActivity.java b/TIMCommon/timcommon/src/main/java/com/tencent/qcloud/tuikit/timcommon/component/activities/SelectionMinimalistActivity.java new file mode 100644 index 0000000..e82b651 --- /dev/null +++ b/TIMCommon/timcommon/src/main/java/com/tencent/qcloud/tuikit/timcommon/component/activities/SelectionMinimalistActivity.java @@ -0,0 +1,243 @@ +package com.tencent.qcloud.tuikit.timcommon.component.activities; + +import android.content.Context; +import android.content.Intent; +import android.os.Bundle; +import android.text.InputFilter; +import android.text.TextUtils; +import android.view.LayoutInflater; +import android.view.View; +import android.view.ViewGroup; +import android.widget.EditText; +import android.widget.ImageView; +import android.widget.TextView; + +import androidx.annotation.NonNull; +import androidx.recyclerview.widget.DividerItemDecoration; +import androidx.recyclerview.widget.RecyclerView; + +import com.tencent.qcloud.tuikit.timcommon.R; +import com.tencent.qcloud.tuikit.timcommon.component.CustomLinearLayoutManager; +import com.tencent.qcloud.tuikit.timcommon.component.TitleBarLayout; +import com.tencent.qcloud.tuikit.timcommon.component.interfaces.ITitleBarLayout; + +import java.util.ArrayList; + +public class SelectionMinimalistActivity extends BaseMinimalistLightActivity { + + private static OnResultReturnListener sOnResultReturnListener; + + private RecyclerView selectListView; + private SelectAdapter selectListAdapter; + private EditText input; + private int mSelectionType; + private ArrayList selectList = new ArrayList<>(); + private int selectedItem = -1; + private OnItemClickListener onItemClickListener; + private boolean needConfirm = true; + private boolean returnNow = true; + + public static void startTextSelection(Context context, Bundle bundle, OnResultReturnListener listener) { + bundle.putInt(Selection.TYPE, Selection.TYPE_TEXT); + startSelection(context, bundle, listener); + } + + public static void startListSelection(Context context, Bundle bundle, OnResultReturnListener listener) { + bundle.putInt(Selection.TYPE, Selection.TYPE_LIST); + startSelection(context, bundle, listener); + } + + private static void startSelection(Context context, Bundle bundle, OnResultReturnListener listener) { + Intent intent = new Intent(context, SelectionMinimalistActivity.class); + intent.putExtra(Selection.CONTENT, bundle); + intent.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK); + context.startActivity(intent); + sOnResultReturnListener = listener; + } + + @Override + protected void onCreate(Bundle savedInstanceState) { + super.onCreate(savedInstanceState); + setContentView(R.layout.core_minimalist_selection_activity); + final TitleBarLayout titleBar = findViewById(R.id.edit_title_bar); + selectListView = findViewById(R.id.select_list); + selectListAdapter = new SelectAdapter(); + selectListView.setAdapter(selectListAdapter); + selectListView.setLayoutManager(new CustomLinearLayoutManager(this)); + DividerItemDecoration dividerItemDecoration = new DividerItemDecoration(this, DividerItemDecoration.VERTICAL); + dividerItemDecoration.setDrawable(getResources().getDrawable(R.drawable.core_list_divider)); + selectListView.addItemDecoration(dividerItemDecoration); + onItemClickListener = new OnItemClickListener() { + @Override + public void onClick(int position) { + selectedItem = position; + selectListAdapter.setSelectedItem(position); + selectListAdapter.notifyDataSetChanged(); + if (!needConfirm) { + echoClick(); + } + } + }; + input = findViewById(R.id.edit_content_et); + + Bundle bundle = getIntent().getBundleExtra(Selection.CONTENT); + switch (bundle.getInt(Selection.TYPE)) { + case Selection.TYPE_TEXT: + selectListView.setVisibility(View.GONE); + String defaultString = bundle.getString(Selection.INIT_CONTENT); + int limit = bundle.getInt(Selection.LIMIT); + if (!TextUtils.isEmpty(defaultString)) { + input.setText(defaultString); + input.setSelection(defaultString.length()); + } + if (limit > 0) { + input.setFilters(new InputFilter[]{new InputFilter.LengthFilter(limit)}); + } + break; + case Selection.TYPE_LIST: + input.setVisibility(View.GONE); + ArrayList list = bundle.getStringArrayList(Selection.LIST); + selectedItem = bundle.getInt(Selection.DEFAULT_SELECT_ITEM_INDEX); + if (list == null || list.size() == 0) { + return; + } + selectList.clear(); + selectList.addAll(list); + selectListAdapter.setSelectedItem(selectedItem); + selectListAdapter.setData(selectList); + selectListAdapter.notifyDataSetChanged(); + + break; + default: + finish(); + return; + } + mSelectionType = bundle.getInt(Selection.TYPE); + + final String title = bundle.getString(Selection.TITLE); + + needConfirm = bundle.getBoolean(Selection.NEED_CONFIRM, true); + returnNow = bundle.getBoolean(Selection.RETURN_NOW, true); + + titleBar.setTitle(title, ITitleBarLayout.Position.MIDDLE); + titleBar.setOnLeftClickListener(new View.OnClickListener() { + @Override + public void onClick(View v) { + finish(); + } + }); + titleBar.getRightIcon().setVisibility(View.GONE); + if (needConfirm) { + titleBar.getRightTitle().setText(getResources().getString(com.tencent.qcloud.tuicore.R.string.sure)); + titleBar.setOnRightClickListener(new View.OnClickListener() { + @Override + public void onClick(View v) { + echoClick(); + } + }); + } else { + titleBar.getRightGroup().setVisibility(View.GONE); + } + } + + private void echoClick() { + switch (mSelectionType) { + case Selection.TYPE_TEXT: + if (sOnResultReturnListener != null) { + sOnResultReturnListener.onReturn(input.getText().toString()); + } + break; + case Selection.TYPE_LIST: + if (sOnResultReturnListener != null) { + sOnResultReturnListener.onReturn(selectedItem); + } + break; + } + if (returnNow) { + finish(); + } + } + + @Override + protected void onStop() { + super.onStop(); + sOnResultReturnListener = null; + } + + class SelectAdapter extends RecyclerView.Adapter { + int selectedItem = -1; + ArrayList data = new ArrayList<>(); + + public void setData(ArrayList data) { + this.data.clear(); + this.data.addAll(data); + } + + public void setSelectedItem(int selectedItem) { + this.selectedItem = selectedItem; + } + + @NonNull + @Override + public SelectViewHolder onCreateViewHolder(@NonNull ViewGroup parent, int viewType) { + View view = LayoutInflater.from(SelectionMinimalistActivity.this).inflate(R.layout.core_select_item_layout,parent, false); + return new SelectViewHolder(view); + } + + @Override + public void onBindViewHolder(@NonNull SelectViewHolder holder, int position) { + String nameStr = data.get(position); + holder.name.setText(nameStr); + if (selectedItem == position) { + holder.selectedIcon.setVisibility(View.VISIBLE); + } else { + holder.selectedIcon.setVisibility(View.GONE); + } + holder.itemView.setOnClickListener(new View.OnClickListener() { + @Override + public void onClick(View v) { + onItemClickListener.onClick(position); + } + }); + } + + @Override + public int getItemCount() { + return data.size(); + } + + class SelectViewHolder extends RecyclerView.ViewHolder{ + TextView name; + ImageView selectedIcon; + public SelectViewHolder(@NonNull View itemView) { + super(itemView); + name = itemView.findViewById(R.id.name); + selectedIcon = itemView.findViewById(R.id.selected_icon); + } + } + } + + public interface OnResultReturnListener { + void onReturn(Object res); + } + + public interface OnItemClickListener { + void onClick(int position); + } + + public static class Selection { + public static final String SELECT_ALL = "select_all"; + public static final String CONTENT = "content"; + public static final String TYPE = "type"; + public static final String TITLE = "title"; + public static final String INIT_CONTENT = "init_content"; + public static final String DEFAULT_SELECT_ITEM_INDEX = "default_select_item_index"; + public static final String LIST = "list"; + public static final String LIMIT = "limit"; + public static final String NEED_CONFIRM = "needConfirm"; + public static final String RETURN_NOW = "returnNow"; + public static final int TYPE_TEXT = 1; + public static final int TYPE_LIST = 2; + + } +} diff --git a/TIMCommon/timcommon/src/main/java/com/tencent/qcloud/tuikit/timcommon/component/dialog/TUIKitDialog.java b/TIMCommon/timcommon/src/main/java/com/tencent/qcloud/tuikit/timcommon/component/dialog/TUIKitDialog.java new file mode 100644 index 0000000..7033eb6 --- /dev/null +++ b/TIMCommon/timcommon/src/main/java/com/tencent/qcloud/tuikit/timcommon/component/dialog/TUIKitDialog.java @@ -0,0 +1,337 @@ +package com.tencent.qcloud.tuikit.timcommon.component.dialog; + +import static com.tencent.qcloud.tuicore.TUIConfig.TUICORE_SETTINGS_SP_NAME; + +import android.app.Dialog; +import android.content.Context; +import android.text.method.MovementMethod; +import android.view.Display; +import android.view.LayoutInflater; +import android.view.View; +import android.view.View.OnClickListener; +import android.view.WindowManager; +import android.widget.Button; +import android.widget.FrameLayout; +import android.widget.ImageView; +import android.widget.LinearLayout; +import android.widget.LinearLayout.LayoutParams; +import android.widget.TextView; + +import androidx.annotation.NonNull; + +import com.tencent.qcloud.tuicore.BuildConfig; +import com.tencent.qcloud.tuicore.TUIConfig; +import com.tencent.qcloud.tuicore.util.SPUtils; +import com.tencent.qcloud.tuikit.timcommon.R; + +import java.lang.ref.WeakReference; + +public class TUIKitDialog { + + private Context mContext; + protected Dialog dialog; + private LinearLayout mBackgroundLayout; + private LinearLayout mMainLayout; + protected TextView mTitleTv; + private Button mCancelButton; + private Button mSureButton; + private ImageView mLineImg; + private Display mDisplay; + + private boolean showTitle = false; + private boolean showPosBtn = false; + private boolean showNegBtn = false; + + private float dialogWidth = 0.7f; + + + public TUIKitDialog(Context context) { + this.mContext = context; + WindowManager windowManager = (WindowManager) context.getSystemService(Context.WINDOW_SERVICE); + mDisplay = windowManager.getDefaultDisplay(); + } + + public TUIKitDialog builder() { + View view = LayoutInflater.from(mContext).inflate(R.layout.view_dialog, null); + mBackgroundLayout = (LinearLayout) view.findViewById(R.id.ll_background); + mMainLayout = (LinearLayout) view.findViewById(R.id.ll_alert); + mMainLayout.setVerticalGravity(View.GONE); + mTitleTv = (TextView) view.findViewById(R.id.tv_title); + mTitleTv.setVisibility(View.GONE); + mCancelButton = (Button) view.findViewById(R.id.btn_neg); + mCancelButton.setVisibility(View.GONE); + mSureButton = (Button) view.findViewById(R.id.btn_pos); + mSureButton.setVisibility(View.GONE); + mLineImg = (ImageView) view.findViewById(R.id.img_line); + mLineImg.setVisibility(View.GONE); + + dialog = new Dialog(mContext, R.style.TUIKit_AlertDialogStyle); + dialog.setContentView(view); + + mBackgroundLayout.setLayoutParams(new FrameLayout.LayoutParams((int) (mDisplay.getWidth() * dialogWidth), LayoutParams.WRAP_CONTENT)); + return this; + } + + + public TUIKitDialog setTitle(@NonNull CharSequence title) { + showTitle = true; + mTitleTv.setText(title); + return this; + } + + /*** + * Whether to click back to cancel + * @param cancel + * @return + */ + public TUIKitDialog setCancelable(boolean cancel) { + dialog.setCancelable(cancel); + return this; + } + + /** + * Whether the setting can be canceled + * + * @param isCancelOutside + * @return + */ + public TUIKitDialog setCancelOutside(boolean isCancelOutside) { + dialog.setCanceledOnTouchOutside(isCancelOutside); + return this; + } + + public TUIKitDialog setPositiveButton(CharSequence text, + final OnClickListener listener) { + showPosBtn = true; + mSureButton.setText(text); + mSureButton.setOnClickListener(new OnClickListener() { + @Override + public void onClick(View v) { + listener.onClick(v); + dialog.dismiss(); + } + }); + return this; + } + + public void setTitleGravity(int gravity) { + mTitleTv.setGravity(gravity); + } + + public TUIKitDialog setPositiveButton(final OnClickListener listener) { + setPositiveButton(TUIConfig.getAppContext().getString(com.tencent.qcloud.tuicore.R.string.sure), listener); + return this; + } + + public TUIKitDialog setNegativeButton(CharSequence text, + final OnClickListener listener) { + showNegBtn = true; + mCancelButton.setText(text); + mCancelButton.setOnClickListener(new OnClickListener() { + @Override + public void onClick(View v) { + listener.onClick(v); + dialog.dismiss(); + } + }); + return this; + } + + public TUIKitDialog setNegativeButton(final OnClickListener listener) { + setNegativeButton(TUIConfig.getAppContext().getString(com.tencent.qcloud.tuicore.R.string.cancel), listener); + return this; + } + + + private void setLayout() { + if (!showTitle) { + mTitleTv.setVisibility(View.GONE); + } + + if (showTitle) { + mTitleTv.setVisibility(View.VISIBLE); + } + + if (!showPosBtn && !showNegBtn) { + mSureButton.setVisibility(View.GONE); + mSureButton.setOnClickListener(new OnClickListener() { + @Override + public void onClick(View v) { + dialog.dismiss(); + } + }); + } + + if (showPosBtn && showNegBtn) { + mSureButton.setVisibility(View.VISIBLE); + mCancelButton.setVisibility(View.VISIBLE); + mLineImg.setVisibility(View.VISIBLE); + } + + if (showPosBtn && !showNegBtn) { + mSureButton.setVisibility(View.VISIBLE); + } + + if (!showPosBtn && showNegBtn) { + mCancelButton.setVisibility(View.VISIBLE); + } + } + + public void show() { + setLayout(); + dialog.show(); + } + + public void dismiss() { + if (dialog != null && dialog.isShowing()) { + dialog.dismiss(); + } + } + + public boolean isShowing() { + return dialog != null && dialog.isShowing(); + } + + /** + * 设置dialog 宽度 + * + * @param dialogWidth + * @return + */ + public TUIKitDialog setDialogWidth(float dialogWidth) { + if (mBackgroundLayout != null) { + mBackgroundLayout.setLayoutParams(new FrameLayout.LayoutParams((int) (mDisplay.getWidth() * dialogWidth), LayoutParams.WRAP_CONTENT)); + } + this.dialogWidth = dialogWidth; + return this; + } + + public static class TUIIMUpdateDialog { + + private static final class TUIIMUpdateDialogHolder { + private static final TUIIMUpdateDialog instance = new TUIIMUpdateDialog(); + } + + public static final String KEY_NEVER_SHOW = "neverShow"; + + private boolean isNeverShow; + private boolean isShowOnlyDebug = false; + private String dialogFeatureName; + + private WeakReference tuiKitDialog; + + public static TUIIMUpdateDialog getInstance() { + return TUIIMUpdateDialogHolder.instance; + } + + private TUIIMUpdateDialog() { + isNeverShow = SPUtils.getInstance(TUICORE_SETTINGS_SP_NAME).getBoolean(getDialogFeatureName(), false); + } + + public TUIIMUpdateDialog createDialog(Context context) { + tuiKitDialog = new WeakReference<>(new TUIKitDialog(context)); + tuiKitDialog.get().builder(); + return this; + } + + public void setNeverShow(boolean neverShowAlert) { + this.isNeverShow = neverShowAlert; + SPUtils.getInstance(TUICORE_SETTINGS_SP_NAME).put(getDialogFeatureName(), neverShowAlert); + } + + public TUIIMUpdateDialog setShowOnlyDebug(boolean isShowOnlyDebug) { + this.isShowOnlyDebug = isShowOnlyDebug; + return this; + } + + public TUIIMUpdateDialog setMovementMethod(MovementMethod movementMethod) { + if (tuiKitDialog != null && tuiKitDialog.get() != null) { + tuiKitDialog.get().mTitleTv.setMovementMethod(movementMethod); + } + return this; + } + + public TUIIMUpdateDialog setHighlightColor(int color) { + if (tuiKitDialog != null && tuiKitDialog.get() != null) { + tuiKitDialog.get().mTitleTv.setHighlightColor(color); + } + return this; + } + + public TUIIMUpdateDialog setCancelable(boolean cancelable) { + if (tuiKitDialog != null && tuiKitDialog.get() != null) { + tuiKitDialog.get().setCancelable(cancelable); + } + return this; + } + + public TUIIMUpdateDialog setCancelOutside(boolean cancelOutside) { + if (tuiKitDialog != null && tuiKitDialog.get() != null) { + tuiKitDialog.get().setCancelOutside(cancelOutside); + } + return this; + } + + public TUIIMUpdateDialog setTitle(CharSequence charSequence) { + if (tuiKitDialog != null && tuiKitDialog.get() != null) { + tuiKitDialog.get().setTitle(charSequence); + } + return this; + } + + public TUIIMUpdateDialog setDialogWidth(float dialogWidth) { + if (tuiKitDialog != null && tuiKitDialog.get() != null) { + tuiKitDialog.get().setDialogWidth(dialogWidth); + } + return this; + } + + public TUIIMUpdateDialog setPositiveButton(CharSequence text, OnClickListener clickListener) { + if (tuiKitDialog != null && tuiKitDialog.get() != null) { + tuiKitDialog.get().setPositiveButton(text, clickListener); + } + return this; + } + + public TUIIMUpdateDialog setNegativeButton(CharSequence text, OnClickListener clickListener) { + if (tuiKitDialog != null && tuiKitDialog.get() != null) { + tuiKitDialog.get().setNegativeButton(text, clickListener); + } + return this; + } + + public TUIIMUpdateDialog setDialogFeatureName(String featureName) { + this.dialogFeatureName = featureName; + return this; + } + + private String getDialogFeatureName() { + return dialogFeatureName; + } + + public void show() { + if (tuiKitDialog == null || tuiKitDialog.get() == null) { + return; + } + isNeverShow = SPUtils.getInstance(TUICORE_SETTINGS_SP_NAME).getBoolean(getDialogFeatureName(), false); + Dialog dialog = tuiKitDialog.get().dialog; + if (dialog == null || dialog.isShowing()) { + return; + } + if (isNeverShow) { + return; + } + if (isShowOnlyDebug && !BuildConfig.DEBUG) { + return; + } + tuiKitDialog.get().show(); + } + + public void dismiss() { + if (tuiKitDialog == null || tuiKitDialog.get() == null) { + return; + } + tuiKitDialog.get().dismiss(); + } + } +} diff --git a/TIMCommon/timcommon/src/main/java/com/tencent/qcloud/tuikit/timcommon/component/face/ChatFace.java b/TIMCommon/timcommon/src/main/java/com/tencent/qcloud/tuikit/timcommon/component/face/ChatFace.java new file mode 100644 index 0000000..2a16219 --- /dev/null +++ b/TIMCommon/timcommon/src/main/java/com/tencent/qcloud/tuikit/timcommon/component/face/ChatFace.java @@ -0,0 +1,51 @@ +package com.tencent.qcloud.tuikit.timcommon.component.face; + +import java.io.Serializable; + +public class ChatFace implements Serializable { + private int width; + private int height; + protected String faceUrl; + private FaceGroup faceGroup; + private String faceKey; + + public void setFaceKey(String faceKey) { + this.faceKey = faceKey; + } + + public String getFaceKey() { + return faceKey; + } + + public void setFaceGroup(FaceGroup faceGroup) { + this.faceGroup = faceGroup; + } + + public FaceGroup getFaceGroup() { + return faceGroup; + } + + public void setWidth(int width) { + this.width = width; + } + + public void setHeight(int height) { + this.height = height; + } + + public void setFaceUrl(String faceUrl) { + this.faceUrl = faceUrl; + } + + public String getFaceUrl() { + return faceUrl; + } + + public int getHeight() { + return height; + } + + public int getWidth() { + return width; + } +} diff --git a/TIMCommon/timcommon/src/main/java/com/tencent/qcloud/tuikit/timcommon/component/face/CustomFace.java b/TIMCommon/timcommon/src/main/java/com/tencent/qcloud/tuikit/timcommon/component/face/CustomFace.java new file mode 100644 index 0000000..275fc77 --- /dev/null +++ b/TIMCommon/timcommon/src/main/java/com/tencent/qcloud/tuikit/timcommon/component/face/CustomFace.java @@ -0,0 +1,18 @@ +package com.tencent.qcloud.tuikit.timcommon.component.face; + +/** + * 自定义表情属性类 + * + * Custom expression attribute class + */ +public class CustomFace extends ChatFace { + + /** + * 设置表情在asset中的路径 + * + * @param assetPath + */ + public void setAssetPath(String assetPath) { + this.faceUrl = "file:///android_asset/" + assetPath; + } +} diff --git a/TIMCommon/timcommon/src/main/java/com/tencent/qcloud/tuikit/timcommon/component/face/Emoji.java b/TIMCommon/timcommon/src/main/java/com/tencent/qcloud/tuikit/timcommon/component/face/Emoji.java new file mode 100644 index 0000000..fb790fe --- /dev/null +++ b/TIMCommon/timcommon/src/main/java/com/tencent/qcloud/tuikit/timcommon/component/face/Emoji.java @@ -0,0 +1,14 @@ +package com.tencent.qcloud.tuikit.timcommon.component.face; + +import android.graphics.Bitmap; + +public class Emoji extends ChatFace { + private Bitmap icon; + public Bitmap getIcon() { + return icon; + } + + public void setIcon(Bitmap icon) { + this.icon = icon; + } +} diff --git a/TIMCommon/timcommon/src/main/java/com/tencent/qcloud/tuikit/timcommon/component/face/FaceGroup.java b/TIMCommon/timcommon/src/main/java/com/tencent/qcloud/tuikit/timcommon/component/face/FaceGroup.java new file mode 100644 index 0000000..2bb6343 --- /dev/null +++ b/TIMCommon/timcommon/src/main/java/com/tencent/qcloud/tuikit/timcommon/component/face/FaceGroup.java @@ -0,0 +1,90 @@ +package com.tencent.qcloud.tuikit.timcommon.component.face; + +import android.text.TextUtils; + +import java.util.ArrayList; +import java.util.LinkedHashMap; +import java.util.Map; + +public class FaceGroup { + private int groupID; + private String groupName; + private String desc; + private String faceGroupIconUrl; + private int pageRowCount; + private int pageColumnCount; + private final Map faces = new LinkedHashMap<>(); + + public int getGroupID() { + return groupID; + } + + public void setGroupID(int groupID) { + this.groupID = groupID; + } + + public void setGroupName(String groupName) { + this.groupName = groupName; + } + + public String getGroupName() { + return groupName; + } + + public String getDesc() { + return desc; + } + + public void setDesc(String desc) { + this.desc = desc; + } + + public void setFaceGroupIconUrl(String faceGroupIconUrl) { + this.faceGroupIconUrl = faceGroupIconUrl; + } + + public String getFaceGroupIconUrl() { + return faceGroupIconUrl; + } + + public int getPageRowCount() { + return pageRowCount; + } + + public void setPageRowCount(int pageRowCount) { + this.pageRowCount = pageRowCount; + } + + public int getPageColumnCount() { + return pageColumnCount; + } + + public void setPageColumnCount(int pageColumnCount) { + this.pageColumnCount = pageColumnCount; + } + + public ArrayList getFaces() { + return new ArrayList<>(faces.values()); + } + + public void addFace(String faceKey, ChatFace face) { + face.setFaceGroup(this); + faces.put(faceKey, face); + } + + public ChatFace getFace(String faceKey) { + if (TextUtils.isEmpty(faceKey)) { + return null; + } + ChatFace face = faces.get(faceKey); + if (face == null) { + int index = faceKey.lastIndexOf("@2x"); + if (index == -1) { + return null; + } + String oldFaceKey = faceKey.substring(0, index); + face = faces.get(oldFaceKey); + } + return face; + } +} diff --git a/TIMCommon/timcommon/src/main/java/com/tencent/qcloud/tuikit/timcommon/component/face/FaceManager.java b/TIMCommon/timcommon/src/main/java/com/tencent/qcloud/tuikit/timcommon/component/face/FaceManager.java new file mode 100644 index 0000000..c6b041c --- /dev/null +++ b/TIMCommon/timcommon/src/main/java/com/tencent/qcloud/tuikit/timcommon/component/face/FaceManager.java @@ -0,0 +1,459 @@ +package com.tencent.qcloud.tuikit.timcommon.component.face; + +import android.annotation.SuppressLint; +import android.content.Context; +import android.graphics.Bitmap; +import android.text.Editable; +import android.text.Spannable; +import android.text.SpannableStringBuilder; +import android.text.TextUtils; +import android.text.style.ImageSpan; +import android.widget.EditText; +import android.widget.ImageView; +import android.widget.TextView; + +import com.bumptech.glide.Glide; +import com.bumptech.glide.request.RequestOptions; +import com.tencent.qcloud.tuikit.timcommon.R; +import com.tencent.qcloud.tuikit.timcommon.TIMCommonService; +import com.tencent.qcloud.tuikit.timcommon.util.ScreenUtil; +import com.tencent.qcloud.tuikit.timcommon.util.TIMCommonLog; +import com.tencent.qcloud.tuikit.timcommon.util.ThreadUtils; + +import java.util.ArrayList; +import java.util.LinkedHashMap; +import java.util.List; +import java.util.Map; +import java.util.concurrent.ConcurrentHashMap; +import java.util.concurrent.ExecutionException; +import java.util.regex.Matcher; +import java.util.regex.Pattern; + +public class FaceManager { + private static final String TAG = "FaceManager"; + + private static final class FaceManagerHolder { + @SuppressLint("StaticFieldLeak") + private static final FaceManager instance = new FaceManager(); + } + + public static final int EMOJI_GROUP_ID = 0; + private static final int EMOJI_SIZE = 20; + private static final int EMOJI_COLUMN_COUNT = 8; + private static final int EMOJI_ROW_COUNT = 3; + + private final Map emojiMap = new LinkedHashMap<>(); + private final Context context; + private final String[] emojiKeys; + private final String[] emojiNames; + + private final Map faceGroupMap = new ConcurrentHashMap<>(); + + /** + * Does the emojis need to be loaded + * 表情是否需要加载 + */ + private boolean needLoad = true; + + private FaceManager() { + context = TIMCommonService.getAppContext(); + emojiKeys = context.getResources().getStringArray(R.array.emoji_key); + emojiNames = context.getResources().getStringArray(R.array.emoji_name); + } + + public static FaceManager getInstance() { + return FaceManagerHolder.instance; + } + + public static ArrayList getEmojiList() { + return new ArrayList<>(getInstance().emojiMap.values()); + } + + /** + * add a new faceGroup + * @param groupID must >= 1 + * @param faceGroup the faceGroup be added + */ + public static void addFaceGroup(int groupID, FaceGroup faceGroup) { + faceGroup.setGroupID(groupID); + getInstance().faceGroupMap.put(groupID, faceGroup); + } + + public static List getFaceGroupList() { + return new ArrayList<>(getInstance().faceGroupMap.values()); + } + + public static String[] getEmojiNames(){ + return getInstance().emojiNames; + } + + public static String[] getEmojiKey(){ + return getInstance().emojiKeys; + } + + public static void loadEmojis() { + getInstance().internalLoadEmojis(); + } + + private synchronized void internalLoadEmojis() { + if (!needLoad) { + return; + } + needLoad = false; + TIMCommonLog.i(TAG, "start load emojis"); + Thread loadEmojiThread = new Thread() { + @Override + public void run() { + // load chat default emojis + FaceGroup emojiFaceGroup = new FaceGroup(); + for (String emojiKey : getInstance().emojiKeys) { + String emojiFilePath = "emoji/" + emojiKey + "@2x.png"; + Emoji emoji = loadAssetEmoji(emojiKey, emojiFilePath); + if (emoji != null) { + emojiMap.put(emojiKey, emoji); + emojiFaceGroup.addFace(emojiKey, emoji); + } + } + emojiFaceGroup.setPageColumnCount(EMOJI_COLUMN_COUNT); + emojiFaceGroup.setPageRowCount(EMOJI_ROW_COUNT); + emojiFaceGroup.setFaceGroupIconUrl("file:///android_asset/emoji/[可爱]@2x.png"); + addFaceGroup(EMOJI_GROUP_ID, emojiFaceGroup); + TIMCommonLog.i(TAG, "load emojis finished"); + } + }; + loadEmojiThread.setName("TUIChatLoadEmojiThread"); + ThreadUtils.execute(loadEmojiThread); + } + + private Emoji loadAssetEmoji(String emojiKey, String assetFilePath) { + String realPath = "file:///android_asset/" + assetFilePath; + int emojiSize = ScreenUtil.dip2px(EMOJI_SIZE); + Bitmap bitmap = loadBitmap(realPath, emojiSize, emojiSize); + if (bitmap == null) { + return null; + } + Emoji emoji = new Emoji(); + emoji.setIcon(bitmap); + emoji.setFaceKey(emojiKey); + return emoji; + } + + private Bitmap loadBitmap(String resUrl, int width, int height) { + Bitmap bitmap = null; + try { + bitmap = Glide.with(context) + .asBitmap() + .load(resUrl) + .apply(new RequestOptions().error(android.R.drawable.ic_menu_report_image)) + .submit(width, height) + .get(); + } catch (InterruptedException | ExecutionException e) { + TIMCommonLog.e(TAG, "load bitmap failed : " + e.getMessage()); + } + return bitmap; + } + + public static void loadFace(ChatFace chatFace, ImageView imageView) { + getInstance().internalLoadFace(chatFace, imageView, true); + } + + private void internalLoadFace(ChatFace chatFace, ImageView imageView, boolean isBitMap) { + if (imageView == null || chatFace == null) { + return; + } + if (chatFace instanceof Emoji) { + Glide.with(imageView.getContext()) + .load(((Emoji) chatFace).getIcon()) + .centerInside() + .apply(new RequestOptions().error(android.R.drawable.ic_menu_report_image)) + .into(imageView); + return; + } + String faceUrl = ""; + FaceGroup faceGroup = chatFace.getFaceGroup(); + if (faceGroup != null) { + ChatFace face = faceGroup.getFace(chatFace.getFaceKey()); + if (face != null) { + faceUrl = face.getFaceUrl(); + } + } + if (isBitMap) { + Glide.with(imageView.getContext()) + .asBitmap() + .load(faceUrl) + .centerInside() + .apply(new RequestOptions().error(android.R.drawable.ic_menu_report_image)) + .into(imageView); + } else { + Glide.with(imageView.getContext()) + .load(faceUrl) + .centerInside() + .apply(new RequestOptions().error(android.R.drawable.ic_menu_report_image)) + .into(imageView); + } + } + + public static void loadFace(int faceGroupID, String faceKey, ImageView view) { + getInstance().internalLoadFace(faceGroupID, faceKey, view); + } + + private void internalLoadFace(int faceGroupID, String faceKey, ImageView imageView) { + if (imageView == null) { + return; + } + if (TextUtils.isEmpty(faceKey)) { + Glide.with(imageView.getContext()) + .load(android.R.drawable.ic_menu_report_image) + .centerInside() + .into(imageView); + return; + } + String faceUrl = ""; + FaceGroup faceGroup = faceGroupMap.get(faceGroupID); + if (faceGroup != null) { + ChatFace face = faceGroup.getFace(faceKey); + if (face != null) { + faceUrl = face.getFaceUrl(); + } + } + Glide.with(imageView.getContext()) + .load(faceUrl) + .centerInside() + .apply(new RequestOptions().error(android.R.drawable.ic_menu_report_image)) + .into(imageView); + } + + public static Map getEmojiMap() { + return getInstance().emojiMap; + } + + public static boolean isFaceChar(String faceChar) { + return getEmojiMap().get(faceChar) != null; + } + + public static boolean handlerEmojiText(TextView comment, CharSequence content, boolean typing) { + if (content == null) { + comment.setText(null); + return false; + } + + Spannable spannable; + if (comment instanceof EditText && content instanceof Editable) { + spannable = (Editable) content; + ImageSpan[] imageSpans = ((Editable) content).getSpans(0, content.length(), ImageSpan.class); + for (ImageSpan span : imageSpans) { + ((Editable) content).removeSpan(span); + } + } else { + spannable = new SpannableStringBuilder(content); + } + String regex = "\\[(\\S+?)\\]"; + Pattern p = Pattern.compile(regex); + Matcher m = p.matcher(content); + boolean imageFound = false; + while (m.find()) { + String emojiName = m.group(); + Emoji emoji = getEmojiMap().get(emojiName); + if (emoji != null) { + Bitmap bitmap = emoji.getIcon(); + if (bitmap != null) { + imageFound = true; + + ImageSpan imageSpan = new ImageSpan(getInstance().context, bitmap); + spannable.setSpan(imageSpan, m.start(), m.end(), Spannable.SPAN_INCLUSIVE_EXCLUSIVE); + } + } + } + // 如果没有发现表情图片,并且当前是输入状态,不再重设输入框 + // If no emoticon picture is found, and it is currently in the input state, the input box will not be reset. + if (!imageFound && typing) { + return false; + } + int selection = comment.getSelectionStart(); + if (!(comment instanceof EditText)) { + comment.setText(spannable); + } + if (comment instanceof EditText) { + ((EditText) comment).setSelection(selection); + } + + return true; + } + + public static Bitmap getEmoji(String name) { + Emoji emoji = getEmojiMap().get(name); + if (emoji != null) { + return emoji.getIcon(); + } + return null; + } + + public static String emojiJudge(String text){ + if (TextUtils.isEmpty(text)){ + return ""; + } + + String[] emojiList = FaceManager.getEmojiKey(); + if (emojiList ==null || emojiList.length == 0){ + return text; + } + SpannableStringBuilder sb = new SpannableStringBuilder(text); + String regex = "\\[(\\S+?)\\]"; + Pattern p = Pattern.compile(regex); + Matcher m = p.matcher(text); + ArrayList emojiDataArrayList = new ArrayList<>(); + // 遍历找到匹配字符并存储 + // Traverse to find matching characters and store + int lastMentionIndex = -1; + while (m.find()) { + String emojiName = m.group(); + int start; + if (lastMentionIndex != -1) { + start = text.indexOf(emojiName, lastMentionIndex); + } else { + start = text.indexOf(emojiName); + } + int end = start + emojiName.length(); + lastMentionIndex = end; + + int index = findEmoji(emojiName); + String[] emojiListValues = FaceManager.getEmojiNames(); + if (index != -1 && emojiListValues != null && emojiListValues.length >= index){ + emojiName = emojiListValues[index]; + } + + EmojiData emojiData =new EmojiData(); + emojiData.setStart(start); + emojiData.setEnd(end); + emojiData.setEmojiText(emojiName); + + emojiDataArrayList.add(emojiData); + } + + // 倒叙替换 + // flashback replacement + if (emojiDataArrayList.isEmpty()){ + return text; + } + for (int i = emojiDataArrayList.size() - 1; i >= 0; i--){ + EmojiData emojiData = emojiDataArrayList.get(i); + String emojiName = emojiData.getEmojiText(); + int start = emojiData.getStart(); + int end = emojiData.getEnd(); + + if (!TextUtils.isEmpty(emojiName) && start != -1 && end != -1) { + sb.replace(start, end, emojiName); + } + } + return sb.toString(); + } + + public static List findEmojiKeyListFromText(String text) { + if (TextUtils.isEmpty(text)) { + return null; + } + List emojiKeyList = new ArrayList<>(); + // TUIKit custom emoji. + String regexOfCustomEmoji = "\\[(\\S+?)\\]"; + Pattern patternOfCustomEmoji = Pattern.compile(regexOfCustomEmoji); + Matcher matcherOfCustomEmoji = patternOfCustomEmoji.matcher(text); + while (matcherOfCustomEmoji.find()) { + String emojiName = matcherOfCustomEmoji.group(); + Emoji emoji = getEmojiMap().get(emojiName); + if (emoji != null) { + Bitmap bitmap = emoji.getIcon(); + if (bitmap != null) { + emojiKeyList.add(emojiName); + } + } + } + + // Universal standard emoji. + String regexOfUniversalEmoji = getRegexOfUniversalEmoji(); + Pattern patternOfUniversalEmoji = Pattern.compile(regexOfUniversalEmoji); + Matcher matcherOfUniversalEmoji = patternOfUniversalEmoji.matcher(text); + while (matcherOfUniversalEmoji.find()) { + String emojiKey = matcherOfUniversalEmoji.group(); + if (!TextUtils.isEmpty(emojiKey)) { + emojiKeyList.add(matcherOfUniversalEmoji.group()); + } + } + + return emojiKeyList; + } + + private static int findEmoji(String text){ + int result = -1; + if (TextUtils.isEmpty(text)){ + return result; + } + + String[] emojiList = FaceManager.getEmojiKey(); + if (emojiList == null || emojiList.length == 0){ + return result; + } + + for (int i = 0; i < emojiList.length; i++){ + if (text.equals(emojiList[i])){ + result = i; + break; + } + } + + return result; + } + + private static class EmojiData { + private int start; + private int end; + private String emojiText; + + public int getEnd() { + return end; + } + + public void setEnd(int end) { + this.end = end; + } + + public int getStart() { + return start; + } + + public void setStart(int start) { + this.start = start; + } + + public String getEmojiText() { + return emojiText; + } + + public void setEmojiText(String emojiText) { + this.emojiText = emojiText; + } + } + + // Regex of universal emoji, refer to https://unicode.org/reports/tr51/#EBNF_and_Regex + private static String getRegexOfUniversalEmoji() { + String RI = "[\\U0001F1E6-\\U0001F1FF]"; + // \u0023(#), \u002A(*), \u0030(keycap 0), \u0039(keycap 9), \u00A9(©), \u00AE(®) couldn't be added to NSString directly, need to transform a little bit. + String unsupport = "0x0023|0x002A|[0x0030-0x0039]|"; + String support = "\\U000000A9|\\U000000AE|\\u203C|\\u2049|\\u2122|\\u2139|[\\u2194-\\u2199]|[\\u21A9-\\u21AA]|[\\u231A-\\u231B]|\\u2328|\\u23CF|[\\u23E9-\\u23EF]|[\\u23F0-\\u23F3]|[\\u23F8-\\u23FA]|\\u24C2|[\\u25AA-\\u25AB]|\\u25B6|\\u25C0|[\\u25FB-\\u25FE]|[\\u2600-\\u2604]|\\u260E|\\u2611|[\\u2614-\\u2615]|\\u2618|\\u261D|\\u2620|[\\u2622-\\u2623]|\\u2626|\\u262A|[\\u262E-\\u262F]|[\\u2638-\\u263A]|\\u2640|\\u2642|[\\u2648-\\u264F]|[\\u2650-\\u2653]|\\u265F|\\u2660|\\u2663|[\\u2665-\\u2666]|\\u2668|\\u267B|[\\u267E-\\u267F]|[\\u2692-\\u2697]|\\u2699|[\\u269B-\\u269C]|[\\u26A0-\\u26A1]|\\u26A7|[\\u26AA-\\u26AB]|[\\u26B0-\\u26B1]|[\\u26BD-\\u26BE]|[\\u26C4-\\u26C5]|\\u26C8|[\\u26CE-\\u26CF]|\\u26D1|[\\u26D3-\\u26D4]|[\\u26E9-\\u26EA]|[\\u26F0-\\u26F5]|[\\u26F7-\\u26FA]|\\u26FD|\\u2702|\\u2705|[\\u2708-\\u270D]|\\u270F|\\u2712|\\u2714|\\u2716|\\u271D|\\u2721|\\u2728|[\\u2733-\\u2734]|\\u2744|\\u2747|\\u274C|\\u274E|[\\u2753-\\u2755]|\\u2757|[\\u2763-\\u2764]|[\\u2795-\\u2797]|\\u27A1|\\u27B0|\\u27BF|[\\u2934-\\u2935]|[\\u2B05-\\u2B07]|[\\u2B1B-\\u2B1C]|\\u2B50|\\u2B55|\\u3030|\\u303D|\\u3297|\\u3299|\\U0001F004|\\U0001F0CF|[\\U0001F170-\\U0001F171]|[\\U0001F17E-\\U0001F17F]|\\U0001F18E|[\\U0001F191-\\U0001F19A]|[\\U0001F1E6-\\U0001F1FF]|[\\U0001F201-\\U0001F202]|\\U0001F21A|\\U0001F22F|[\\U0001F232-\\U0001F23A]|[\\U0001F250-\\U0001F251]|[\\U0001F300-\\U0001F30F]|[\\U0001F310-\\U0001F31F]|[\\U0001F320-\\U0001F321]|[\\U0001F324-\\U0001F32F]|[\\U0001F330-\\U0001F33F]|[\\U0001F340-\\U0001F34F]|[\\U0001F350-\\U0001F35F]|[\\U0001F360-\\U0001F36F]|[\\U0001F370-\\U0001F37F]|[\\U0001F380-\\U0001F38F]|[\\U0001F390-\\U0001F393]|[\\U0001F396-\\U0001F397]|[\\U0001F399-\\U0001F39B]|[\\U0001F39E-\\U0001F39F]|[\\U0001F3A0-\\U0001F3AF]|[\\U0001F3B0-\\U0001F3BF]|[\\U0001F3C0-\\U0001F3CF]|[\\U0001F3D0-\\U0001F3DF]|[\\U0001F3E0-\\U0001F3EF]|\\U0001F3F0|[\\U0001F3F3-\\U0001F3F5]|[\\U0001F3F7-\\U0001F3FF]|[\\U0001F400-\\U0001F40F]|[\\U0001F410-\\U0001F41F]|[\\U0001F420-\\U0001F42F]|[\\U0001F430-\\U0001F43F]|[\\U0001F440-\\U0001F44F]|[\\U0001F450-\\U0001F45F]|[\\U0001F460-\\U0001F46F]|[\\U0001F470-\\U0001F47F]|[\\U0001F480-\\U0001F48F]|[\\U0001F490-\\U0001F49F]|[\\U0001F4A0-\\U0001F4AF]|[\\U0001F4B0-\\U0001F4BF]|[\\U0001F4C0-\\U0001F4CF]|[\\U0001F4D0-\\U0001F4DF]|[\\U0001F4E0-\\U0001F4EF]|[\\U0001F4F0-\\U0001F4FF]|[\\U0001F500-\\U0001F50F]|[\\U0001F510-\\U0001F51F]|[\\U0001F520-\\U0001F52F]|[\\U0001F530-\\U0001F53D]|[\\U0001F549-\\U0001F54E]|[\\U0001F550-\\U0001F55F]|[\\U0001F560-\\U0001F567]|\\U0001F56F|\\U0001F570|[\\U0001F573-\\U0001F57A]|\\U0001F587|[\\U0001F58A-\\U0001F58D]|\\U0001F590|[\\U0001F595-\\U0001F596]|[\\U0001F5A4-\\U0001F5A5]|\\U0001F5A8|[\\U0001F5B1-\\U0001F5B2]|\\U0001F5BC|[\\U0001F5C2-\\U0001F5C4]|[\\U0001F5D1-\\U0001F5D3]|[\\U0001F5DC-\\U0001F5DE]|\\U0001F5E1|\\U0001F5E3|\\U0001F5E8|\\U0001F5EF|\\U0001F5F3|[\\U0001F5FA-\\U0001F5FF]|[\\U0001F600-\\U0001F60F]|[\\U0001F610-\\U0001F61F]|[\\U0001F620-\\U0001F62F]|[\\U0001F630-\\U0001F63F]|[\\U0001F640-\\U0001F64F]|[\\U0001F650-\\U0001F65F]|[\\U0001F660-\\U0001F66F]|[\\U0001F670-\\U0001F67F]|[\\U0001F680-\\U0001F68F]|[\\U0001F690-\\U0001F69F]|[\\U0001F6A0-\\U0001F6AF]|[\\U0001F6B0-\\U0001F6BF]|[\\U0001F6C0-\\U0001F6C5]|[\\U0001F6CB-\\U0001F6CF]|[\\U0001F6D0-\\U0001F6D2]|[\\U0001F6D5-\\U0001F6D7]|[\\U0001F6DD-\\U0001F6DF]|[\\U0001F6E0-\\U0001F6E5]|\\U0001F6E9|[\\U0001F6EB-\\U0001F6EC]|\\U0001F6F0|[\\U0001F6F3-\\U0001F6FC]|[\\U0001F7E0-\\U0001F7EB]|\\U0001F7F0|[\\U0001F90C-\\U0001F90F]|[\\U0001F910-\\U0001F91F]|[\\U0001F920-\\U0001F92F]|[\\U0001F930-\\U0001F93A]|[\\U0001F93C-\\U0001F93F]|[\\U0001F940-\\U0001F945]|[\\U0001F947-\\U0001F94C]|[\\U0001F94D-\\U0001F94F]|[\\U0001F950-\\U0001F95F]|[\\U0001F960-\\U0001F96F]|[\\U0001F970-\\U0001F97F]|[\\U0001F980-\\U0001F98F]|[\\U0001F990-\\U0001F99F]|[\\U0001F9A0-\\U0001F9AF]|[\\U0001F9B0-\\U0001F9BF]|[\\U0001F9C0-\\U0001F9CF]|[\\U0001F9D0-\\U0001F9DF]|[\\U0001F9E0-\\U0001F9EF]|[\\U0001F9F0-\\U0001F9FF]|[\\U0001FA70-\\U0001FA74]|[\\U0001FA78-\\U0001FA7C]|[\\U0001FA80-\\U0001FA86]|[\\U0001FA90-\\U0001FA9F]|[\\U0001FAA0-\\U0001FAAC]|[\\U0001FAB0-\\U0001FABA]|[\\U0001FAC0-\\U0001FAC5]|[\\U0001FAD0-\\U0001FAD9]|[\\U0001FAE0-\\U0001FAE7]|[\\U0001FAF0-\\U0001FAF6]"; + String Emoji = unsupport + support; + + // Construct regex of emoji by the rules above. + String EMod = "[\\U0001F3FB-\\U0001F3FF]"; + + String variationSelector = "\\uFE0F"; + String keycap = "\\u20E3"; + String tags = "[\\U000E0020-\\U000E007E]"; + String termTag = "\\U000E007F"; + String zwj = "\\u200D"; + + String RISequence = "[" + RI + "]" + "[" + RI + "]"; + String element = "[" + Emoji + "]" + "(" + "[" + EMod + "]|" + variationSelector + keycap + "?|[" + tags + "]+" + termTag + "?)?"; + String regexEmoji = RISequence + "|" + element + "(" + zwj + "(" + RISequence + "|" + element + "))*"; + + return regexEmoji; + } +} diff --git a/TIMCommon/timcommon/src/main/java/com/tencent/qcloud/tuikit/timcommon/component/face/RecentEmojiManager.java b/TIMCommon/timcommon/src/main/java/com/tencent/qcloud/tuikit/timcommon/component/face/RecentEmojiManager.java new file mode 100644 index 0000000..370b4d5 --- /dev/null +++ b/TIMCommon/timcommon/src/main/java/com/tencent/qcloud/tuikit/timcommon/component/face/RecentEmojiManager.java @@ -0,0 +1,58 @@ +package com.tencent.qcloud.tuikit.timcommon.component.face; + +import android.text.TextUtils; +import android.util.Base64; + +import com.tencent.qcloud.tuicore.util.SPUtils; + +import java.io.ByteArrayInputStream; +import java.io.ByteArrayOutputStream; +import java.io.IOException; +import java.io.ObjectInputStream; +import java.io.ObjectOutputStream; +import java.util.Collection; + + +public class RecentEmojiManager { + public static final String PREFERENCE_NAME = "recentFace";//"preference"; + + private static final RecentEmojiManager instance = new RecentEmojiManager(); + + private RecentEmojiManager() {} + + public static RecentEmojiManager getInstance() { + return instance; + } + + public String getString(String key) { + return SPUtils.getInstance(PREFERENCE_NAME).getString(key); + } + + public RecentEmojiManager putString(String key, String value) { + SPUtils.getInstance(PREFERENCE_NAME).put(key, value); + return this; + } + + + public RecentEmojiManager putCollection(String key, Collection collection) throws IOException { + ByteArrayOutputStream byteArrayOutputStream = new ByteArrayOutputStream(); + ObjectOutputStream objectOutputStream = new ObjectOutputStream(byteArrayOutputStream); + objectOutputStream.writeObject(collection); + String collectionString = new String(Base64.encode(byteArrayOutputStream.toByteArray(), Base64.DEFAULT)); + objectOutputStream.close(); + return putString(key, collectionString); + } + + public Collection getCollection(String key) throws IOException, ClassNotFoundException { + String collectionString = getString(key); + if (TextUtils.isEmpty(collectionString) || TextUtils.isEmpty(collectionString.trim())) { + return null; + } + byte[] mobileBytes = Base64.decode(collectionString.getBytes(), Base64.DEFAULT); + ByteArrayInputStream byteArrayInputStream = new ByteArrayInputStream(mobileBytes); + ObjectInputStream objectInputStream = new ObjectInputStream(byteArrayInputStream); + Collection collection = (Collection) objectInputStream.readObject(); + objectInputStream.close(); + return collection; + } +} diff --git a/TIMCommon/timcommon/src/main/java/com/tencent/qcloud/tuikit/timcommon/component/fragments/BaseFragment.java b/TIMCommon/timcommon/src/main/java/com/tencent/qcloud/tuikit/timcommon/component/fragments/BaseFragment.java new file mode 100644 index 0000000..505f594 --- /dev/null +++ b/TIMCommon/timcommon/src/main/java/com/tencent/qcloud/tuikit/timcommon/component/fragments/BaseFragment.java @@ -0,0 +1,34 @@ +package com.tencent.qcloud.tuikit.timcommon.component.fragments; + +import androidx.fragment.app.Fragment; +import androidx.fragment.app.FragmentTransaction; + +public class BaseFragment extends Fragment { + + public void forward(Fragment fragment, boolean hide) { + forward(getId(), fragment, null, hide); + } + + public void forward(int viewId, Fragment fragment, String name, boolean hide) { + if (getFragmentManager() == null){ + return; + } + FragmentTransaction trans = getFragmentManager().beginTransaction(); + if (hide) { + trans.hide(this); + trans.add(viewId, fragment); + } else { + trans.replace(viewId, fragment); + } + + trans.addToBackStack(name); + trans.commitAllowingStateLoss(); + } + + public void backward() { + if (getFragmentManager() == null){ + return; + } + getFragmentManager().popBackStack(); + } +} diff --git a/TIMCommon/timcommon/src/main/java/com/tencent/qcloud/tuikit/timcommon/component/gatherimage/MultiImageData.java b/TIMCommon/timcommon/src/main/java/com/tencent/qcloud/tuikit/timcommon/component/gatherimage/MultiImageData.java new file mode 100644 index 0000000..e63624c --- /dev/null +++ b/TIMCommon/timcommon/src/main/java/com/tencent/qcloud/tuikit/timcommon/component/gatherimage/MultiImageData.java @@ -0,0 +1,100 @@ +package com.tencent.qcloud.tuikit.timcommon.component.gatherimage; + +import android.graphics.Bitmap; +import android.graphics.Color; + +import java.util.ArrayList; +import java.util.HashMap; +import java.util.List; +import java.util.Map; + +/** + * Multiple image data + */ + +public class MultiImageData implements Cloneable{ + final static int maxSize = 9; + List imageUrls; + int defaultImageResId; + Map bitmapMap; + int bgColor = Color.parseColor("#cfd3d8"); + + + int targetImageSize; + int maxWidth, maxHeight; + int rowCount; + int columnCount; + int gap = 6; + + public MultiImageData() { + } + + public MultiImageData(int defaultImageResId) { + this.defaultImageResId = defaultImageResId; + } + + public MultiImageData(List imageUrls, int defaultImageResId) { + this.imageUrls = imageUrls; + this.defaultImageResId = defaultImageResId; + } + + public int getDefaultImageResId() { + return defaultImageResId; + } + + public void setDefaultImageResId(int defaultImageResId) { + this.defaultImageResId = defaultImageResId; + } + + public List getImageUrls() { + return imageUrls; + } + + public void setImageUrls(List imageUrls) { + this.imageUrls = imageUrls; + } + + public void putBitmap(Bitmap bitmap, int position) { + if (null != bitmapMap) { + synchronized (bitmapMap) { + bitmapMap.put(position, bitmap); + } + } else { + bitmapMap = new HashMap<>(); + synchronized (bitmapMap) { + bitmapMap.put(position, bitmap); + } + } + } + + public Bitmap getBitmap(int position) { + if (null != bitmapMap) { + synchronized (bitmapMap) { + return bitmapMap.get(position); + } + } + return null; + } + + public int size() { + if (null != imageUrls) { + return imageUrls.size() > maxSize ? maxSize : imageUrls.size(); + } else { + return 0; + } + } + + @Override + protected MultiImageData clone() throws CloneNotSupportedException { + MultiImageData multiImageData = (MultiImageData) super.clone(); + if (imageUrls != null) { + multiImageData.imageUrls = new ArrayList<>(imageUrls.size()); + multiImageData.imageUrls.addAll(imageUrls); + } + if (bitmapMap != null) { + multiImageData.bitmapMap = new HashMap<>(); + multiImageData.bitmapMap.putAll(bitmapMap); + } + return multiImageData; + } +} diff --git a/TIMCommon/timcommon/src/main/java/com/tencent/qcloud/tuikit/timcommon/component/gatherimage/ShadeImageView.java b/TIMCommon/timcommon/src/main/java/com/tencent/qcloud/tuikit/timcommon/component/gatherimage/ShadeImageView.java new file mode 100644 index 0000000..670ab2b --- /dev/null +++ b/TIMCommon/timcommon/src/main/java/com/tencent/qcloud/tuikit/timcommon/component/gatherimage/ShadeImageView.java @@ -0,0 +1,95 @@ +package com.tencent.qcloud.tuikit.timcommon.component.gatherimage; + +import android.annotation.SuppressLint; +import android.content.Context; +import android.content.res.TypedArray; +import android.graphics.Bitmap; +import android.graphics.Canvas; +import android.graphics.Color; +import android.graphics.Paint; +import android.graphics.PorterDuff; +import android.graphics.PorterDuffXfermode; +import android.graphics.Rect; +import android.graphics.RectF; +import android.util.AttributeSet; +import android.util.SparseArray; +import android.widget.ImageView; + +import androidx.annotation.Nullable; + +import com.tencent.qcloud.tuikit.timcommon.R; +import com.tencent.qcloud.tuikit.timcommon.util.ScreenUtil; + +@SuppressLint("AppCompatCustomView") +public class ShadeImageView extends ImageView { + + private static SparseArray sRoundBitmapArray = new SparseArray(); + private Paint mShadePaint = new Paint(); + private Bitmap mRoundBitmap; + private int radius; + + public ShadeImageView(Context context) { + super(context); + } + + public ShadeImageView(Context context, @Nullable AttributeSet attrs) { + super(context, attrs); + init(context, attrs); + } + + public ShadeImageView(Context context, @Nullable AttributeSet attrs, int defStyleAttr) { + super(context, attrs, defStyleAttr); + init(context, attrs); + } + + private void init(Context context, AttributeSet attrs) { + radius = (int) ScreenUtil.dp2px(4.0f, getResources().getDisplayMetrics()); + TypedArray array = context.obtainStyledAttributes(attrs, R.styleable.core_round_rect_image_style); + radius = array.getDimensionPixelSize(R.styleable.core_round_rect_image_style_round_radius, radius); + array.recycle(); + setLayerType(LAYER_TYPE_HARDWARE, null); + } + + @Override + protected void onDraw(Canvas canvas) { + super.onDraw(canvas); + mShadePaint.setColor(Color.RED); + mShadePaint.setXfermode(new PorterDuffXfermode(PorterDuff.Mode.DST_IN)); + mRoundBitmap = sRoundBitmapArray.get(getMeasuredWidth() + radius); + if (mRoundBitmap == null) { + mRoundBitmap = getRoundBitmap(); + sRoundBitmapArray.put(getMeasuredWidth() + radius, mRoundBitmap); + } + canvas.drawBitmap(mRoundBitmap, 0, 0, mShadePaint); + } + + /** + * Get rounded rectangle + * + * @return Bitmap + */ + private Bitmap getRoundBitmap() { + Bitmap output = Bitmap.createBitmap(getWidth(), getHeight(), Bitmap.Config.ARGB_8888); + Canvas canvas = new Canvas(output); + final int color = Color.parseColor("#cfd3d8"); + final Rect rect = new Rect(0, 0, getMeasuredWidth(), getMeasuredHeight()); + final RectF rectF = new RectF(rect); + Paint paint = new Paint(); + paint.setAntiAlias(true); + canvas.drawARGB(0, 0, 0, 0); + paint.setColor(color); + canvas.drawRoundRect(rectF, radius, radius, paint); + return output; + } + + + public int getRadius() { + return radius; + } + + public void setRadius(int radius) { + this.radius = radius; + } + + +} diff --git a/TIMCommon/timcommon/src/main/java/com/tencent/qcloud/tuikit/timcommon/component/gatherimage/SynthesizedImageView.java b/TIMCommon/timcommon/src/main/java/com/tencent/qcloud/tuikit/timcommon/component/gatherimage/SynthesizedImageView.java new file mode 100644 index 0000000..4acee4b --- /dev/null +++ b/TIMCommon/timcommon/src/main/java/com/tencent/qcloud/tuikit/timcommon/component/gatherimage/SynthesizedImageView.java @@ -0,0 +1,90 @@ +package com.tencent.qcloud.tuikit.timcommon.component.gatherimage; + +import android.content.Context; +import android.content.res.TypedArray; +import android.graphics.Color; +import android.util.AttributeSet; + +import androidx.annotation.Nullable; + +import com.tencent.qcloud.tuikit.timcommon.R; + +import java.util.List; + + +public class SynthesizedImageView extends ShadeImageView { + /** + * 群聊头像合成器 + * + * Group Chat Avatar Synthesizer + */ + TeamHeadSynthesizer teamHeadSynthesizer; + int imageSize = 100; + int synthesizedBg = Color.parseColor("#cfd3d8"); + int defaultImageResId = R.mipmap.core_default_user_icon_lively; + int imageGap = 6; + + public SynthesizedImageView(Context context) { + super(context); + init(context); + } + + public SynthesizedImageView(Context context, @Nullable AttributeSet attrs) { + super(context, attrs); + initAttrs(attrs); + init(context); + } + + public SynthesizedImageView(Context context, @Nullable AttributeSet attrs, int defStyleAttr) { + super(context, attrs, defStyleAttr); + initAttrs(attrs); + init(context); + } + + private void initAttrs(AttributeSet attributeSet) { + TypedArray ta = getContext().obtainStyledAttributes(attributeSet, R.styleable.SynthesizedImageView); + if (null != ta) { + synthesizedBg = ta.getColor(R.styleable.SynthesizedImageView_synthesized_image_bg, synthesizedBg); +// defaultImageResId = ta.getResourceId(R.styleable.SynthesizedImageView_synthesized_default_image, defaultImageResId); + imageSize = ta.getDimensionPixelSize(R.styleable.SynthesizedImageView_synthesized_image_size, imageSize); + imageGap = ta.getDimensionPixelSize(R.styleable.SynthesizedImageView_synthesized_image_gap, imageGap); + ta.recycle(); + } + } + + private void init(Context context) { + teamHeadSynthesizer = new TeamHeadSynthesizer(context, this); + teamHeadSynthesizer.setMaxWidthHeight(imageSize, imageSize); + teamHeadSynthesizer.setDefaultImage(defaultImageResId); + teamHeadSynthesizer.setBgColor(synthesizedBg); + teamHeadSynthesizer.setGap(imageGap); + } + + public SynthesizedImageView displayImage(List imageUrls) { + teamHeadSynthesizer.getMultiImageData().setImageUrls(imageUrls); + return this; + } + + public SynthesizedImageView defaultImage(int defaultImage) { + teamHeadSynthesizer.setDefaultImage(defaultImage); + return this; + } + + public SynthesizedImageView synthesizedWidthHeight(int maxWidth, int maxHeight) { + teamHeadSynthesizer.setMaxWidthHeight(maxWidth, maxHeight); + return this; + } + + public void setImageId(String id) { + teamHeadSynthesizer.setImageId(id); + } + + public void load(String imageId) { + teamHeadSynthesizer.load(imageId); + } + + public void clear() { + teamHeadSynthesizer.clearImage(); + } + +} diff --git a/TIMCommon/timcommon/src/main/java/com/tencent/qcloud/tuikit/timcommon/component/gatherimage/Synthesizer.java b/TIMCommon/timcommon/src/main/java/com/tencent/qcloud/tuikit/timcommon/component/gatherimage/Synthesizer.java new file mode 100644 index 0000000..ce2e559 --- /dev/null +++ b/TIMCommon/timcommon/src/main/java/com/tencent/qcloud/tuikit/timcommon/component/gatherimage/Synthesizer.java @@ -0,0 +1,14 @@ +package com.tencent.qcloud.tuikit.timcommon.component.gatherimage; + +import android.graphics.Bitmap; +import android.graphics.Canvas; + + +public interface Synthesizer { + + Bitmap synthesizeImageList(MultiImageData imageData); + + boolean asyncLoadImageList(MultiImageData imageData); + + void drawDrawable(Canvas canvas, MultiImageData imageData); +} diff --git a/TIMCommon/timcommon/src/main/java/com/tencent/qcloud/tuikit/timcommon/component/gatherimage/TeamHeadSynthesizer.java b/TIMCommon/timcommon/src/main/java/com/tencent/qcloud/tuikit/timcommon/component/gatherimage/TeamHeadSynthesizer.java new file mode 100644 index 0000000..8a20a47 --- /dev/null +++ b/TIMCommon/timcommon/src/main/java/com/tencent/qcloud/tuikit/timcommon/component/gatherimage/TeamHeadSynthesizer.java @@ -0,0 +1,330 @@ +package com.tencent.qcloud.tuikit.timcommon.component.gatherimage; + +import android.content.Context; +import android.graphics.Bitmap; +import android.graphics.BitmapFactory; +import android.graphics.Canvas; +import android.graphics.Rect; +import android.text.TextUtils; +import android.widget.ImageView; + +import com.tencent.qcloud.tuicore.TUIConfig; +import com.tencent.qcloud.tuikit.timcommon.TIMCommonConfig; +import com.tencent.qcloud.tuikit.timcommon.component.impl.GlideEngine; +import com.tencent.qcloud.tuikit.timcommon.util.ImageUtil; +import com.tencent.qcloud.tuikit.timcommon.util.ThreadUtils; + +import java.io.File; +import java.util.ArrayList; +import java.util.List; +import java.util.concurrent.ExecutionException; + + +public class TeamHeadSynthesizer implements Synthesizer { + + MultiImageData multiImageData; + Context mContext; + + ImageView imageView; + + // It is safe to set and get only in the main thread + private String currentImageId = ""; + Callback callback = new Callback() { + @Override + public void onCall(Bitmap bitmap, String targetID) { + if (!TextUtils.equals(getImageId(), targetID)) { + return; + } + GlideEngine.loadUserIcon(imageView, bitmap); + } + }; + + + public TeamHeadSynthesizer(Context mContext, ImageView imageView) { + this.mContext = mContext; + this.imageView = imageView; + init(); + } + + private void init() { + multiImageData = new MultiImageData(); + } + + public void setMaxWidthHeight(int maxWidth, int maxHeight) { + multiImageData.maxWidth = maxWidth; + multiImageData.maxHeight = maxHeight; + } + + public MultiImageData getMultiImageData() { + return multiImageData; + } + + public int getDefaultImage() { + return multiImageData.getDefaultImageResId(); + } + + public void setDefaultImage(int defaultImageResId) { + multiImageData.setDefaultImageResId(defaultImageResId); + } + + public void setBgColor(int bgColor) { + multiImageData.bgColor = bgColor; + } + + public void setGap(int gap) { + multiImageData.gap = gap; + } + + /** + * Set Grid params + * + * @param imagesSize Number of pictures + * @return gridParam[0] Rows gridParam[1] columns + */ + protected int[] calculateGridParam(int imagesSize) { + int[] gridParam = new int[2]; + if (imagesSize < 3) { + gridParam[0] = 1; + gridParam[1] = imagesSize; + } else if (imagesSize <= 4) { + gridParam[0] = 2; + gridParam[1] = 2; + } else { + gridParam[0] = imagesSize / 3 + (imagesSize % 3 == 0 ? 0 : 1); + gridParam[1] = 3; + } + return gridParam; + } + + @Override + public Bitmap synthesizeImageList(MultiImageData imageData) { + Bitmap mergeBitmap = Bitmap.createBitmap(imageData.maxWidth, imageData.maxHeight, Bitmap.Config.ARGB_8888); + Canvas canvas = new Canvas(mergeBitmap); + drawDrawable(canvas, imageData); + canvas.save(); + canvas.restore(); + return mergeBitmap; + } + + @Override + public boolean asyncLoadImageList(MultiImageData imageData) { + boolean loadSuccess = true; + List imageUrls = imageData.getImageUrls(); + for (int i = 0; i < imageUrls.size(); i++) { + Bitmap defaultIcon = BitmapFactory.decodeResource(mContext.getResources(), TIMCommonConfig.getDefaultAvatarImage()); + try { + Bitmap bitmap = asyncLoadImage(imageUrls.get(i), imageData.targetImageSize); + imageData.putBitmap(bitmap, i); + } catch (InterruptedException e) { + e.printStackTrace(); + imageData.putBitmap(defaultIcon, i); + } catch (ExecutionException e) { + e.printStackTrace(); + imageData.putBitmap(defaultIcon, i); + } + } + return loadSuccess; + } + + @Override + public void drawDrawable(Canvas canvas, MultiImageData imageData) { + canvas.drawColor(imageData.bgColor); + int size = imageData.size(); + int t_center = (imageData.maxHeight + imageData.gap) / 2; + int b_center = (imageData.maxHeight - imageData.gap) / 2; + int l_center = (imageData.maxWidth + imageData.gap) / 2; + int r_center = (imageData.maxWidth - imageData.gap) / 2; + int center = (imageData.maxHeight - imageData.targetImageSize) / 2; + for (int i = 0; i < size; i++) { + int rowNum = i / imageData.columnCount; + int columnNum = i % imageData.columnCount; + + int left = ((int) (imageData.targetImageSize * (imageData.columnCount == 1 ? columnNum + 0.5 : columnNum) + imageData.gap * (columnNum + 1))); + int top = ((int) (imageData.targetImageSize * (imageData.columnCount == 1 ? rowNum + 0.5 : rowNum) + imageData.gap * (rowNum + 1))); + int right = left + imageData.targetImageSize; + int bottom = top + imageData.targetImageSize; + + Bitmap bitmap = imageData.getBitmap(i); + if (size == 1) { + drawBitmapAtPosition(canvas, left, top, right, bottom, bitmap); + } else if (size == 2) { + drawBitmapAtPosition(canvas, left, center, right, center + imageData.targetImageSize, bitmap); + } else if (size == 3) { + if (i == 0) { + drawBitmapAtPosition(canvas, center, top, center + imageData.targetImageSize, bottom, bitmap); + } else { + drawBitmapAtPosition(canvas, imageData.gap * i + imageData.targetImageSize * (i - 1), t_center, imageData.gap * i + imageData.targetImageSize * i, t_center + imageData.targetImageSize, bitmap); + } + } else if (size == 4) { + drawBitmapAtPosition(canvas, left, top, right, bottom, bitmap); + } else if (size == 5) { + if (i == 0) { + drawBitmapAtPosition(canvas, r_center - imageData.targetImageSize, r_center - imageData.targetImageSize, r_center, r_center, bitmap); + } else if (i == 1) { + drawBitmapAtPosition(canvas, l_center, r_center - imageData.targetImageSize, l_center + imageData.targetImageSize, r_center, bitmap); + } else { + drawBitmapAtPosition(canvas, imageData.gap * (i - 1) + imageData.targetImageSize * (i - 2), t_center, imageData.gap * (i - 1) + imageData.targetImageSize * (i - 1), t_center + + imageData.targetImageSize, bitmap); + } + } else if (size == 6) { + if (i < 3) { + drawBitmapAtPosition(canvas, imageData.gap * (i + 1) + imageData.targetImageSize * i, b_center - imageData.targetImageSize, imageData.gap * (i + 1) + imageData.targetImageSize * (i + 1), b_center, bitmap); + } else { + drawBitmapAtPosition(canvas, imageData.gap * (i - 2) + imageData.targetImageSize * (i - 3), t_center, imageData.gap * (i - 2) + imageData.targetImageSize * (i - 2), t_center + + imageData.targetImageSize, bitmap); + } + } else if (size == 7) { + if (i == 0) { + drawBitmapAtPosition(canvas, center, imageData.gap, center + imageData.targetImageSize, imageData.gap + imageData.targetImageSize, bitmap); + } else if (i > 0 && i < 4) { + drawBitmapAtPosition(canvas, imageData.gap * i + imageData.targetImageSize * (i - 1), center, imageData.gap * i + imageData.targetImageSize * i, center + imageData.targetImageSize, bitmap); + } else { + drawBitmapAtPosition(canvas, imageData.gap * (i - 3) + imageData.targetImageSize * (i - 4), t_center + imageData.targetImageSize / 2, imageData.gap * (i - 3) + imageData.targetImageSize * (i - 3), t_center + imageData.targetImageSize / 2 + imageData.targetImageSize, bitmap); + } + } else if (size == 8) { + if (i == 0) { + drawBitmapAtPosition(canvas, r_center - imageData.targetImageSize, imageData.gap, r_center, imageData.gap + imageData.targetImageSize, bitmap); + } else if (i == 1) { + drawBitmapAtPosition(canvas, l_center, imageData.gap, l_center + imageData.targetImageSize, imageData.gap + imageData.targetImageSize, bitmap); + } else if (i > 1 && i < 5) { + drawBitmapAtPosition(canvas, imageData.gap * (i - 1) + imageData.targetImageSize * (i - 2), center, imageData.gap * (i - 1) + imageData.targetImageSize * (i - 1), center + imageData.targetImageSize, bitmap); + } else { + drawBitmapAtPosition(canvas, imageData.gap * (i - 4) + imageData.targetImageSize * (i - 5), t_center + imageData.targetImageSize / 2, imageData.gap * (i - 4) + imageData.targetImageSize * (i - 4), t_center + imageData.targetImageSize / 2 + imageData.targetImageSize, bitmap); + } + } else if (size == 9) { + drawBitmapAtPosition(canvas, left, top, right, bottom, bitmap); + } + } + } + + /** + * DrawBitmap + * + * @param canvas + * @param left + * @param top + * @param right + * @param bottom + * @param bitmap + */ + public void drawBitmapAtPosition(Canvas canvas, int left, int top, int right, int bottom, Bitmap bitmap) { + if (null == bitmap) { + if (multiImageData.getDefaultImageResId() > 0) { + bitmap = BitmapFactory.decodeResource(mContext.getResources(), multiImageData.getDefaultImageResId()); + } + } + if (null != bitmap) { + Rect rect = new Rect(left, top, right, bottom); + canvas.drawBitmap(bitmap, null, rect, null); + } + } + + private Bitmap asyncLoadImage(Object imageUrl, int targetImageSize) throws ExecutionException, InterruptedException { + return GlideEngine.loadBitmap(imageUrl, targetImageSize); + } + + public void setImageId(String id) { + currentImageId = id; + } + + public String getImageId() { + return currentImageId; + } + + public void load(String imageId) { + if (multiImageData.size() == 0) { + // 发起请求时的图片 id 和当前图片 id 不一致,说明发生了复用,此时不应该再设置图像 + // The image id when the request is initiated is inconsistent with the current image id, + // indicating that multiplexing has occurred, and the image should not be set at this time. + if (imageId != null && !TextUtils.equals(imageId, currentImageId)) { + return; + } + GlideEngine.loadUserIcon(imageView, getDefaultImage()); + return; + } + + if (multiImageData.size() == 1) { + // 发起请求时的图片 id 和当前图片 id 不一致,说明发生了复用,此时不应该再设置图像 + // The image id when the request is initiated is inconsistent with the current image id, + // indicating that multiplexing has occurred, and the image should not be set at this time. + if (imageId != null && !TextUtils.equals(imageId, currentImageId)) { + return; + } + GlideEngine.loadUserIcon(imageView, multiImageData.getImageUrls().get(0)); + return; + } + + // 异步加载图像前先清空内容,避免闪烁 + // Clear the content before loading images asynchronously to avoid flickering + clearImage(); + + // 初始化图片信息,由于是异步加载和合成头像,这里需要传给合成线程一个局部对象,只在异步加载线程中使用 + // 这样在图片被复用时外部线程再次设置 url 就不会覆盖此局部对象 + // Initialize the image information. Since it is asynchronous loading and synthesizing the avatar, + // a local object needs to be passed to the synthesis thread, which is only used in the asynchronous + // loading thread, so that when the image is reused, the external thread will not overwrite the local + // object by setting the url again. + MultiImageData copyMultiImageData; + try { + copyMultiImageData = multiImageData.clone(); + } catch (CloneNotSupportedException e) { + e.printStackTrace(); + List urlList = new ArrayList(); + if (multiImageData.imageUrls != null) { + urlList.addAll(multiImageData.imageUrls); + } + copyMultiImageData = new MultiImageData(urlList, multiImageData.defaultImageResId); + } + int[] gridParam = calculateGridParam(multiImageData.size()); + copyMultiImageData.rowCount = gridParam[0]; + copyMultiImageData.columnCount = gridParam[1]; + copyMultiImageData.targetImageSize = (copyMultiImageData.maxWidth - (copyMultiImageData.columnCount + 1) + * copyMultiImageData.gap) / (copyMultiImageData.columnCount == 1 ? 2 : copyMultiImageData.columnCount); + final String finalImageId = imageId; + final MultiImageData finalCopyMultiImageData = copyMultiImageData; + ThreadUtils.execute(new Runnable() { + @Override + public void run() { + final File file = new File(TUIConfig.getImageBaseDir() + finalImageId); + boolean cacheBitmapExists = false; + Bitmap existsBitmap = null; + if (file.exists() && file.isFile()) { + BitmapFactory.Options options = new BitmapFactory.Options(); + existsBitmap = BitmapFactory.decodeFile(file.getPath(), options); + if (options.outWidth > 0 && options.outHeight > 0) { + cacheBitmapExists = true; + } + } + if (!cacheBitmapExists) { + asyncLoadImageList(finalCopyMultiImageData); + final Bitmap bitmap = synthesizeImageList(finalCopyMultiImageData); + ImageUtil.storeBitmap(file, bitmap); + ImageUtil.setGroupConversationAvatar(finalImageId, file.getAbsolutePath()); + ThreadUtils.postOnUiThread(new Runnable() { + @Override + public void run() { + callback.onCall(bitmap, finalImageId); + } + }); + } else { + final Bitmap finalExistsBitmap = existsBitmap; + ThreadUtils.postOnUiThread(new Runnable() { + @Override + public void run() { + callback.onCall(finalExistsBitmap, finalImageId); + } + }); + } + } + }); + } + + public void clearImage() { + GlideEngine.clear(imageView); + } + + interface Callback { + void onCall(Bitmap bitmap, String targetID); + } + +} diff --git a/TIMCommon/timcommon/src/main/java/com/tencent/qcloud/tuikit/timcommon/component/gatherimage/UserIconView.java b/TIMCommon/timcommon/src/main/java/com/tencent/qcloud/tuikit/timcommon/component/gatherimage/UserIconView.java new file mode 100644 index 0000000..996328a --- /dev/null +++ b/TIMCommon/timcommon/src/main/java/com/tencent/qcloud/tuikit/timcommon/component/gatherimage/UserIconView.java @@ -0,0 +1,67 @@ +package com.tencent.qcloud.tuikit.timcommon.component.gatherimage; + +import android.content.Context; +import android.content.res.TypedArray; +import android.util.AttributeSet; +import android.widget.RelativeLayout; + +import com.tencent.qcloud.tuikit.timcommon.R; + +import java.util.List; + +public class UserIconView extends RelativeLayout { + + private SynthesizedImageView mIconView; + private int mDefaultImageResId; + private int mIconRadius; + + public UserIconView(Context context) { + super(context); + init(null); + } + + public UserIconView(Context context, AttributeSet attrs) { + super(context, attrs); + init(attrs); + } + + public UserIconView(Context context, AttributeSet attrs, int defStyleAttr) { + super(context, attrs, defStyleAttr); + init(attrs); + } + + private void init(AttributeSet attributeSet) { + inflate(getContext(), R.layout.profile_icon_view, this); + if (attributeSet != null) { + TypedArray ta = getContext().obtainStyledAttributes(attributeSet, R.styleable.UserIconView); + if (null != ta) { + mDefaultImageResId = ta.getResourceId(R.styleable.UserIconView_default_image, mDefaultImageResId); + mIconRadius = ta.getDimensionPixelSize(R.styleable.UserIconView_image_radius, mIconRadius); + ta.recycle(); + } + } + + mIconView = findViewById(R.id.profile_icon); + if (mDefaultImageResId > 0) { + mIconView.defaultImage(mDefaultImageResId); + } + if (mIconRadius > 0) { + mIconView.setRadius(mIconRadius); + } + } + + public void setDefaultImageResId(int resId) { + mDefaultImageResId = resId; + mIconView.defaultImage(resId); + } + + public void setRadius(int radius) { + mIconRadius = radius; + mIconView.setRadius(mIconRadius); + } + + public void setIconUrls(List iconUrls) { + mIconView.displayImage(iconUrls).load(null); + } + +} diff --git a/TIMCommon/timcommon/src/main/java/com/tencent/qcloud/tuikit/timcommon/component/impl/CornerTransform.java b/TIMCommon/timcommon/src/main/java/com/tencent/qcloud/tuikit/timcommon/component/impl/CornerTransform.java new file mode 100644 index 0000000..5c66e8c --- /dev/null +++ b/TIMCommon/timcommon/src/main/java/com/tencent/qcloud/tuikit/timcommon/component/impl/CornerTransform.java @@ -0,0 +1,135 @@ +package com.tencent.qcloud.tuikit.timcommon.component.impl; + +import android.content.Context; +import android.graphics.Bitmap; +import android.graphics.BitmapShader; +import android.graphics.Canvas; +import android.graphics.Matrix; +import android.graphics.Paint; +import android.graphics.RectF; +import android.graphics.Shader; + +import androidx.annotation.NonNull; + +import com.bumptech.glide.Glide; +import com.bumptech.glide.load.Transformation; +import com.bumptech.glide.load.engine.Resource; +import com.bumptech.glide.load.engine.bitmap_recycle.BitmapPool; +import com.bumptech.glide.load.resource.bitmap.BitmapResource; + +import java.security.MessageDigest; + +public class CornerTransform implements Transformation { + + private BitmapPool mBitmapPool; + private float radius; + private boolean exceptLeftTop, exceptRightTop, exceptLeftBottom, exceptRightBottom; + + public CornerTransform(Context context, float radius) { + this.mBitmapPool = Glide.get(context).getBitmapPool(); + this.radius = radius; + } + + public void setExceptCorner(boolean leftTop, boolean rightTop, boolean leftBottom, boolean rightBottom) { + this.exceptLeftTop = leftTop; + this.exceptRightTop = rightTop; + this.exceptLeftBottom = leftBottom; + this.exceptRightBottom = rightBottom; + } + + @NonNull + @Override + public Resource transform(@NonNull Context context, @NonNull Resource resource, int outWidth, int outHeight) { + Bitmap source = resource.get(); + int finalWidth, finalHeight; + // 输出目标的宽高或高宽比例 + // The width-height or height-width ratio of the output target + float ratio; + if (outWidth > outHeight) { + // 输出宽度>输出高度,求高宽比 + // output width > output height, find the aspect ratio + ratio = (float) outHeight / (float) outWidth; + finalWidth = source.getWidth(); + // 固定原图宽度,求最终高度 + // Fix the width of the original image and find the final height + finalHeight = (int) ((float) source.getWidth() * ratio); + if (finalHeight > source.getHeight()) { + // 求出的最终高度>原图高度,求宽高比 + // Find the final height > the original image height, find the aspect ratio + ratio = (float) outWidth / (float) outHeight; + finalHeight = source.getHeight(); + // 固定原图高度,求最终宽度 + // Fix the width of the original image and find the final width + finalWidth = (int) ((float) source.getHeight() * ratio); + } + } else if (outWidth < outHeight) { + // 输出宽度 < 输出高度,求宽高比 + // output width < output height, find the aspect ratio + ratio = (float) outWidth / (float) outHeight; + finalHeight = source.getHeight(); + // 固定原图高度,求最终宽度 + // Fix the width of the original image and find the final width + finalWidth = (int) ((float) source.getHeight() * ratio); + if (finalWidth > source.getWidth()) { + // 求出的最终宽度 > 原图宽度,求高宽比 + // Find the final width > the original image width, find the aspect ratio + ratio = (float) outHeight / (float) outWidth; + finalWidth = source.getWidth(); + finalHeight = (int) ((float) source.getWidth() * ratio); + } + } else { + // 输出宽度=输出高度 + // output width = output height + finalHeight = source.getHeight(); + finalWidth = finalHeight; + } + + // 修正圆角 + // Correct rounded corners + this.radius *= (float) finalHeight / (float) outHeight; + Bitmap outBitmap = this.mBitmapPool.get(finalWidth, finalHeight, Bitmap.Config.ARGB_8888); + if (outBitmap == null) { + outBitmap = Bitmap.createBitmap(finalWidth, finalHeight, Bitmap.Config.ARGB_8888); + } + + Canvas canvas = new Canvas(outBitmap); + Paint paint = new Paint(); + BitmapShader shader = new BitmapShader(source, Shader.TileMode.CLAMP, Shader.TileMode.CLAMP); + // 计算中心位置,进行偏移 + // Calculate the center position and offset it + int width = (source.getWidth() - finalWidth) / 2; + int height = (source.getHeight() - finalHeight) / 2; + if (width != 0 || height != 0) { + Matrix matrix = new Matrix(); + matrix.setTranslate((float) (-width), (float) (-height)); + shader.setLocalMatrix(matrix); + } + + paint.setShader(shader); + paint.setAntiAlias(true); + RectF rectF = new RectF(0.0F, 0.0F, (float) canvas.getWidth(), (float) canvas.getHeight()); + canvas.drawRoundRect(rectF, this.radius, this.radius, paint); + + if (exceptLeftTop) { + canvas.drawRect(0, 0, radius, radius, paint); + } + if (exceptRightTop) { + canvas.drawRect(canvas.getWidth() - radius, 0, radius, radius, paint); + } + + if (exceptLeftBottom) { + canvas.drawRect(0, canvas.getHeight() - radius, radius, canvas.getHeight(), paint); + } + + if (exceptRightBottom) { + canvas.drawRect(canvas.getWidth() - radius, canvas.getHeight() - radius, canvas.getWidth(), canvas.getHeight(), paint); + } + + return BitmapResource.obtain(outBitmap, this.mBitmapPool); + } + + @Override + public void updateDiskCacheKey(@NonNull MessageDigest messageDigest) { + + } +} \ No newline at end of file diff --git a/TIMCommon/timcommon/src/main/java/com/tencent/qcloud/tuikit/timcommon/component/impl/GlideEngine.java b/TIMCommon/timcommon/src/main/java/com/tencent/qcloud/tuikit/timcommon/component/impl/GlideEngine.java new file mode 100644 index 0000000..cf2b10a --- /dev/null +++ b/TIMCommon/timcommon/src/main/java/com/tencent/qcloud/tuikit/timcommon/component/impl/GlideEngine.java @@ -0,0 +1,152 @@ +package com.tencent.qcloud.tuikit.timcommon.component.impl; + +import android.content.Context; +import android.graphics.Bitmap; +import android.net.Uri; +import android.widget.ImageView; + +import com.bumptech.glide.Glide; +import com.bumptech.glide.Priority; +import com.bumptech.glide.load.resource.bitmap.RoundedCorners; +import com.bumptech.glide.request.RequestListener; +import com.bumptech.glide.request.RequestOptions; +import com.tencent.qcloud.tuikit.timcommon.R; +import com.tencent.qcloud.tuicore.TUILogin; +import com.tencent.qcloud.tuicore.TUIThemeManager; + +import java.io.File; +import java.util.concurrent.ExecutionException; + +public class GlideEngine { + + public static void loadCornerImageWithoutPlaceHolder(ImageView imageView, String filePath, RequestListener listener, float radius) { + RoundedCorners transform = null; + if ((int) radius > 0) { + transform = new RoundedCorners((int) radius); + } + + RequestOptions options = new RequestOptions() + .centerCrop(); + if (transform != null) { + options = options.transform(transform); + } + Glide.with(TUILogin.getAppContext()) + .load(filePath) + .apply(options) + .listener(listener) + .into(imageView); + } + + public static void loadImage(ImageView imageView, String filePath, RequestListener listener) { + Glide.with(TUILogin.getAppContext()) + .load(filePath) + .listener(listener) + .apply(new RequestOptions().error(TUIThemeManager.getAttrResId(TUILogin.getAppContext(), R.attr.core_default_user_icon))) + .into(imageView); + } + + public static void loadImage(ImageView imageView, String filePath) { + Glide.with(TUILogin.getAppContext()) + .load(filePath) + .apply(new RequestOptions().error(TUIThemeManager.getAttrResId(TUILogin.getAppContext(), R.attr.core_default_user_icon))) + .into(imageView); + } + + public static void clear(ImageView imageView) { + Glide.with(TUILogin.getAppContext()).clear(imageView); + } + + public static void loadImage(ImageView imageView, Uri uri) { + if (uri == null) { + return; + } + Glide.with(TUILogin.getAppContext()) + .load(uri) + .apply(new RequestOptions().error(TUIThemeManager.getAttrResId(TUILogin.getAppContext(), R.attr.core_default_user_icon))) + .into(imageView); + } + + public static void loadImage(String filePath, String url) { + try { + File file = Glide.with(TUILogin.getAppContext()).asFile().load(url).submit().get(); + File destFile = new File(filePath); + file.renameTo(destFile); + } catch (InterruptedException e) { + e.printStackTrace(); + } catch (ExecutionException e) { + e.printStackTrace(); + } + } + + public static void loadImage(ImageView imageView, Object uri) { + if (uri == null) { + return; + } + Glide.with(TUILogin.getAppContext()) + .load(uri) + .apply(new RequestOptions().error(TUIThemeManager.getAttrResId(TUILogin.getAppContext(), R.attr.core_default_user_icon))) + .into(imageView); + } + + public static void loadUserIcon(ImageView imageView, Object uri) { + loadUserIcon(imageView, uri, 0); + } + + public static void loadUserIcon(ImageView imageView, Object uri, int radius) { + Glide.with(TUILogin.getAppContext()) + .load(uri) + .placeholder(TUIThemeManager.getAttrResId(TUILogin.getAppContext(), R.attr.core_default_user_icon)) + .apply(new RequestOptions().centerCrop().error(TUIThemeManager.getAttrResId(TUILogin.getAppContext(), R.attr.core_default_user_icon))) + .into(imageView); + } + + public static void loadUserIcon(ImageView imageView, Object uri, int defaultResId, int radius) { + Glide.with(TUILogin.getAppContext()) + .load(uri) + .placeholder(defaultResId) + .apply(new RequestOptions().centerCrop().error(defaultResId)) + .into(imageView); + } + + public static Bitmap loadBitmap(Object imageUrl, int targetImageSize) throws InterruptedException, ExecutionException { + if (imageUrl == null) { + return null; + } + return Glide.with(TUILogin.getAppContext()).asBitmap() + .load(imageUrl) + .apply(new RequestOptions().error(TUIThemeManager.getAttrResId(TUILogin.getAppContext(), R.attr.core_default_user_icon))) + .into(targetImageSize, targetImageSize) + .get(); + } + + public static Bitmap loadBitmap(Object imageUrl, int width, int height) throws InterruptedException, ExecutionException { + if (imageUrl == null) { + return null; + } + return Glide.with(TUILogin.getAppContext()).asBitmap() + .load(imageUrl) + .apply(new RequestOptions().error(TUIThemeManager.getAttrResId(TUILogin.getAppContext(), R.attr.core_default_user_icon))) + .into(width, height) + .get(); + } + + + public void loadImage(Context context, int resizeX, int resizeY, ImageView imageView, Uri uri) { + Glide.with(context) + .load(uri) + .apply(new RequestOptions() + .override(resizeX, resizeY) + .priority(Priority.HIGH) + .fitCenter()) + .into(imageView); + } + + public static void loadImageSetDefault(ImageView imageView, Object uri, int defaultResId) { + Glide.with(TUILogin.getAppContext()) + .load(uri) + .placeholder(defaultResId) + .apply(new RequestOptions().centerCrop().error(defaultResId)) + .into(imageView); + } + +} diff --git a/TIMCommon/timcommon/src/main/java/com/tencent/qcloud/tuikit/timcommon/component/interfaces/ILayout.java b/TIMCommon/timcommon/src/main/java/com/tencent/qcloud/tuikit/timcommon/component/interfaces/ILayout.java new file mode 100644 index 0000000..ba0ad02 --- /dev/null +++ b/TIMCommon/timcommon/src/main/java/com/tencent/qcloud/tuikit/timcommon/component/interfaces/ILayout.java @@ -0,0 +1,20 @@ +package com.tencent.qcloud.tuikit.timcommon.component.interfaces; + +import com.tencent.qcloud.tuikit.timcommon.component.TitleBarLayout; + +public interface ILayout { + + /** + * get title bar + * + * @return + */ + TitleBarLayout getTitleBar(); + + /** + * Set the parent container of this Layout + * + * @param parent + */ + void setParentLayout(Object parent); +} diff --git a/TIMCommon/timcommon/src/main/java/com/tencent/qcloud/tuikit/timcommon/component/interfaces/ITitleBarLayout.java b/TIMCommon/timcommon/src/main/java/com/tencent/qcloud/tuikit/timcommon/component/interfaces/ITitleBarLayout.java new file mode 100644 index 0000000..3000da5 --- /dev/null +++ b/TIMCommon/timcommon/src/main/java/com/tencent/qcloud/tuikit/timcommon/component/interfaces/ITitleBarLayout.java @@ -0,0 +1,153 @@ +package com.tencent.qcloud.tuikit.timcommon.component.interfaces; + +import android.view.View; +import android.widget.ImageView; +import android.widget.LinearLayout; +import android.widget.TextView; + +/** + * 会话列表窗口 {@link ConversationLayout}、聊天窗口 {@link ChatLayout} 等都自带标题栏,
+ * 标题栏设计为左中右三部分标题,左边可为图片+文字,中间为文字,右边也可为图片+文字,这些区域返回的都是标准的
+ * Android View,可以根据业务需要对这些 View 进行交互响应处理。 + * + * Conversation list window {@link ConversationLayout}、chat window {@link ChatLayout} have title bar, + * The title bar is designed as a three-part title on the left, middle and right. The left can be + * picture + text, the middle is text, and the right can also be picture + text. These areas return the + * standard Android View,These Views can be interactively processed according to business needs。 + */ +public interface ITitleBarLayout { + + /** + * 设置左边标题的点击事件 + * + * Set the click event of the left header + * + * @param listener + */ + void setOnLeftClickListener(View.OnClickListener listener); + + /** + * 设置右边标题的点击事件 + * + * Set the click event of the right title + * + * @param listener + */ + void setOnRightClickListener(View.OnClickListener listener); + + /** + * 设置标题 + * + * set Title + * + */ + void setTitle(String title, Position position); + + /** + * 返回左边标题区域 + * + * Return to the left header area + * + * @return + */ + LinearLayout getLeftGroup(); + + /** + * 返回右边标题区域 + * + * Return to the right header area + * + * @return + */ + LinearLayout getRightGroup(); + + /** + * 返回左边标题的图片 + * + * Returns the image for the left header + * + * @return + */ + ImageView getLeftIcon(); + + /** + * 设置左边标题的图片 + * + * Set the image for the left header + * + * @param resId + */ + void setLeftIcon(int resId); + + /** + * 返回右边标题的图片 + * + * Returns the image with the right header + * + * @return + */ + ImageView getRightIcon(); + + /** + * 设置右边标题的图片 + * + * Set the image for the title on the right + * + * @param resId + */ + void setRightIcon(int resId); + + /** + * 返回左边标题的文字 + * + * Returns the text of the left header + * + * @return + */ + TextView getLeftTitle(); + + /** + * 返回中间标题的文字 + * + * Returns the text of the middle title + * + * @return + */ + TextView getMiddleTitle(); + + /** + * 返回右边标题的文字 + * + * Returns the text of the title on the right + * + * @return + */ + TextView getRightTitle(); + + /** + * 标题区域的枚举值 + * + * enumeration value of the header area + */ + enum Position { + /** + * 左边标题 + * + * left title + */ + LEFT, + /** + * 中间标题 + * + * middle title + */ + MIDDLE, + /** + * 右边标题 + * + * right title + */ + RIGHT + } + +} diff --git a/TIMCommon/timcommon/src/main/java/com/tencent/qcloud/tuikit/timcommon/component/interfaces/IUIKitCallback.java b/TIMCommon/timcommon/src/main/java/com/tencent/qcloud/tuikit/timcommon/component/interfaces/IUIKitCallback.java new file mode 100644 index 0000000..b6e2d4a --- /dev/null +++ b/TIMCommon/timcommon/src/main/java/com/tencent/qcloud/tuikit/timcommon/component/interfaces/IUIKitCallback.java @@ -0,0 +1,25 @@ +package com.tencent.qcloud.tuikit.timcommon.component.interfaces; + +public abstract class IUIKitCallback { + + public void onSuccess(T data) {}; + + public void onError(String module, int errCode, String errMsg) {} + + public void onError(int errCode, String errMsg, T data) {} + + public void onProgress(Object data) {} + + public static void callbackOnSuccess(IUIKitCallback callback, O data) { + if (callback != null) { + callback.onSuccess(data); + } + } + + public static void callbackOnError(IUIKitCallback callback, int errCode, String errMsg, O data) { + if (callback != null) { + callback.onError(errCode, errMsg, data); + } + } + +} diff --git a/TIMCommon/timcommon/src/main/java/com/tencent/qcloud/tuikit/timcommon/component/swipe/Attributes.java b/TIMCommon/timcommon/src/main/java/com/tencent/qcloud/tuikit/timcommon/component/swipe/Attributes.java new file mode 100644 index 0000000..ffee554 --- /dev/null +++ b/TIMCommon/timcommon/src/main/java/com/tencent/qcloud/tuikit/timcommon/component/swipe/Attributes.java @@ -0,0 +1,9 @@ +package com.tencent.qcloud.tuikit.timcommon.component.swipe; + + +public class Attributes { + + public enum Mode { + Single, Multiple + } +} diff --git a/TIMCommon/timcommon/src/main/java/com/tencent/qcloud/tuikit/timcommon/component/swipe/RecyclerSwipeAdapter.java b/TIMCommon/timcommon/src/main/java/com/tencent/qcloud/tuikit/timcommon/component/swipe/RecyclerSwipeAdapter.java new file mode 100644 index 0000000..4a65cde --- /dev/null +++ b/TIMCommon/timcommon/src/main/java/com/tencent/qcloud/tuikit/timcommon/component/swipe/RecyclerSwipeAdapter.java @@ -0,0 +1,87 @@ +package com.tencent.qcloud.tuikit.timcommon.component.swipe; + +import android.view.ViewGroup; + +import androidx.recyclerview.widget.RecyclerView; + +import java.util.List; + +public abstract class RecyclerSwipeAdapter extends RecyclerView.Adapter implements SwipeItemMangerInterface, SwipeAdapterInterface { + + public SwipeItemMangerImpl mItemManger = new SwipeItemMangerImpl(this); + + @Override + public abstract VH onCreateViewHolder(ViewGroup parent, int viewType); + + @Override + public abstract void onBindViewHolder(VH viewHolder, final int position); + + @Override + public void notifyDatasetChanged() { + super.notifyDataSetChanged(); + } + + @Override + public void notifySwipeItemChanged(int position) { + super.notifyItemChanged(position); + } + + @Override + public void openItem(int position) { + mItemManger.openItem(position); + } + + @Override + public void closeItem(int position) { + mItemManger.closeItem(position); + } + + @Override + public void closeAllExcept(SwipeLayout layout) { + mItemManger.closeAllExcept(layout); + } + + @Override + public void closeAllSwipeItems() { + mItemManger.closeAllSwipeItems(); + } + + @Override + public List getOpenItems() { + return mItemManger.getOpenItems(); + } + + @Override + public List getOpenLayouts() { + return mItemManger.getOpenLayouts(); + } + + @Override + public void removeShownLayouts(SwipeLayout layout) { + mItemManger.removeShownLayouts(layout); + } + + @Override + public boolean isOpen(int position) { + return mItemManger.isOpen(position); + } + + @Override + public Attributes.Mode getMode() { + return mItemManger.getMode(); + } + + @Override + public void setMode(Attributes.Mode mode) { + mItemManger.setMode(mode); + } + + @Override + public void switchAllSwipeEnable(boolean enable) { + mItemManger.switchAllSwipeEnable(enable); + } + + public void setSwipeEnabled(boolean enabled) { + mItemManger.setSwipeEnabled(enabled); + } +} diff --git a/TIMCommon/timcommon/src/main/java/com/tencent/qcloud/tuikit/timcommon/component/swipe/SimpleSwipeListener.java b/TIMCommon/timcommon/src/main/java/com/tencent/qcloud/tuikit/timcommon/component/swipe/SimpleSwipeListener.java new file mode 100644 index 0000000..691fe78 --- /dev/null +++ b/TIMCommon/timcommon/src/main/java/com/tencent/qcloud/tuikit/timcommon/component/swipe/SimpleSwipeListener.java @@ -0,0 +1,28 @@ +package com.tencent.qcloud.tuikit.timcommon.component.swipe; + +public class SimpleSwipeListener implements SwipeLayout.SwipeListener { + + @Override + public void onStartOpen(SwipeLayout layout) { + } + + @Override + public void onOpen(SwipeLayout layout) { + } + + @Override + public void onStartClose(SwipeLayout layout) { + } + + @Override + public void onClose(SwipeLayout layout) { + } + + @Override + public void onUpdate(SwipeLayout layout, int leftOffset, int topOffset) { + } + + @Override + public void onHandRelease(SwipeLayout layout, float xvel, float yvel) { + } +} diff --git a/TIMCommon/timcommon/src/main/java/com/tencent/qcloud/tuikit/timcommon/component/swipe/SwipeAdapterInterface.java b/TIMCommon/timcommon/src/main/java/com/tencent/qcloud/tuikit/timcommon/component/swipe/SwipeAdapterInterface.java new file mode 100644 index 0000000..81384b9 --- /dev/null +++ b/TIMCommon/timcommon/src/main/java/com/tencent/qcloud/tuikit/timcommon/component/swipe/SwipeAdapterInterface.java @@ -0,0 +1,11 @@ +package com.tencent.qcloud.tuikit.timcommon.component.swipe; + +public interface SwipeAdapterInterface { + + int getSwipeLayoutResourceId(int position); + + void notifyDatasetChanged(); + + void notifySwipeItemChanged(int position); + +} diff --git a/TIMCommon/timcommon/src/main/java/com/tencent/qcloud/tuikit/timcommon/component/swipe/SwipeItemMangerImpl.java b/TIMCommon/timcommon/src/main/java/com/tencent/qcloud/tuikit/timcommon/component/swipe/SwipeItemMangerImpl.java new file mode 100644 index 0000000..bb45d3f --- /dev/null +++ b/TIMCommon/timcommon/src/main/java/com/tencent/qcloud/tuikit/timcommon/component/swipe/SwipeItemMangerImpl.java @@ -0,0 +1,219 @@ +package com.tencent.qcloud.tuikit.timcommon.component.swipe; + +import android.view.View; + +import java.util.ArrayList; +import java.util.Collections; +import java.util.HashSet; +import java.util.List; +import java.util.Set; + +public class SwipeItemMangerImpl implements SwipeItemMangerInterface { + + private Attributes.Mode mode = Attributes.Mode.Single; + public final int INVALID_POSITION = -1; + + protected int mOpenPosition = INVALID_POSITION; + + protected Set mOpenPositions = new HashSet(); + protected Set mShownLayouts = new HashSet(); + + protected boolean isSwipeEnabled = true; + protected SwipeAdapterInterface swipeAdapterInterface; + + public SwipeItemMangerImpl(SwipeAdapterInterface swipeAdapterInterface) { + if (swipeAdapterInterface == null) + throw new IllegalArgumentException("SwipeAdapterInterface can not be null"); + + this.swipeAdapterInterface = swipeAdapterInterface; + } + + public Attributes.Mode getMode() { + return mode; + } + + public void setMode(Attributes.Mode mode) { + this.mode = mode; + mOpenPositions.clear(); + mShownLayouts.clear(); + mOpenPosition = INVALID_POSITION; + } + + public void bind(View view, int position) { + int resId = swipeAdapterInterface.getSwipeLayoutResourceId(position); + SwipeLayout swipeLayout = (SwipeLayout) view.findViewById(resId); + if (swipeLayout == null) + throw new IllegalStateException("can not find SwipeLayout in target view"); + + swipeLayout.setSwipeEnabled(isSwipeEnabled); + if (swipeLayout.getTag(resId) == null) { + OnLayoutListener onLayoutListener = new OnLayoutListener(position); + SwipeMemory swipeMemory = new SwipeMemory(position); + swipeLayout.addSwipeListener(swipeMemory); + swipeLayout.addOnLayoutListener(onLayoutListener); + swipeLayout.setTag(resId, new ValueBox(position, swipeMemory, onLayoutListener)); + mShownLayouts.add(swipeLayout); + } else { + ValueBox valueBox = (ValueBox) swipeLayout.getTag(resId); + valueBox.swipeMemory.setPosition(position); + valueBox.onLayoutListener.setPosition(position); + valueBox.position = position; + } + } + + @Override + public void openItem(int position) { + if (mode == Attributes.Mode.Multiple) { + if (!mOpenPositions.contains(position)) + mOpenPositions.add(position); + } else { + mOpenPosition = position; + } + swipeAdapterInterface.notifySwipeItemChanged(position); + } + + @Override + public void closeItem(int position) { + if (mode == Attributes.Mode.Multiple) { + mOpenPositions.remove(position); + } else { + if (mOpenPosition == position) + mOpenPosition = INVALID_POSITION; + } + swipeAdapterInterface.notifySwipeItemChanged(position); + } + + @Override + public void closeAllExcept(SwipeLayout layout) { + for (SwipeLayout s : mShownLayouts) { + if (s != layout) + s.close(); + } + } + + @Override + public void closeAllSwipeItems() { + if (mode == Attributes.Mode.Multiple) { + mOpenPositions.clear(); + } else { + mOpenPosition = INVALID_POSITION; + } + for (SwipeLayout s : mShownLayouts) { + s.close(); + } + } + + @Override + public void switchAllSwipeEnable(boolean enable) { + for (SwipeLayout s : mShownLayouts) { + s.setSwipeEnabled(enable); + } + } + + @Override + public void removeShownLayouts(SwipeLayout layout) { + mShownLayouts.remove(layout); + } + + @Override + public List getOpenItems() { + if (mode == Attributes.Mode.Multiple) { + return new ArrayList(mOpenPositions); + } else { + return Collections.singletonList(mOpenPosition); + } + } + + @Override + public List getOpenLayouts() { + return new ArrayList(mShownLayouts); + } + + @Override + public boolean isOpen(int position) { + if (mode == Attributes.Mode.Multiple) { + return mOpenPositions.contains(position); + } else { + return mOpenPosition == position; + } + } + + public void setSwipeEnabled(boolean swipeEnabled) { + isSwipeEnabled = swipeEnabled; + } + + class ValueBox { + OnLayoutListener onLayoutListener; + SwipeMemory swipeMemory; + int position; + + ValueBox(int position, SwipeMemory swipeMemory, OnLayoutListener onLayoutListener) { + this.swipeMemory = swipeMemory; + this.onLayoutListener = onLayoutListener; + this.position = position; + } + } + + class OnLayoutListener implements SwipeLayout.OnLayout { + + private int position; + + OnLayoutListener(int position) { + this.position = position; + } + + public void setPosition(int position) { + this.position = position; + } + + @Override + public void onLayout(SwipeLayout v) { + if (isOpen(position)) { + v.open(false, false); + } else { + v.close(false, false); + } + } + + } + + class SwipeMemory extends SimpleSwipeListener { + + private int position; + + SwipeMemory(int position) { + this.position = position; + } + + @Override + public void onClose(SwipeLayout layout) { + if (mode == Attributes.Mode.Multiple) { + mOpenPositions.remove(position); + } else { + mOpenPosition = INVALID_POSITION; + } + } + + @Override + public void onStartOpen(SwipeLayout layout) { + if (mode == Attributes.Mode.Single) { + closeAllExcept(layout); + } + } + + @Override + public void onOpen(SwipeLayout layout) { + if (mode == Attributes.Mode.Multiple) + mOpenPositions.add(position); + else { + closeAllExcept(layout); + mOpenPosition = position; + } + } + + public void setPosition(int position) { + this.position = position; + } + } + +} diff --git a/TIMCommon/timcommon/src/main/java/com/tencent/qcloud/tuikit/timcommon/component/swipe/SwipeItemMangerInterface.java b/TIMCommon/timcommon/src/main/java/com/tencent/qcloud/tuikit/timcommon/component/swipe/SwipeItemMangerInterface.java new file mode 100644 index 0000000..555bd6d --- /dev/null +++ b/TIMCommon/timcommon/src/main/java/com/tencent/qcloud/tuikit/timcommon/component/swipe/SwipeItemMangerInterface.java @@ -0,0 +1,28 @@ +package com.tencent.qcloud.tuikit.timcommon.component.swipe; + +import java.util.List; + +public interface SwipeItemMangerInterface { + + void openItem(int position); + + void closeItem(int position); + + void closeAllExcept(SwipeLayout layout); + + void closeAllSwipeItems(); + + List getOpenItems(); + + List getOpenLayouts(); + + void removeShownLayouts(SwipeLayout layout); + + boolean isOpen(int position); + + Attributes.Mode getMode(); + + void setMode(Attributes.Mode mode); + + void switchAllSwipeEnable(boolean enable); +} diff --git a/TIMCommon/timcommon/src/main/java/com/tencent/qcloud/tuikit/timcommon/component/swipe/SwipeLayout.java b/TIMCommon/timcommon/src/main/java/com/tencent/qcloud/tuikit/timcommon/component/swipe/SwipeLayout.java new file mode 100644 index 0000000..a16a696 --- /dev/null +++ b/TIMCommon/timcommon/src/main/java/com/tencent/qcloud/tuikit/timcommon/component/swipe/SwipeLayout.java @@ -0,0 +1,1662 @@ +package com.tencent.qcloud.tuikit.timcommon.component.swipe; + +import android.content.Context; +import android.content.res.TypedArray; +import android.graphics.Rect; +import android.util.AttributeSet; +import android.view.GestureDetector; +import android.view.Gravity; +import android.view.HapticFeedbackConstants; +import android.view.MotionEvent; +import android.view.View; +import android.view.ViewConfiguration; +import android.view.ViewGroup; +import android.view.ViewParent; +import android.widget.AbsListView; +import android.widget.AdapterView; +import android.widget.FrameLayout; + +import androidx.core.view.GravityCompat; +import androidx.core.view.ViewCompat; +import androidx.customview.widget.ViewDragHelper; + +import com.tencent.qcloud.tuikit.timcommon.R; + +import java.lang.reflect.Method; +import java.util.ArrayList; +import java.util.Arrays; +import java.util.HashMap; +import java.util.LinkedHashMap; +import java.util.List; +import java.util.Map; + +public class SwipeLayout extends FrameLayout { + @Deprecated + public static final int EMPTY_LAYOUT = -1; + private static final int DRAG_LEFT = 1; + private static final int DRAG_RIGHT = 2; + private static final int DRAG_TOP = 4; + private static final int DRAG_BOTTOM = 8; + private static final DragEdge DefaultDragEdge = DragEdge.Right; + + private int mTouchSlop; + + private DragEdge mCurrentDragEdge = DefaultDragEdge; + private ViewDragHelper mDragHelper; + + private int mDragDistance = 0; + private LinkedHashMap mDragEdges = new LinkedHashMap<>(); + private ShowMode mShowMode; + + private float[] mEdgeSwipesOffset = new float[4]; + + private List mSwipeListeners = new ArrayList<>(); + private List mSwipeDeniers = new ArrayList<>(); + private Map> mRevealListeners = new HashMap<>(); + private Map mShowEntirely = new HashMap<>(); + private Map mViewBoundCache = new HashMap<>();//save all children's bound, restore in onLayout + + private DoubleClickListener mDoubleClickListener; + + private boolean mSwipeEnabled = true; + private boolean[] mSwipesEnabled = new boolean[]{true, true, true, true}; + private boolean mClickToClose = false; + private float mWillOpenPercentAfterOpen = 0.75f; + private float mWillOpenPercentAfterClose = 0.25f; + + public enum DragEdge { + Left, + Top, + Right, + Bottom + } + + public enum ShowMode { + LayDown, + PullOut + } + + public SwipeLayout(Context context) { + this(context, null); + } + + public SwipeLayout(Context context, AttributeSet attrs) { + this(context, attrs, 0); + } + + public SwipeLayout(Context context, AttributeSet attrs, int defStyle) { + super(context, attrs, defStyle); + mDragHelper = ViewDragHelper.create(this, mDragHelperCallback); + mTouchSlop = ViewConfiguration.get(context).getScaledTouchSlop(); + + TypedArray a = context.obtainStyledAttributes(attrs, R.styleable.SwipeLayout); + int dragEdgeChoices = a.getInt(R.styleable.SwipeLayout_drag_edge, DRAG_RIGHT); + mEdgeSwipesOffset[DragEdge.Left.ordinal()] = a.getDimension(R.styleable.SwipeLayout_leftEdgeSwipeOffset, 0); + mEdgeSwipesOffset[DragEdge.Right.ordinal()] = a.getDimension(R.styleable.SwipeLayout_rightEdgeSwipeOffset, 0); + mEdgeSwipesOffset[DragEdge.Top.ordinal()] = a.getDimension(R.styleable.SwipeLayout_topEdgeSwipeOffset, 0); + mEdgeSwipesOffset[DragEdge.Bottom.ordinal()] = a.getDimension(R.styleable.SwipeLayout_bottomEdgeSwipeOffset, 0); + setClickToClose(a.getBoolean(R.styleable.SwipeLayout_clickToClose, mClickToClose)); + + if ((dragEdgeChoices & DRAG_LEFT) == DRAG_LEFT) { + mDragEdges.put(DragEdge.Left, null); + } + if ((dragEdgeChoices & DRAG_TOP) == DRAG_TOP) { + mDragEdges.put(DragEdge.Top, null); + } + if ((dragEdgeChoices & DRAG_RIGHT) == DRAG_RIGHT) { + mDragEdges.put(DragEdge.Right, null); + } + if ((dragEdgeChoices & DRAG_BOTTOM) == DRAG_BOTTOM) { + mDragEdges.put(DragEdge.Bottom, null); + } + int ordinal = a.getInt(R.styleable.SwipeLayout_show_mode, ShowMode.PullOut.ordinal()); + mShowMode = ShowMode.values()[ordinal]; + a.recycle(); + + } + + public interface SwipeListener { + void onStartOpen(SwipeLayout layout); + + void onOpen(SwipeLayout layout); + + void onStartClose(SwipeLayout layout); + + void onClose(SwipeLayout layout); + + void onUpdate(SwipeLayout layout, int leftOffset, int topOffset); + + void onHandRelease(SwipeLayout layout, float xvel, float yvel); + } + + public void addSwipeListener(SwipeListener l) { + mSwipeListeners.add(l); + } + + public void removeSwipeListener(SwipeListener l) { + mSwipeListeners.remove(l); + } + + public void removeAllSwipeListener() { + mSwipeListeners.clear(); + } + + public interface SwipeDenier { + boolean shouldDenySwipe(MotionEvent ev); + } + + public void addSwipeDenier(SwipeDenier denier) { + mSwipeDeniers.add(denier); + } + + public void removeSwipeDenier(SwipeDenier denier) { + mSwipeDeniers.remove(denier); + } + + public void removeAllSwipeDeniers() { + mSwipeDeniers.clear(); + } + + public interface OnRevealListener { + void onReveal(View child, DragEdge edge, float fraction, int distance); + } + + /** + * bind a view with a specific + * {@link com.tencent.qcloud.tuikit.timcommon.component.swipe.SwipeLayout.OnRevealListener} + * + * @param childId the view id. + * @param l the target + * {@link com.tencent.qcloud.tuikit.timcommon.component.swipe.SwipeLayout.OnRevealListener} + */ + public void addRevealListener(int childId, OnRevealListener l) { + View child = findViewById(childId); + if (child == null) { + throw new IllegalArgumentException("Child does not belong to SwipeListener."); + } + + if (!mShowEntirely.containsKey(child)) { + mShowEntirely.put(child, false); + } + if (mRevealListeners.get(child) == null) + mRevealListeners.put(child, new ArrayList()); + + mRevealListeners.get(child).add(l); + } + + /** + * bind multiple views with an + * {@link com.tencent.qcloud.tuikit.timcommon.component.swipe.SwipeLayout.OnRevealListener}. + * + * @param childIds the view id. + * @param l the {@link com.tencent.qcloud.tuikit.timcommon.component.swipe.SwipeLayout.OnRevealListener} + */ + public void addRevealListener(int[] childIds, OnRevealListener l) { + for (int i : childIds) + addRevealListener(i, l); + } + + public void removeRevealListener(int childId, OnRevealListener l) { + View child = findViewById(childId); + + if (child == null) return; + + mShowEntirely.remove(child); + if (mRevealListeners.containsKey(child)) mRevealListeners.get(child).remove(l); + } + + public void removeAllRevealListeners(int childId) { + View child = findViewById(childId); + if (child != null) { + mRevealListeners.remove(child); + mShowEntirely.remove(child); + } + } + + private ViewDragHelper.Callback mDragHelperCallback = new ViewDragHelper.Callback() { + + @Override + public int clampViewPositionHorizontal(View child, int left, int dx) { + if (child == getSurfaceView()) { + switch (mCurrentDragEdge) { + case Top: + case Bottom: + return getPaddingLeft(); + case Left: + if (left < getPaddingLeft()) return getPaddingLeft(); + if (left > getPaddingLeft() + mDragDistance) + return getPaddingLeft() + mDragDistance; + break; + case Right: + if (left > getPaddingLeft()) return getPaddingLeft(); + if (left < getPaddingLeft() - mDragDistance) + return getPaddingLeft() - mDragDistance; + break; + } + } else if (getCurrentBottomView() == child) { + + switch (mCurrentDragEdge) { + case Top: + case Bottom: + return getPaddingLeft(); + case Left: + if (mShowMode == ShowMode.PullOut) { + if (left > getPaddingLeft()) return getPaddingLeft(); + } + break; + case Right: + if (mShowMode == ShowMode.PullOut) { + if (left < getMeasuredWidth() - mDragDistance) { + return getMeasuredWidth() - mDragDistance; + } + } + break; + } + } + return left; + } + + @Override + public int clampViewPositionVertical(View child, int top, int dy) { + if (child == getSurfaceView()) { + switch (mCurrentDragEdge) { + case Left: + case Right: + return getPaddingTop(); + case Top: + if (top < getPaddingTop()) return getPaddingTop(); + if (top > getPaddingTop() + mDragDistance) + return getPaddingTop() + mDragDistance; + break; + case Bottom: + if (top < getPaddingTop() - mDragDistance) { + return getPaddingTop() - mDragDistance; + } + if (top > getPaddingTop()) { + return getPaddingTop(); + } + } + } else { + View surfaceView = getSurfaceView(); + int surfaceViewTop = surfaceView == null ? 0 : surfaceView.getTop(); + switch (mCurrentDragEdge) { + case Left: + case Right: + return getPaddingTop(); + case Top: + if (mShowMode == ShowMode.PullOut) { + if (top > getPaddingTop()) return getPaddingTop(); + } else { + if (surfaceViewTop + dy < getPaddingTop()) + return getPaddingTop(); + if (surfaceViewTop + dy > getPaddingTop() + mDragDistance) + return getPaddingTop() + mDragDistance; + } + break; + case Bottom: + if (mShowMode == ShowMode.PullOut) { + if (top < getMeasuredHeight() - mDragDistance) + return getMeasuredHeight() - mDragDistance; + } else { + if (surfaceViewTop + dy >= getPaddingTop()) + return getPaddingTop(); + if (surfaceViewTop + dy <= getPaddingTop() - mDragDistance) + return getPaddingTop() - mDragDistance; + } + } + } + return top; + } + + @Override + public boolean tryCaptureView(View child, int pointerId) { + boolean result = child == getSurfaceView() || getBottomViews().contains(child); + if (result) { + isCloseBeforeDrag = getOpenStatus() == Status.Close; + } + return result; + } + + @Override + public int getViewHorizontalDragRange(View child) { + return mDragDistance; + } + + @Override + public int getViewVerticalDragRange(View child) { + return mDragDistance; + } + + boolean isCloseBeforeDrag = true; + + @Override + public void onViewReleased(View releasedChild, float xvel, float yvel) { + super.onViewReleased(releasedChild, xvel, yvel); + processHandRelease(xvel, yvel, isCloseBeforeDrag); + for (SwipeListener l : mSwipeListeners) { + l.onHandRelease(SwipeLayout.this, xvel, yvel); + } + + invalidate(); + } + + @Override + public void onViewPositionChanged(View changedView, int left, int top, int dx, int dy) { + View surfaceView = getSurfaceView(); + if (surfaceView == null) return; + View currentBottomView = getCurrentBottomView(); + int evLeft = surfaceView.getLeft(), + evRight = surfaceView.getRight(), + evTop = surfaceView.getTop(), + evBottom = surfaceView.getBottom(); + if (changedView == surfaceView) { + + if (mShowMode == ShowMode.PullOut && currentBottomView != null) { + if (mCurrentDragEdge == DragEdge.Left || mCurrentDragEdge == DragEdge.Right) { + currentBottomView.offsetLeftAndRight(dx); + } else { + currentBottomView.offsetTopAndBottom(dy); + } + } + + } else if (getBottomViews().contains(changedView)) { + + if (mShowMode == ShowMode.PullOut) { + surfaceView.offsetLeftAndRight(dx); + surfaceView.offsetTopAndBottom(dy); + } else { + Rect rect = computeBottomLayDown(mCurrentDragEdge); + if (currentBottomView != null) { + currentBottomView.layout(rect.left, rect.top, rect.right, rect.bottom); + } + + int newLeft = surfaceView.getLeft() + dx, newTop = surfaceView.getTop() + dy; + + if (mCurrentDragEdge == DragEdge.Left && newLeft < getPaddingLeft()) + newLeft = getPaddingLeft(); + else if (mCurrentDragEdge == DragEdge.Right && newLeft > getPaddingLeft()) + newLeft = getPaddingLeft(); + else if (mCurrentDragEdge == DragEdge.Top && newTop < getPaddingTop()) + newTop = getPaddingTop(); + else if (mCurrentDragEdge == DragEdge.Bottom && newTop > getPaddingTop()) + newTop = getPaddingTop(); + + surfaceView.layout(newLeft, newTop, newLeft + getMeasuredWidth(), newTop + getMeasuredHeight()); + } + } + + dispatchRevealEvent(evLeft, evTop, evRight, evBottom); + + dispatchSwipeEvent(evLeft, evTop, dx, dy); + + invalidate(); + + captureChildrenBound(); + } + }; + + private void captureChildrenBound() { + View currentBottomView = getCurrentBottomView(); + if (getOpenStatus() == Status.Close) { + mViewBoundCache.remove(currentBottomView); + return; + } + + View[] views = new View[]{getSurfaceView(), currentBottomView}; + for (View child : views) { + Rect rect = mViewBoundCache.get(child); + if (rect == null) { + rect = new Rect(); + mViewBoundCache.put(child, rect); + } + rect.left = child.getLeft(); + rect.top = child.getTop(); + rect.right = child.getRight(); + rect.bottom = child.getBottom(); + } + } + + /** + * the dispatchRevealEvent method may not always get accurate position, it + * makes the view may not always get the event when the view is totally + * show( fraction = 1), so , we need to calculate every time. + */ + protected boolean isViewTotallyFirstShowed(View child, Rect relativePosition, DragEdge edge, int surfaceLeft, + int surfaceTop, int surfaceRight, int surfaceBottom) { + if (mShowEntirely.get(child)) return false; + int childLeft = relativePosition.left; + int childRight = relativePosition.right; + int childTop = relativePosition.top; + int childBottom = relativePosition.bottom; + boolean r = false; + if (getShowMode() == ShowMode.LayDown) { + if ((edge == DragEdge.Right && surfaceRight <= childLeft) + || (edge == DragEdge.Left && surfaceLeft >= childRight) + || (edge == DragEdge.Top && surfaceTop >= childBottom) + || (edge == DragEdge.Bottom && surfaceBottom <= childTop)) r = true; + } else if (getShowMode() == ShowMode.PullOut) { + if ((edge == DragEdge.Right && childRight <= getWidth()) + || (edge == DragEdge.Left && childLeft >= getPaddingLeft()) + || (edge == DragEdge.Top && childTop >= getPaddingTop()) + || (edge == DragEdge.Bottom && childBottom <= getHeight())) r = true; + } + return r; + } + + protected boolean isViewShowing(View child, Rect relativePosition, DragEdge availableEdge, int surfaceLeft, + int surfaceTop, int surfaceRight, int surfaceBottom) { + int childLeft = relativePosition.left; + int childRight = relativePosition.right; + int childTop = relativePosition.top; + int childBottom = relativePosition.bottom; + if (getShowMode() == ShowMode.LayDown) { + switch (availableEdge) { + case Right: + if (surfaceRight > childLeft && surfaceRight <= childRight) { + return true; + } + break; + case Left: + if (surfaceLeft < childRight && surfaceLeft >= childLeft) { + return true; + } + break; + case Top: + if (surfaceTop >= childTop && surfaceTop < childBottom) { + return true; + } + break; + case Bottom: + if (surfaceBottom > childTop && surfaceBottom <= childBottom) { + return true; + } + break; + } + } else if (getShowMode() == ShowMode.PullOut) { + switch (availableEdge) { + case Right: + if (childLeft <= getWidth() && childRight > getWidth()) return true; + break; + case Left: + if (childRight >= getPaddingLeft() && childLeft < getPaddingLeft()) return true; + break; + case Top: + if (childTop < getPaddingTop() && childBottom >= getPaddingTop()) return true; + break; + case Bottom: + if (childTop < getHeight() && childTop >= getPaddingTop()) return true; + break; + } + } + return false; + } + + protected Rect getRelativePosition(View child) { + View t = child; + Rect r = new Rect(t.getLeft(), t.getTop(), 0, 0); + while (t.getParent() != null && t != getRootView()) { + t = (View) t.getParent(); + if (t == this) break; + r.left += t.getLeft(); + r.top += t.getTop(); + } + r.right = r.left + child.getMeasuredWidth(); + r.bottom = r.top + child.getMeasuredHeight(); + return r; + } + + private int mEventCounter = 0; + + protected void dispatchSwipeEvent(int surfaceLeft, int surfaceTop, int dx, int dy) { + DragEdge edge = getDragEdge(); + boolean open = true; + if (edge == DragEdge.Left) { + if (dx < 0) open = false; + } else if (edge == DragEdge.Right) { + if (dx > 0) open = false; + } else if (edge == DragEdge.Top) { + if (dy < 0) open = false; + } else if (edge == DragEdge.Bottom) { + if (dy > 0) open = false; + } + + dispatchSwipeEvent(surfaceLeft, surfaceTop, open); + } + + protected void dispatchSwipeEvent(int surfaceLeft, int surfaceTop, boolean open) { + safeBottomView(); + Status status = getOpenStatus(); + + if (!mSwipeListeners.isEmpty()) { + mEventCounter++; + for (SwipeListener l : mSwipeListeners) { + if (mEventCounter == 1) { + if (open) { + l.onStartOpen(this); + } else { + l.onStartClose(this); + } + } + l.onUpdate(SwipeLayout.this, surfaceLeft - getPaddingLeft(), surfaceTop - getPaddingTop()); + } + + if (status == Status.Close) { + for (SwipeListener l : mSwipeListeners) { + l.onClose(SwipeLayout.this); + } + mEventCounter = 0; + mClickToClose = false; + } + + if (status == Status.Open) { + View currentBottomView = getCurrentBottomView(); + if (currentBottomView != null) { + currentBottomView.setEnabled(true); + } + for (SwipeListener l : mSwipeListeners) { + l.onOpen(SwipeLayout.this); + } + mEventCounter = 0; + mClickToClose = true; + } + } + } + + /** + * prevent bottom view get any touch event. Especially in LayDown mode. + */ + private void safeBottomView() { + Status status = getOpenStatus(); + List bottoms = getBottomViews(); + + if (status == Status.Close) { + for (View bottom : bottoms) { + if (bottom != null && bottom.getVisibility() != INVISIBLE) { + bottom.setVisibility(INVISIBLE); + } + } + } else { + View currentBottomView = getCurrentBottomView(); + if (currentBottomView != null && currentBottomView.getVisibility() != VISIBLE) { + currentBottomView.setVisibility(VISIBLE); + } + } + } + + protected void dispatchRevealEvent(final int surfaceLeft, final int surfaceTop, final int surfaceRight, + final int surfaceBottom) { + if (mRevealListeners.isEmpty()) return; + for (Map.Entry> entry : mRevealListeners.entrySet()) { + View child = entry.getKey(); + Rect rect = getRelativePosition(child); + if (isViewShowing(child, rect, mCurrentDragEdge, surfaceLeft, surfaceTop, + surfaceRight, surfaceBottom)) { + mShowEntirely.put(child, false); + int distance = 0; + float fraction = 0f; + if (getShowMode() == ShowMode.LayDown) { + switch (mCurrentDragEdge) { + case Left: + distance = rect.left - surfaceLeft; + fraction = distance / (float) child.getWidth(); + break; + case Right: + distance = rect.right - surfaceRight; + fraction = distance / (float) child.getWidth(); + break; + case Top: + distance = rect.top - surfaceTop; + fraction = distance / (float) child.getHeight(); + break; + case Bottom: + distance = rect.bottom - surfaceBottom; + fraction = distance / (float) child.getHeight(); + break; + } + } else if (getShowMode() == ShowMode.PullOut) { + switch (mCurrentDragEdge) { + case Left: + distance = rect.right - getPaddingLeft(); + fraction = distance / (float) child.getWidth(); + break; + case Right: + distance = rect.left - getWidth(); + fraction = distance / (float) child.getWidth(); + break; + case Top: + distance = rect.bottom - getPaddingTop(); + fraction = distance / (float) child.getHeight(); + break; + case Bottom: + distance = rect.top - getHeight(); + fraction = distance / (float) child.getHeight(); + break; + } + } + + for (OnRevealListener l : entry.getValue()) { + l.onReveal(child, mCurrentDragEdge, Math.abs(fraction), distance); + if (Math.abs(fraction) == 1) { + mShowEntirely.put(child, true); + } + } + } + + if (isViewTotallyFirstShowed(child, rect, mCurrentDragEdge, surfaceLeft, surfaceTop, + surfaceRight, surfaceBottom)) { + mShowEntirely.put(child, true); + for (OnRevealListener l : entry.getValue()) { + if (mCurrentDragEdge == DragEdge.Left + || mCurrentDragEdge == DragEdge.Right) + l.onReveal(child, mCurrentDragEdge, 1, child.getWidth()); + else + l.onReveal(child, mCurrentDragEdge, 1, child.getHeight()); + } + } + + } + } + + @Override + public void computeScroll() { + super.computeScroll(); + if (mDragHelper.continueSettling(true)) { + ViewCompat.postInvalidateOnAnimation(this); + } + } + + /** + * {@link android.view.View.OnLayoutChangeListener} added in API 11. I need + * to support it from API 8. + */ + public interface OnLayout { + void onLayout(SwipeLayout v); + } + + private List mOnLayoutListeners; + + public void addOnLayoutListener(OnLayout l) { + if (mOnLayoutListeners == null) mOnLayoutListeners = new ArrayList(); + mOnLayoutListeners.add(l); + } + + public void removeOnLayoutListener(OnLayout l) { + if (mOnLayoutListeners != null) mOnLayoutListeners.remove(l); + } + + public void clearDragEdge() { + mDragEdges.clear(); + } + + public void setDrag(DragEdge dragEdge, int childId) { + clearDragEdge(); + addDrag(dragEdge, childId); + } + + public void setDrag(DragEdge dragEdge, View child) { + clearDragEdge(); + addDrag(dragEdge, child); + } + + public void addDrag(DragEdge dragEdge, int childId) { + addDrag(dragEdge, findViewById(childId), null); + } + + public void addDrag(DragEdge dragEdge, View child) { + addDrag(dragEdge, child, null); + } + + public void addDrag(DragEdge dragEdge, View child, ViewGroup.LayoutParams params) { + if (child == null) return; + + if (params == null) { + params = generateDefaultLayoutParams(); + } + if (!checkLayoutParams(params)) { + params = generateLayoutParams(params); + } + int gravity = -1; + switch (dragEdge) { + case Left: + gravity = Gravity.LEFT; + break; + case Right: + gravity = Gravity.RIGHT; + break; + case Top: + gravity = Gravity.TOP; + break; + case Bottom: + gravity = Gravity.BOTTOM; + break; + } + if (params instanceof LayoutParams) { + ((LayoutParams) params).gravity = gravity; + } + addView(child, 0, params); + } + + @Override + public void addView(View child, int index, ViewGroup.LayoutParams params) { + if (child == null) return; + int gravity = Gravity.NO_GRAVITY; + try { + gravity = (Integer) params.getClass().getField("gravity").get(params); + } catch (Exception e) { + e.printStackTrace(); + } + + if (gravity > 0) { + gravity = GravityCompat.getAbsoluteGravity(gravity, ViewCompat.getLayoutDirection(this)); + + if ((gravity & Gravity.LEFT) == Gravity.LEFT) { + mDragEdges.put(DragEdge.Left, child); + } + if ((gravity & Gravity.RIGHT) == Gravity.RIGHT) { + mDragEdges.put(DragEdge.Right, child); + } + if ((gravity & Gravity.TOP) == Gravity.TOP) { + mDragEdges.put(DragEdge.Top, child); + } + if ((gravity & Gravity.BOTTOM) == Gravity.BOTTOM) { + mDragEdges.put(DragEdge.Bottom, child); + } + } else { + for (Map.Entry entry : mDragEdges.entrySet()) { + if (entry.getValue() == null) { + //means used the drag_edge attr, the no gravity child should be use set + mDragEdges.put(entry.getKey(), child); + break; + } + } + } + if (child.getParent() == this) { + return; + } + super.addView(child, index, params); + } + + @Override + protected void onLayout(boolean changed, int l, int t, int r, int b) { + updateBottomViews(); + + if (mOnLayoutListeners != null) for (int i = 0; i < mOnLayoutListeners.size(); i++) { + mOnLayoutListeners.get(i).onLayout(this); + } + } + + void layoutPullOut() { + View surfaceView = getSurfaceView(); + Rect surfaceRect = mViewBoundCache.get(surfaceView); + if (surfaceRect == null) surfaceRect = computeSurfaceLayoutArea(false); + if (surfaceView != null) { + surfaceView.layout(surfaceRect.left, surfaceRect.top, surfaceRect.right, surfaceRect.bottom); + bringChildToFront(surfaceView); + } + View currentBottomView = getCurrentBottomView(); + Rect bottomViewRect = mViewBoundCache.get(currentBottomView); + if (bottomViewRect == null) + bottomViewRect = computeBottomLayoutAreaViaSurface(ShowMode.PullOut, surfaceRect); + if (currentBottomView != null) { + currentBottomView.layout(bottomViewRect.left, bottomViewRect.top, bottomViewRect.right, bottomViewRect.bottom); + } + } + + void layoutLayDown() { + View surfaceView = getSurfaceView(); + Rect surfaceRect = mViewBoundCache.get(surfaceView); + if (surfaceRect == null) surfaceRect = computeSurfaceLayoutArea(false); + if (surfaceView != null) { + surfaceView.layout(surfaceRect.left, surfaceRect.top, surfaceRect.right, surfaceRect.bottom); + bringChildToFront(surfaceView); + } + View currentBottomView = getCurrentBottomView(); + Rect bottomViewRect = mViewBoundCache.get(currentBottomView); + if (bottomViewRect == null) + bottomViewRect = computeBottomLayoutAreaViaSurface(ShowMode.LayDown, surfaceRect); + if (currentBottomView != null) { + currentBottomView.layout(bottomViewRect.left, bottomViewRect.top, bottomViewRect.right, bottomViewRect.bottom); + } + } + + private boolean mIsBeingDragged; + + private void checkCanDrag(MotionEvent ev) { + if (mIsBeingDragged) return; + if (getOpenStatus() == Status.Middle) { + mIsBeingDragged = true; + return; + } + Status status = getOpenStatus(); + float distanceX = ev.getRawX() - sX; + float distanceY = ev.getRawY() - sY; + float angle = Math.abs(distanceY / distanceX); + angle = (float) Math.toDegrees(Math.atan(angle)); + if (getOpenStatus() == Status.Close) { + DragEdge dragEdge; + if (angle < 45) { + if (distanceX > 0 && isLeftSwipeEnabled()) { + dragEdge = DragEdge.Left; + } else if (distanceX < 0 && isRightSwipeEnabled()) { + dragEdge = DragEdge.Right; + } else return; + + } else { + if (distanceY > 0 && isTopSwipeEnabled()) { + dragEdge = DragEdge.Top; + } else if (distanceY < 0 && isBottomSwipeEnabled()) { + dragEdge = DragEdge.Bottom; + } else return; + } + setCurrentDragEdge(dragEdge); + } + + boolean doNothing = false; + if (mCurrentDragEdge == DragEdge.Right) { + boolean suitable = (status == Status.Open && distanceX > mTouchSlop) + || (status == Status.Close && distanceX < -mTouchSlop); + suitable = suitable || (status == Status.Middle); + + if (angle > 30 || !suitable) { + doNothing = true; + } + } + + if (mCurrentDragEdge == DragEdge.Left) { + boolean suitable = (status == Status.Open && distanceX < -mTouchSlop) + || (status == Status.Close && distanceX > mTouchSlop); + suitable = suitable || status == Status.Middle; + + if (angle > 30 || !suitable) { + doNothing = true; + } + } + + if (mCurrentDragEdge == DragEdge.Top) { + boolean suitable = (status == Status.Open && distanceY < -mTouchSlop) + || (status == Status.Close && distanceY > mTouchSlop); + suitable = suitable || status == Status.Middle; + + if (angle < 60 || !suitable) { + doNothing = true; + } + } + + if (mCurrentDragEdge == DragEdge.Bottom) { + boolean suitable = (status == Status.Open && distanceY > mTouchSlop) + || (status == Status.Close && distanceY < -mTouchSlop); + suitable = suitable || status == Status.Middle; + + if (angle < 60 || !suitable) { + doNothing = true; + } + } + mIsBeingDragged = !doNothing; + } + + @Override + public boolean onInterceptTouchEvent(MotionEvent ev) { + if (!isSwipeEnabled()) { + return false; + } + if (mClickToClose && getOpenStatus() == Status.Open && isTouchOnSurface(ev)) { + return true; + } + for (SwipeDenier denier : mSwipeDeniers) { + if (denier != null && denier.shouldDenySwipe(ev)) { + return false; + } + } + + switch (ev.getAction()) { + case MotionEvent.ACTION_DOWN: + mDragHelper.processTouchEvent(ev); + mIsBeingDragged = false; + sX = ev.getRawX(); + sY = ev.getRawY(); + //if the swipe is in middle state(scrolling), should intercept the touch + if (getOpenStatus() == Status.Middle) { + mIsBeingDragged = true; + } + break; + case MotionEvent.ACTION_MOVE: + boolean beforeCheck = mIsBeingDragged; + checkCanDrag(ev); + if (mIsBeingDragged) { + ViewParent parent = getParent(); + if (parent != null) { + parent.requestDisallowInterceptTouchEvent(true); + } + } + if (!beforeCheck && mIsBeingDragged) { + //let children has one chance to catch the touch, and request the swipe not intercept + //useful when swipeLayout wrap a swipeLayout or other gestural layout + return false; + } + break; + + case MotionEvent.ACTION_CANCEL: + case MotionEvent.ACTION_UP: + mIsBeingDragged = false; + mDragHelper.processTouchEvent(ev); + break; + default://handle other action, such as ACTION_POINTER_DOWN/UP + mDragHelper.processTouchEvent(ev); + } + return mIsBeingDragged; + } + + private float sX = -1, sY = -1; + + @Override + public boolean onTouchEvent(MotionEvent event) { + if (!isSwipeEnabled()) return super.onTouchEvent(event); + + int action = event.getActionMasked(); + gestureDetector.onTouchEvent(event); + + switch (action) { + case MotionEvent.ACTION_DOWN: + mDragHelper.processTouchEvent(event); + sX = event.getRawX(); + sY = event.getRawY(); + + + case MotionEvent.ACTION_MOVE: { + //the drag state and the direction are already judged at onInterceptTouchEvent + checkCanDrag(event); + if (mIsBeingDragged) { + getParent().requestDisallowInterceptTouchEvent(true); + mDragHelper.processTouchEvent(event); + } + break; + } + case MotionEvent.ACTION_UP: + case MotionEvent.ACTION_CANCEL: + mIsBeingDragged = false; + mDragHelper.processTouchEvent(event); + break; + + default://handle other action, such as ACTION_POINTER_DOWN/UP + mDragHelper.processTouchEvent(event); + } + + return super.onTouchEvent(event) || mIsBeingDragged || action == MotionEvent.ACTION_DOWN; + } + + public boolean isClickToClose() { + return mClickToClose; + } + + public void setClickToClose(boolean mClickToClose) { + this.mClickToClose = mClickToClose; + } + + public void setSwipeEnabled(boolean enabled) { + mSwipeEnabled = enabled; + } + + public boolean isSwipeEnabled() { + return mSwipeEnabled; + } + + public boolean isLeftSwipeEnabled() { + View bottomView = mDragEdges.get(DragEdge.Left); + return bottomView != null && bottomView.getParent() == this + && bottomView != getSurfaceView() && mSwipesEnabled[DragEdge.Left.ordinal()]; + } + + public void setLeftSwipeEnabled(boolean leftSwipeEnabled) { + this.mSwipesEnabled[DragEdge.Left.ordinal()] = leftSwipeEnabled; + } + + public boolean isRightSwipeEnabled() { + View bottomView = mDragEdges.get(DragEdge.Right); + return bottomView != null && bottomView.getParent() == this + && bottomView != getSurfaceView() && mSwipesEnabled[DragEdge.Right.ordinal()]; + } + + public void setRightSwipeEnabled(boolean rightSwipeEnabled) { + this.mSwipesEnabled[DragEdge.Right.ordinal()] = rightSwipeEnabled; + } + + public boolean isTopSwipeEnabled() { + View bottomView = mDragEdges.get(DragEdge.Top); + return bottomView != null && bottomView.getParent() == this + && bottomView != getSurfaceView() && mSwipesEnabled[DragEdge.Top.ordinal()]; + } + + public void setTopSwipeEnabled(boolean topSwipeEnabled) { + this.mSwipesEnabled[DragEdge.Top.ordinal()] = topSwipeEnabled; + } + + public boolean isBottomSwipeEnabled() { + View bottomView = mDragEdges.get(DragEdge.Bottom); + return bottomView != null && bottomView.getParent() == this + && bottomView != getSurfaceView() && mSwipesEnabled[DragEdge.Bottom.ordinal()]; + } + + public void setBottomSwipeEnabled(boolean bottomSwipeEnabled) { + this.mSwipesEnabled[DragEdge.Bottom.ordinal()] = bottomSwipeEnabled; + } + + /*** + * Returns the percentage of revealing at which the view below should the view finish opening + * if it was already open before dragging + * + * @returns The percentage of view revealed to trigger, default value is 0.25 + */ + public float getWillOpenPercentAfterOpen() { + return mWillOpenPercentAfterOpen; + } + + /*** + * Allows to stablish at what percentage of revealing the view below should the view finish opening + * if it was already open before dragging + * + * @param willOpenPercentAfterOpen The percentage of view revealed to trigger, default value is 0.25 + */ + public void setWillOpenPercentAfterOpen(float willOpenPercentAfterOpen) { + this.mWillOpenPercentAfterOpen = willOpenPercentAfterOpen; + } + + /*** + * Returns the percentage of revealing at which the view below should the view finish opening + * if it was already closed before dragging + * + * @returns The percentage of view revealed to trigger, default value is 0.25 + */ + public float getWillOpenPercentAfterClose() { + return mWillOpenPercentAfterClose; + } + + /*** + * Allows to stablish at what percentage of revealing the view below should the view finish opening + * if it was already closed before dragging + * + * @param willOpenPercentAfterClose The percentage of view revealed to trigger, default value is 0.75 + */ + public void setWillOpenPercentAfterClose(float willOpenPercentAfterClose) { + this.mWillOpenPercentAfterClose = willOpenPercentAfterClose; + } + + private boolean insideAdapterView() { + return getAdapterView() != null; + } + + private AdapterView getAdapterView() { + ViewParent t = getParent(); + if (t instanceof AdapterView) { + return (AdapterView) t; + } + return null; + } + + public void performAdapterViewItemClick() { + if (getOpenStatus() != Status.Close) return; + ViewParent t = getParent(); + if (t instanceof AdapterView) { + AdapterView view = (AdapterView) t; + int p = view.getPositionForView(SwipeLayout.this); + if (p != AdapterView.INVALID_POSITION) { + view.performItemClick(view.getChildAt(p - view.getFirstVisiblePosition()), p, view + .getAdapter().getItemId(p)); + } + } + } + + private boolean performAdapterViewItemLongClick() { + if (getOpenStatus() != Status.Close) return false; + ViewParent t = getParent(); + if (t instanceof AdapterView) { + AdapterView view = (AdapterView) t; + int p = view.getPositionForView(SwipeLayout.this); + if (p == AdapterView.INVALID_POSITION) return false; + long vId = view.getItemIdAtPosition(p); + boolean handled = false; + try { + Method m = AbsListView.class.getDeclaredMethod("performLongPress", View.class, int.class, long.class); + m.setAccessible(true); + handled = (boolean) m.invoke(view, SwipeLayout.this, p, vId); + + } catch (Exception e) { + e.printStackTrace(); + + if (view.getOnItemLongClickListener() != null) { + handled = view.getOnItemLongClickListener().onItemLongClick(view, SwipeLayout.this, p, vId); + } + if (handled) { + view.performHapticFeedback(HapticFeedbackConstants.LONG_PRESS); + } + } + return handled; + } + return false; + } + + @Override + protected void onAttachedToWindow() { + super.onAttachedToWindow(); + if (insideAdapterView()) { + if (clickListener == null) { + setOnClickListener(new OnClickListener() { + @Override + public void onClick(View v) { + performAdapterViewItemClick(); + } + }); + } + if (longClickListener == null) { + setOnLongClickListener(new OnLongClickListener() { + @Override + public boolean onLongClick(View v) { + performAdapterViewItemLongClick(); + return true; + } + }); + } + } + } + + OnClickListener clickListener; + + @Override + public void setOnClickListener(OnClickListener l) { + super.setOnClickListener(l); + clickListener = l; + } + + OnLongClickListener longClickListener; + + @Override + public void setOnLongClickListener(OnLongClickListener l) { + super.setOnLongClickListener(l); + longClickListener = l; + } + + private Rect hitSurfaceRect; + + private boolean isTouchOnSurface(MotionEvent ev) { + View surfaceView = getSurfaceView(); + if (surfaceView == null) { + return false; + } + if (hitSurfaceRect == null) { + hitSurfaceRect = new Rect(); + } + surfaceView.getHitRect(hitSurfaceRect); + return hitSurfaceRect.contains((int) ev.getX(), (int) ev.getY()); + } + + private GestureDetector gestureDetector = new GestureDetector(getContext(), new SwipeDetector()); + + class SwipeDetector extends GestureDetector.SimpleOnGestureListener { + @Override + public boolean onSingleTapUp(MotionEvent e) { + if (isTouchOnSurface(e)) { + if (mClickToClose) { + close(); + } else { + if (mDoubleClickListener != null) { + mDoubleClickListener.onClick(); + } + } + } + return super.onSingleTapUp(e); + } + + @Override + public boolean onDoubleTap(MotionEvent e) { + if (mDoubleClickListener != null) { + View target; + View bottom = getCurrentBottomView(); + View surface = getSurfaceView(); + if (bottom != null && e.getX() > bottom.getLeft() && e.getX() < bottom.getRight() + && e.getY() > bottom.getTop() && e.getY() < bottom.getBottom()) { + target = bottom; + } else { + target = surface; + } + mDoubleClickListener.onDoubleClick(SwipeLayout.this, target == surface); + } + return true; + } + } + + /** + * set the drag distance, it will force set the bottom view's width or + * height via this value. + * + * @param max max distance in dp unit + */ + public void setDragDistance(int max) { + if (max < 0) max = 0; + mDragDistance = dp2px(max); + requestLayout(); + } + + /** + * There are 2 diffirent show mode. + * {@link com.tencent.qcloud.tuikit.timcommon.component.swipe.SwipeLayout.ShowMode}.PullOut and + * {@link com.tencent.qcloud.tuikit.timcommon.component.swipe.SwipeLayout.ShowMode}.LayDown. + * + * @param mode + */ + public void setShowMode(ShowMode mode) { + mShowMode = mode; + requestLayout(); + } + + public DragEdge getDragEdge() { + return mCurrentDragEdge; + } + + public int getDragDistance() { + return mDragDistance; + } + + public ShowMode getShowMode() { + return mShowMode; + } + + /** + * return null if there is no surface view(no children) + */ + public View getSurfaceView() { + if (getChildCount() == 0) return null; + return getChildAt(getChildCount() - 1); + } + + /** + * return null if there is no bottom view + */ + public View getCurrentBottomView() { + List bottoms = getBottomViews(); + if (mCurrentDragEdge.ordinal() < bottoms.size()) { + return bottoms.get(mCurrentDragEdge.ordinal()); + } + return null; + } + + /** + * @return all bottomViews: left, top, right, bottom (may null if the edge is not set) + */ + public List getBottomViews() { + ArrayList bottoms = new ArrayList(); + for (DragEdge dragEdge : DragEdge.values()) { + bottoms.add(mDragEdges.get(dragEdge)); + } + return bottoms; + } + + public enum Status { + Middle, + Open, + Close + } + + /** + * get the open status. + * + * @return {@link com.tencent.qcloud.tuikit.timcommon.component.swipe.SwipeLayout.Status} Open , Close or + * Middle. + */ + public Status getOpenStatus() { + View surfaceView = getSurfaceView(); + if (surfaceView == null) { + return Status.Close; + } + int surfaceLeft = surfaceView.getLeft(); + int surfaceTop = surfaceView.getTop(); + if (surfaceLeft == getPaddingLeft() && surfaceTop == getPaddingTop()) return Status.Close; + + if (surfaceLeft == (getPaddingLeft() - mDragDistance) || surfaceLeft == (getPaddingLeft() + mDragDistance) + || surfaceTop == (getPaddingTop() - mDragDistance) || surfaceTop == (getPaddingTop() + mDragDistance)) + return Status.Open; + + return Status.Middle; + } + + + /** + * Process the surface release event. + * + * @param xvel xVelocity + * @param yvel yVelocity + * @param isCloseBeforeDragged the open state before drag + */ + protected void processHandRelease(float xvel, float yvel, boolean isCloseBeforeDragged) { + float minVelocity = mDragHelper.getMinVelocity(); + View surfaceView = getSurfaceView(); + DragEdge currentDragEdge = mCurrentDragEdge; + if (currentDragEdge == null || surfaceView == null) { + return; + } + float willOpenPercent = (isCloseBeforeDragged ? mWillOpenPercentAfterClose : mWillOpenPercentAfterOpen); + if (currentDragEdge == DragEdge.Left) { + if (xvel > minVelocity) open(); + else if (xvel < -minVelocity) close(); + else { + float openPercent = 1f * getSurfaceView().getLeft() / mDragDistance; + if (openPercent > willOpenPercent) open(); + else close(); + } + } else if (currentDragEdge == DragEdge.Right) { + if (xvel > minVelocity) close(); + else if (xvel < -minVelocity) open(); + else { + float openPercent = 1f * (-getSurfaceView().getLeft()) / mDragDistance; + if (openPercent > willOpenPercent) open(); + else close(); + } + } else if (currentDragEdge == DragEdge.Top) { + if (yvel > minVelocity) open(); + else if (yvel < -minVelocity) close(); + else { + float openPercent = 1f * getSurfaceView().getTop() / mDragDistance; + if (openPercent > willOpenPercent) open(); + else close(); + } + } else if (currentDragEdge == DragEdge.Bottom) { + if (yvel > minVelocity) close(); + else if (yvel < -minVelocity) open(); + else { + float openPercent = 1f * (-getSurfaceView().getTop()) / mDragDistance; + if (openPercent > willOpenPercent) open(); + else close(); + } + } + } + + /** + * smoothly open surface. + */ + public void open() { + open(true, true); + } + + public void open(boolean smooth) { + open(smooth, true); + } + + public void open(boolean smooth, boolean notify) { + View surface = getSurfaceView(), bottom = getCurrentBottomView(); + if (surface == null) { + return; + } + int dx, dy; + Rect rect = computeSurfaceLayoutArea(true); + if (smooth) { + mDragHelper.smoothSlideViewTo(surface, rect.left, rect.top); + } else { + dx = rect.left - surface.getLeft(); + dy = rect.top - surface.getTop(); + surface.layout(rect.left, rect.top, rect.right, rect.bottom); + if (getShowMode() == ShowMode.PullOut) { + Rect bRect = computeBottomLayoutAreaViaSurface(ShowMode.PullOut, rect); + if (bottom != null) { + bottom.layout(bRect.left, bRect.top, bRect.right, bRect.bottom); + } + } + if (notify) { + dispatchRevealEvent(rect.left, rect.top, rect.right, rect.bottom); + dispatchSwipeEvent(rect.left, rect.top, dx, dy); + } else { + safeBottomView(); + } + } + invalidate(); + } + + public void open(DragEdge edge) { + setCurrentDragEdge(edge); + open(true, true); + } + + public void open(boolean smooth, DragEdge edge) { + setCurrentDragEdge(edge); + open(smooth, true); + } + + public void open(boolean smooth, boolean notify, DragEdge edge) { + setCurrentDragEdge(edge); + open(smooth, notify); + } + + /** + * smoothly close surface. + */ + public void close() { + close(true, true); + } + + public void close(boolean smooth) { + close(smooth, true); + } + + /** + * close surface + * + * @param smooth smoothly or not. + * @param notify if notify all the listeners. + */ + public void close(boolean smooth, boolean notify) { + View surface = getSurfaceView(); + if (surface == null) { + return; + } + int dx, dy; + if (smooth) + mDragHelper.smoothSlideViewTo(getSurfaceView(), getPaddingLeft(), getPaddingTop()); + else { + Rect rect = computeSurfaceLayoutArea(false); + dx = rect.left - surface.getLeft(); + dy = rect.top - surface.getTop(); + surface.layout(rect.left, rect.top, rect.right, rect.bottom); + if (notify) { + dispatchRevealEvent(rect.left, rect.top, rect.right, rect.bottom); + dispatchSwipeEvent(rect.left, rect.top, dx, dy); + } else { + safeBottomView(); + } + } + invalidate(); + } + + public void toggle() { + toggle(true); + } + + public void toggle(boolean smooth) { + if (getOpenStatus() == Status.Open) + close(smooth); + else if (getOpenStatus() == Status.Close) open(smooth); + } + + + /** + * a helper function to compute the Rect area that surface will hold in. + * + * @param open open status or close status. + */ + private Rect computeSurfaceLayoutArea(boolean open) { + int l = getPaddingLeft(), t = getPaddingTop(); + if (open) { + if (mCurrentDragEdge == DragEdge.Left) + l = getPaddingLeft() + mDragDistance; + else if (mCurrentDragEdge == DragEdge.Right) + l = getPaddingLeft() - mDragDistance; + else if (mCurrentDragEdge == DragEdge.Top) + t = getPaddingTop() + mDragDistance; + else t = getPaddingTop() - mDragDistance; + } + return new Rect(l, t, l + getMeasuredWidth(), t + getMeasuredHeight()); + } + + private Rect computeBottomLayoutAreaViaSurface(ShowMode mode, Rect surfaceArea) { + Rect rect = surfaceArea; + View bottomView = getCurrentBottomView(); + + int bl = rect.left, bt = rect.top, br = rect.right, bb = rect.bottom; + if (mode == ShowMode.PullOut) { + if (mCurrentDragEdge == DragEdge.Left) + bl = rect.left - mDragDistance; + else if (mCurrentDragEdge == DragEdge.Right) + bl = rect.right; + else if (mCurrentDragEdge == DragEdge.Top) + bt = rect.top - mDragDistance; + else bt = rect.bottom; + + if (mCurrentDragEdge == DragEdge.Left || mCurrentDragEdge == DragEdge.Right) { + bb = rect.bottom; + br = bl + (bottomView == null ? 0 : bottomView.getMeasuredWidth()); + } else { + bb = bt + (bottomView == null ? 0 : bottomView.getMeasuredHeight()); + br = rect.right; + } + } else if (mode == ShowMode.LayDown) { + if (mCurrentDragEdge == DragEdge.Left) + br = bl + mDragDistance; + else if (mCurrentDragEdge == DragEdge.Right) + bl = br - mDragDistance; + else if (mCurrentDragEdge == DragEdge.Top) + bb = bt + mDragDistance; + else bt = bb - mDragDistance; + + } + return new Rect(bl, bt, br, bb); + + } + + private Rect computeBottomLayDown(DragEdge dragEdge) { + int bl = getPaddingLeft(), bt = getPaddingTop(); + int br, bb; + if (dragEdge == DragEdge.Right) { + bl = getMeasuredWidth() - mDragDistance; + } else if (dragEdge == DragEdge.Bottom) { + bt = getMeasuredHeight() - mDragDistance; + } + if (dragEdge == DragEdge.Left || dragEdge == DragEdge.Right) { + br = bl + mDragDistance; + bb = bt + getMeasuredHeight(); + } else { + br = bl + getMeasuredWidth(); + bb = bt + mDragDistance; + } + return new Rect(bl, bt, br, bb); + } + + public void setOnDoubleClickListener(DoubleClickListener doubleClickListener) { + mDoubleClickListener = doubleClickListener; + } + + public interface DoubleClickListener { + void onDoubleClick(SwipeLayout layout, boolean surface); + void onClick(); + } + + private int dp2px(float dp) { + return (int) (dp * getContext().getResources().getDisplayMetrics().density + 0.5f); + } + + + /** + * Deprecated, use {@link #setDrag(com.tencent.qcloud.tuikit.timcommon.component.swipe.SwipeLayout.DragEdge, android.view.View)} + */ + @Deprecated + public void setDragEdge(DragEdge dragEdge) { + clearDragEdge(); + if (getChildCount() >= 2) { + mDragEdges.put(dragEdge, getChildAt(getChildCount() - 2)); + } + setCurrentDragEdge(dragEdge); + } + + public void onViewRemoved(View child) { + for (Map.Entry entry : new HashMap(mDragEdges).entrySet()) { + if (entry.getValue() == child) { + mDragEdges.remove(entry.getKey()); + } + } + } + + public Map getDragEdgeMap() { + return mDragEdges; + } + + /** + * Deprecated, use {@link #getDragEdgeMap()} + */ + @Deprecated + public List getDragEdges() { + return new ArrayList(mDragEdges.keySet()); + } + + /** + * Deprecated, use {@link #setDrag(com.tencent.qcloud.tuikit.timcommon.component.swipe.SwipeLayout.DragEdge, android.view.View)} + */ + @Deprecated + public void setDragEdges(List dragEdges) { + clearDragEdge(); + for (int i = 0, size = Math.min(dragEdges.size(), getChildCount() - 1); i < size; i++) { + DragEdge dragEdge = dragEdges.get(i); + mDragEdges.put(dragEdge, getChildAt(i)); + } + if (dragEdges.size() == 0 || dragEdges.contains(DefaultDragEdge)) { + setCurrentDragEdge(DefaultDragEdge); + } else { + setCurrentDragEdge(dragEdges.get(0)); + } + } + + /** + * Deprecated, use {@link #addDrag(com.tencent.qcloud.tuikit.timcommon.component.swipe.SwipeLayout.DragEdge, android.view.View)} + */ + @Deprecated + public void setDragEdges(DragEdge... mDragEdges) { + clearDragEdge(); + setDragEdges(Arrays.asList(mDragEdges)); + } + + /** + * Deprecated, use {@link #addDrag(com.tencent.qcloud.tuikit.timcommon.component.swipe.SwipeLayout.DragEdge, android.view.View)} + * When using multiple drag edges it's a good idea to pass the ids of the views that + * you're using for the left, right, top bottom views (-1 if you're not using a particular view) + */ + @Deprecated + public void setBottomViewIds(int leftId, int rightId, int topId, int bottomId) { + addDrag(DragEdge.Left, findViewById(leftId)); + addDrag(DragEdge.Right, findViewById(rightId)); + addDrag(DragEdge.Top, findViewById(topId)); + addDrag(DragEdge.Bottom, findViewById(bottomId)); + } + + private float getCurrentOffset() { + if (mCurrentDragEdge == null) return 0; + return mEdgeSwipesOffset[mCurrentDragEdge.ordinal()]; + } + + private void setCurrentDragEdge(DragEdge dragEdge) { + mCurrentDragEdge = dragEdge; + updateBottomViews(); + } + + private void updateBottomViews() { + View currentBottomView = getCurrentBottomView(); + if (currentBottomView != null) { + if (mCurrentDragEdge == DragEdge.Left || mCurrentDragEdge == DragEdge.Right) { + mDragDistance = currentBottomView.getMeasuredWidth() - dp2px(getCurrentOffset()); + } else { + mDragDistance = currentBottomView.getMeasuredHeight() - dp2px(getCurrentOffset()); + } + } + + if (mShowMode == ShowMode.PullOut) { + layoutPullOut(); + } else if (mShowMode == ShowMode.LayDown) { + layoutLayDown(); + } + + safeBottomView(); + } +} diff --git a/TIMCommon/timcommon/src/main/java/com/tencent/qcloud/tuikit/timcommon/interfaces/ICommonMessageAdapter.java b/TIMCommon/timcommon/src/main/java/com/tencent/qcloud/tuikit/timcommon/interfaces/ICommonMessageAdapter.java new file mode 100644 index 0000000..d8a7b1d --- /dev/null +++ b/TIMCommon/timcommon/src/main/java/com/tencent/qcloud/tuikit/timcommon/interfaces/ICommonMessageAdapter.java @@ -0,0 +1,8 @@ +package com.tencent.qcloud.tuikit.timcommon.interfaces; + +import com.tencent.qcloud.tuikit.timcommon.bean.TUIMessageBean; + +public interface ICommonMessageAdapter { + TUIMessageBean getItem(int position); + void notifyItemChanged(int position); +} diff --git a/TIMCommon/timcommon/src/main/java/com/tencent/qcloud/tuikit/timcommon/interfaces/IMessageProperties.java b/TIMCommon/timcommon/src/main/java/com/tencent/qcloud/tuikit/timcommon/interfaces/IMessageProperties.java new file mode 100644 index 0000000..2c44b34 --- /dev/null +++ b/TIMCommon/timcommon/src/main/java/com/tencent/qcloud/tuikit/timcommon/interfaces/IMessageProperties.java @@ -0,0 +1,353 @@ +package com.tencent.qcloud.tuikit.timcommon.interfaces; + +import android.graphics.drawable.Drawable; + +public interface IMessageProperties { + + /// @name 设置头像 Set Avatar + /// @{ + + /** + * 获取默认头像 + * + * Get default avatar + * + * @return + */ + int getAvatar(); + + /** + * 设置默认头像,默认与左边与右边的头像相同 + * + * Set the default avatar, the default is the same as the avatar on the left and right + * + * @param resId + */ + void setAvatar(int resId); + + /** + * 获取头像圆角 + * + * Get avatar rounded corners + * + * @return + */ + int getAvatarRadius(); + + /** + * 设置头像圆角 + * + * Set avatar rounded corners + * + * @param radius + */ + void setAvatarRadius(int radius); + + /** + * 获得头像大小 + * + * Get avatar size + * + * @return + */ + int[] getAvatarSize(); + + /** + * 设置头像大小 + * + * Set avatar size + * + * @param size + */ + void setAvatarSize(int[] size); + + /// @} + /// @name 设置昵称样式 Set nickname style + /// @{ + + /** + * 获得昵称文字大小 + * + * Get nickname text size + * + * @return + */ + int getNameFontSize(); + + /** + * 设置昵称文字大小 + * + * Set nickname text size + * + * @param size + */ + void setNameFontSize(int size); + + /** + * 获取昵称文字颜色 + * + * Get nickname text color + * + * @return + */ + int getNameFontColor(); + + /** + * 设置昵称文字颜色 + * + * Set nickname text color + * + * @param color + */ + void setNameFontColor(int color); + + /** + * 获取左边昵称显示状态 + * + * Get the display status of the nickname on the left + * + * @return + */ + int getLeftNameVisibility(); + + /** + * 设置左边昵称是否显示 + * + * Set the display status of the nickname on the left + * + * @param visibility + */ + void setLeftNameVisibility(int visibility); + + /** + * 获取右边昵称显示状态 + * + * Get the display status of the nickname on the right + * + * @return + */ + int getRightNameVisibility(); + + /** + * 设置右边昵称是否显示 + * + * Set the display status of the nickname on the right + * + * @param visibility + */ + void setRightNameVisibility(int visibility); + + /// @} + /// @name 设置气泡 set bubbles + /// @{ + + /** + * 获取右边聊天气泡的背景 + * + * Get the background of the chat bubble on the right + * + * @return + */ + Drawable getRightBubble(); + + /** + * 设置右边聊天气泡的背景 + * + * Set the background of the chat bubble on the right + * + * @param drawable + */ + void setRightBubble(Drawable drawable); + + /** + * 获取左边聊天气泡的背景 + * + * Get the background of the left chat bubble + * + * @return + */ + Drawable getLeftBubble(); + + /** + * 设置左边聊天气泡的背景 + * + * Set the background of the left chat bubble + * + * @param drawable + */ + void setLeftBubble(Drawable drawable); + + /// @} + /// @name 设置聊天内容 Set chat content + /// @{ + + /** + * 获取聊天内容字体大小 + * + * Get chat content font size + * + * @return + */ + int getChatContextFontSize(); + + /** + * 设置聊天内容字体大小 + * + * Set chat content font size + * + * @param size + */ + void setChatContextFontSize(int size); + + /** + * 获取右边聊天内容字体颜色 + * + * Get the font color of the chat content on the right + * + * @return + */ + int getRightChatContentFontColor(); + + /** + * 设置右边聊天内容字体颜色 + * + * Set the font color of the chat content on the right + * + * @param color + */ + void setRightChatContentFontColor(int color); + + /** + * 获取左边聊天内容字体颜色 + * + * Get the font color of the chat content on the left + * + * @return + */ + int getLeftChatContentFontColor(); + + /** + * 设置左边聊天内容字体颜色 + * + * Set the font color of the chat content on the left + * + * @param color + */ + void setLeftChatContentFontColor(int color); + + /// @} + /// @name 设置聊天时间 Set chat time + /// @{ + + /** + * 获取聊天时间的背景 + * + * Get the context of the chat time + * + * @return + */ + Drawable getChatTimeBubble(); + + /** + * 设置聊天时间的背景 + * + * Set the context of the chat time + * + * @param drawable + */ + void setChatTimeBubble(Drawable drawable); + + /** + * 获取聊天时间的文字大小 + * + * Get the text size of the chat time + * + * @return + */ + int getChatTimeFontSize(); + + /** + * 设置聊天时间的字体大小 + * + * Set the text size of the chat time + * + * @param size + */ + void setChatTimeFontSize(int size); + + /** + * 获取聊天时间的字体颜色 + * + * Get the font color of chat time + * + * @return + */ + int getChatTimeFontColor(); + + /** + * 设置聊天时间的字体颜色 + * + * Set the font color of chat time + * + * @param color + */ + void setChatTimeFontColor(int color); + + /// @} + /// @name 设置聊天的提示信息 Set up chat alerts + /// @{ + + /** + * 获取聊天提示信息的背景 + * + * Get context for chat alerts + * + * @return + */ + Drawable getTipsMessageBubble(); + + /** + * 设置聊天提示信息的背景 + * + * Set context for chat alerts + * + * @param drawable + */ + void setTipsMessageBubble(Drawable drawable); + + /** + * 获取聊天提示信息的文字大小 + * + * Get the text size of the chat prompt message + * + * @return + */ + int getTipsMessageFontSize(); + + /** + * 设置聊天提示信息的文字大小 + * + * Set the text size of the chat prompt message + * + * @param size + */ + void setTipsMessageFontSize(int size); + + /** + * 获取聊天提示信息的文字颜色 + * + * Get the text color of the chat prompt message + * + * @return + */ + int getTipsMessageFontColor(); + + /** + * 设置聊天提示信息的文字颜色 + * + * Set the text color of the chat prompt message + * + * @param color + */ + void setTipsMessageFontColor(int color); +} diff --git a/TIMCommon/timcommon/src/main/java/com/tencent/qcloud/tuikit/timcommon/interfaces/OnChatInputMoreActionClickListener.java b/TIMCommon/timcommon/src/main/java/com/tencent/qcloud/tuikit/timcommon/interfaces/OnChatInputMoreActionClickListener.java new file mode 100644 index 0000000..c5e7f80 --- /dev/null +++ b/TIMCommon/timcommon/src/main/java/com/tencent/qcloud/tuikit/timcommon/interfaces/OnChatInputMoreActionClickListener.java @@ -0,0 +1,9 @@ +package com.tencent.qcloud.tuikit.timcommon.interfaces; + +import com.tencent.qcloud.tuikit.timcommon.bean.TUIMessageBean; + +public abstract class OnChatInputMoreActionClickListener { + + public void onSendMessageClick(TUIMessageBean msg) {} + +} diff --git a/TIMCommon/timcommon/src/main/java/com/tencent/qcloud/tuikit/timcommon/interfaces/OnChatPopActionClickListener.java b/TIMCommon/timcommon/src/main/java/com/tencent/qcloud/tuikit/timcommon/interfaces/OnChatPopActionClickListener.java new file mode 100644 index 0000000..dbec7b2 --- /dev/null +++ b/TIMCommon/timcommon/src/main/java/com/tencent/qcloud/tuikit/timcommon/interfaces/OnChatPopActionClickListener.java @@ -0,0 +1,25 @@ +package com.tencent.qcloud.tuikit.timcommon.interfaces; + +import com.tencent.qcloud.tuikit.timcommon.bean.TUIMessageBean; + +public abstract class OnChatPopActionClickListener { + + public void onCopyClick(TUIMessageBean msg) {} + + public void onSendMessageClick(TUIMessageBean msg, boolean retry) {} + + public void onDeleteMessageClick(TUIMessageBean msg) {} + + public void onRevokeMessageClick(TUIMessageBean msg) {} + + public void onMultiSelectMessageClick(TUIMessageBean msg) {} + + public void onForwardMessageClick(TUIMessageBean msg) {} + + public void onReplyMessageClick(TUIMessageBean msg) {} + + public void onQuoteMessageClick(TUIMessageBean msg) {} + + public void onInfoMessageClick(TUIMessageBean msg) {} + +} diff --git a/TIMCommon/timcommon/src/main/java/com/tencent/qcloud/tuikit/timcommon/interfaces/OnItemClickListener.java b/TIMCommon/timcommon/src/main/java/com/tencent/qcloud/tuikit/timcommon/interfaces/OnItemClickListener.java new file mode 100644 index 0000000..dce3fff --- /dev/null +++ b/TIMCommon/timcommon/src/main/java/com/tencent/qcloud/tuikit/timcommon/interfaces/OnItemClickListener.java @@ -0,0 +1,32 @@ +package com.tencent.qcloud.tuikit.timcommon.interfaces; + +import android.view.View; + +import com.tencent.qcloud.tuikit.timcommon.bean.TUIMessageBean; + +public abstract class OnItemClickListener { + public void onMessageLongClick(View view, int position, TUIMessageBean messageBean) {} + + public void onMessageClick(View view, int position, TUIMessageBean messageBean) {} + + public void onUserIconClick(View view, int position, TUIMessageBean messageBean) {} + + public void onUserIconLongClick(View view, int position, TUIMessageBean messageBean) {} + + public void onReEditRevokeMessage(View view, int position, TUIMessageBean messageBean) {} + + public void onRecallClick(View view, int position, TUIMessageBean messageBean) {} + + public void onReplyMessageClick(View view, int position, TUIMessageBean messageBean) {} + + public void onReplyDetailClick(TUIMessageBean messageBean) {} + + public void onReactOnClick(String emojiId, TUIMessageBean messageBean) {} + + public void onSendFailBtnClick(View view, int position, TUIMessageBean messageBean) {} + + public void onTextSelected(View view, int position, TUIMessageBean messageBean) {} + + public void onMessageReadStatusClick(View view, TUIMessageBean messageBean) {} + +} diff --git a/TIMCommon/timcommon/src/main/java/com/tencent/qcloud/tuikit/timcommon/minimalistui/widget/message/ChatReactView.java b/TIMCommon/timcommon/src/main/java/com/tencent/qcloud/tuikit/timcommon/minimalistui/widget/message/ChatReactView.java new file mode 100644 index 0000000..d317f0a --- /dev/null +++ b/TIMCommon/timcommon/src/main/java/com/tencent/qcloud/tuikit/timcommon/minimalistui/widget/message/ChatReactView.java @@ -0,0 +1,118 @@ +package com.tencent.qcloud.tuikit.timcommon.minimalistui.widget.message; + +import android.content.Context; +import android.graphics.Bitmap; +import android.util.AttributeSet; +import android.view.LayoutInflater; +import android.view.View; +import android.view.ViewGroup; +import android.widget.FrameLayout; +import android.widget.ImageView; +import android.widget.TextView; + +import androidx.annotation.NonNull; +import androidx.annotation.Nullable; +import androidx.recyclerview.widget.LinearLayoutManager; +import androidx.recyclerview.widget.RecyclerView; + +import com.tencent.qcloud.tuikit.timcommon.R; +import com.tencent.qcloud.tuikit.timcommon.bean.MessageReactBean; +import com.tencent.qcloud.tuikit.timcommon.component.face.FaceManager; + +import java.util.ArrayList; +import java.util.Map; +import java.util.Set; + +public class ChatReactView extends FrameLayout { + + private static final int EMOJI_LIMIT = 5; + private LinearLayoutManager layoutManager; + private TextView reactNumText; + private View moreIcon; + private RecyclerView reactRecyclerView; + private ChatReactAdapter adapter; + public ChatReactView(@NonNull Context context) { + super(context); + initView(); + } + + public ChatReactView(@NonNull Context context, @Nullable AttributeSet attrs) { + super(context, attrs); + initView(); + } + + public ChatReactView(@NonNull Context context, @Nullable AttributeSet attrs, int defStyleAttr) { + super(context, attrs, defStyleAttr); + initView(); + } + + private void initView() { + View view = LayoutInflater.from(getContext()).inflate(R.layout.chat_minimalist_react_preview_layout, this); + reactRecyclerView = view.findViewById(R.id.reacts_emoji_list); + reactNumText = view.findViewById(R.id.reacts_num_text); + moreIcon = view.findViewById(R.id.more_icon); + layoutManager = new LinearLayoutManager(getContext()); + layoutManager.setOrientation(RecyclerView.HORIZONTAL); + reactRecyclerView.setLayoutManager(layoutManager); + adapter = new ChatReactAdapter(); + reactRecyclerView.setAdapter(adapter); + } + + public void setData(MessageReactBean reactBean) { + Map> reactsMap = reactBean.getReacts(); + int num = 0; + for (Set strings : reactsMap.values()) { + num += strings.size(); + } + reactNumText.setText(num + ""); + if (num > EMOJI_LIMIT) { + moreIcon.setVisibility(VISIBLE); + } else { + moreIcon.setVisibility(GONE); + } + if (adapter != null) { + adapter.setData(reactBean); + adapter.notifyDataSetChanged(); + } + } + + static class ChatReactAdapter extends RecyclerView.Adapter { + private MessageReactBean data; + + public void setData(MessageReactBean data) { + this.data = data; + } + + @NonNull + @Override + public ChatReactViewHolder onCreateViewHolder(@NonNull ViewGroup parent, int viewType) { + View view = LayoutInflater.from(parent.getContext()).inflate(R.layout.chat_minimalist_react_item_layout, parent, false); + return new ChatReactViewHolder(view); + } + + @Override + public void onBindViewHolder(@NonNull ChatReactViewHolder holder, int position) { + Map.Entry> entry = new ArrayList<>(data.getReacts().entrySet()).get(position); + String emojiId = entry.getKey(); + Bitmap bitmap = FaceManager.getEmoji(emojiId); + holder.faceImageView.setImageBitmap(bitmap); + } + + @Override + public int getItemCount() { + if (data != null) { + return Math.min(data.getReactSize(), EMOJI_LIMIT); + } + return 0; + } + } + + static class ChatReactViewHolder extends RecyclerView.ViewHolder { + public ImageView faceImageView; + public ChatReactViewHolder(@NonNull View itemView) { + super(itemView); + faceImageView = itemView.findViewById(R.id.face_iv); + } + } + +} diff --git a/TIMCommon/timcommon/src/main/java/com/tencent/qcloud/tuikit/timcommon/minimalistui/widget/message/MessageBaseHolder.java b/TIMCommon/timcommon/src/main/java/com/tencent/qcloud/tuikit/timcommon/minimalistui/widget/message/MessageBaseHolder.java new file mode 100644 index 0000000..1cdec92 --- /dev/null +++ b/TIMCommon/timcommon/src/main/java/com/tencent/qcloud/tuikit/timcommon/minimalistui/widget/message/MessageBaseHolder.java @@ -0,0 +1,232 @@ +package com.tencent.qcloud.tuikit.timcommon.minimalistui.widget.message; + +import android.animation.Animator; +import android.animation.ArgbEvaluator; +import android.animation.ValueAnimator; +import android.content.Context; +import android.graphics.PorterDuff; +import android.graphics.drawable.Drawable; +import android.view.View; +import android.widget.CheckBox; +import android.widget.FrameLayout; +import android.widget.LinearLayout; +import android.widget.TextView; + +import androidx.recyclerview.widget.RecyclerView; + +import com.tencent.qcloud.tuicore.TUIConfig; +import com.tencent.qcloud.tuicore.TUIThemeManager; +import com.tencent.qcloud.tuikit.timcommon.R; +import com.tencent.qcloud.tuikit.timcommon.bean.TUIMessageBean; +import com.tencent.qcloud.tuikit.timcommon.component.MessageProperties; +import com.tencent.qcloud.tuikit.timcommon.interfaces.ICommonMessageAdapter; +import com.tencent.qcloud.tuikit.timcommon.interfaces.OnItemClickListener; + +import java.util.Calendar; +import java.util.Date; +import java.util.Locale; + +public abstract class MessageBaseHolder extends RecyclerView.ViewHolder { + public static final int MSG_TYPE_HEADER_VIEW = -99; + + public ICommonMessageAdapter mAdapter; + public MessageProperties properties = MessageProperties.getInstance(); + protected OnItemClickListener onItemClickListener; + + public FrameLayout msgContentFrame; + public LinearLayout msgArea; + public LinearLayout msgAreaAndReply; + public ChatReactView reactView; + public CheckBox mMutiSelectCheckBox; + public View mContentLayout; + + private ValueAnimator highLightAnimator; + public TextView chatTimeText; + + protected boolean floatMode = false; + + protected boolean isShowStart = true; + + public MessageBaseHolder(View itemView) { + super(itemView); + msgContentFrame = itemView.findViewById(R.id.msg_content_fl); + reactView = itemView.findViewById(R.id.reacts_view); + msgArea = itemView.findViewById(R.id.msg_area); + msgAreaAndReply = itemView.findViewById(R.id.msg_area_and_reply); + mMutiSelectCheckBox = itemView.findViewById(R.id.select_checkbox); + chatTimeText = itemView.findViewById(R.id.message_top_time_tv); + mContentLayout = itemView.findViewById(R.id.message_content_layout); + initVariableLayout(); + } + + public abstract int getVariableLayout(); + + private void setVariableLayout(int resId) { + if (msgContentFrame.getChildCount() == 0) { + View.inflate(itemView.getContext(), resId, msgContentFrame); + } + } + + private void initVariableLayout() { + if (getVariableLayout() != 0) { + setVariableLayout(getVariableLayout()); + } + } + + public void setAdapter(ICommonMessageAdapter adapter) { + mAdapter = adapter; + } + + public void setOnItemClickListener(OnItemClickListener listener) { + this.onItemClickListener = listener; + } + + public OnItemClickListener getOnItemClickListener() { + return this.onItemClickListener; + } + + public void layoutViews(final TUIMessageBean msg, final int position) { + if (properties.getChatTimeBubble() != null) { + chatTimeText.setBackground(properties.getChatTimeBubble()); + } + if (properties.getChatTimeFontColor() != 0) { + chatTimeText.setTextColor(properties.getChatTimeFontColor()); + } + if (properties.getChatTimeFontSize() != 0) { + chatTimeText.setTextSize(properties.getChatTimeFontSize()); + } + + if (position > 1) { + TUIMessageBean last = mAdapter.getItem(position - 1); + if (last != null) { + if (msg.getMessageTime() - last.getMessageTime() >= 5 * 60) { + chatTimeText.setVisibility(View.VISIBLE); + chatTimeText.setText(getTimeFormatText(new Date(msg.getMessageTime() * 1000))); + } else { + chatTimeText.setVisibility(View.GONE); + } + } + } else { + chatTimeText.setVisibility(View.VISIBLE); + chatTimeText.setText(getTimeFormatText(new Date(msg.getMessageTime() * 1000))); + } + } + + public static String getTimeFormatText(Date date) { + if (date == null) { + return ""; + } + Context context = TUIConfig.getAppContext(); + Locale locale; + if (context == null) { + locale = Locale.getDefault(); + } else { + locale = TUIThemeManager.getInstance().getLocale(context); + } + String timeText; + Calendar calendar = Calendar.getInstance(); + calendar.set(Calendar.HOUR_OF_DAY, 0); + calendar.set(Calendar.MINUTE, 0); + calendar.set(Calendar.SECOND, 0); + calendar.set(Calendar.MILLISECOND, 0); + long dayStartTimeInMillis = calendar.getTimeInMillis(); + calendar = Calendar.getInstance(); + calendar.set(Calendar.DAY_OF_WEEK, 1); + calendar.set(Calendar.HOUR_OF_DAY, 0); + calendar.set(Calendar.MINUTE, 0); + calendar.set(Calendar.SECOND, 0); + calendar.set(Calendar.MILLISECOND, 0); + long weekStartTimeInMillis = calendar.getTimeInMillis(); + calendar = Calendar.getInstance(); + calendar.set(Calendar.DAY_OF_YEAR, 1); + calendar.set(Calendar.HOUR_OF_DAY, 0); + calendar.set(Calendar.MINUTE, 0); + calendar.set(Calendar.SECOND, 0); + calendar.set(Calendar.MILLISECOND, 0); + long yearStartTimeInMillis = calendar.getTimeInMillis(); + long outTimeMillis = date.getTime(); + if (outTimeMillis < yearStartTimeInMillis) { + timeText = String.format(locale, "%1$tY/%1$tm/%1$td", date); + } else if (outTimeMillis < weekStartTimeInMillis) { + timeText = String.format(locale, "%1$tm/%1$td", date); + } else if (outTimeMillis < dayStartTimeInMillis) { + timeText = String.format(locale, "%tA", date); + } else { + timeText = context.getResources().getString(R.string.chat_time_today); + } + return timeText; + } + + public void setFloatMode(boolean floatMode) { + this.floatMode = floatMode; + } + + public void stopHighLight() { + if (highLightAnimator != null) { + highLightAnimator.cancel(); + } + clearHighLightBackground(); + } + + protected boolean isShowAvatar(TUIMessageBean messageBean) { + return false; + } + public void startHighLight() { + int highLightColorDark = itemView.getResources().getColor(R.color.chat_message_bubble_high_light_dark_color); + int highLightColorLight = itemView.getResources().getColor(R.color.chat_message_bubble_high_light_light_color); + + if (highLightAnimator == null) { + ArgbEvaluator argbEvaluator = new ArgbEvaluator(); + highLightAnimator = new ValueAnimator(); + highLightAnimator.setIntValues(highLightColorDark, highLightColorLight); + highLightAnimator.addUpdateListener(new ValueAnimator.AnimatorUpdateListener() { + @Override + public void onAnimationUpdate(ValueAnimator animation) { + Integer color = (Integer) animation.getAnimatedValue(); + setHighLightBackground(color); + } + }); + highLightAnimator.addListener(new Animator.AnimatorListener() { + @Override + public void onAnimationStart(Animator animation) { + + } + + @Override + public void onAnimationEnd(Animator animation) { + clearHighLightBackground(); + } + + @Override + public void onAnimationCancel(Animator animation) { + clearHighLightBackground(); + } + + @Override + public void onAnimationRepeat(Animator animation) { + + } + }); + highLightAnimator.setEvaluator(argbEvaluator); + highLightAnimator.setRepeatCount(3); + highLightAnimator.setDuration(250); + highLightAnimator.setRepeatMode(ValueAnimator.REVERSE); + } + highLightAnimator.start(); + } + + public void setHighLightBackground(int color) { + Drawable drawable = msgArea.getBackground(); + if (drawable != null) { + drawable.setColorFilter(color, PorterDuff.Mode.SRC_IN); + } + } + + public void clearHighLightBackground() { + Drawable drawable = msgArea.getBackground(); + if (drawable != null) { + drawable.setColorFilter(null); + } + } + +} diff --git a/TIMCommon/timcommon/src/main/java/com/tencent/qcloud/tuikit/timcommon/minimalistui/widget/message/MessageContentHolder.java b/TIMCommon/timcommon/src/main/java/com/tencent/qcloud/tuikit/timcommon/minimalistui/widget/message/MessageContentHolder.java new file mode 100644 index 0000000..0ca6f18 --- /dev/null +++ b/TIMCommon/timcommon/src/main/java/com/tencent/qcloud/tuikit/timcommon/minimalistui/widget/message/MessageContentHolder.java @@ -0,0 +1,667 @@ +package com.tencent.qcloud.tuikit.timcommon.minimalistui.widget.message; + +import android.text.TextUtils; +import android.view.Gravity; +import android.view.View; +import android.view.ViewGroup; +import android.view.animation.RotateAnimation; +import android.widget.FrameLayout; +import android.widget.ImageView; +import android.widget.LinearLayout; +import android.widget.TextView; + +import androidx.recyclerview.widget.RecyclerView; + +import com.tencent.imsdk.v2.V2TIMManager; +import com.tencent.imsdk.v2.V2TIMMessage; +import com.tencent.imsdk.v2.V2TIMUserFullInfo; +import com.tencent.imsdk.v2.V2TIMValueCallback; +import com.tencent.qcloud.tuicore.TUIConstants; +import com.tencent.qcloud.tuicore.TUICore; +import com.tencent.qcloud.tuicore.TUIThemeManager; +import com.tencent.qcloud.tuikit.timcommon.R; +import com.tencent.qcloud.tuikit.timcommon.bean.MessageReactBean; +import com.tencent.qcloud.tuikit.timcommon.bean.MessageRepliesBean; +import com.tencent.qcloud.tuikit.timcommon.bean.TUIMessageBean; +import com.tencent.qcloud.tuikit.timcommon.component.UnreadCountTextView; +import com.tencent.qcloud.tuikit.timcommon.component.fragments.BaseFragment; +import com.tencent.qcloud.tuikit.timcommon.component.gatherimage.UserIconView; +import com.tencent.qcloud.tuikit.timcommon.util.DateTimeUtil; +import com.tencent.qcloud.tuikit.timcommon.util.ScreenUtil; + +import java.util.ArrayList; +import java.util.Date; +import java.util.HashMap; +import java.util.List; + +public abstract class MessageContentHolder extends MessageBaseHolder { + + protected static final int READ_STATUS_UNREAD = 1; + protected static final int READ_STATUS_PART_READ = 2; + protected static final int READ_STATUS_ALL_READ = 3; + protected static final int READ_STATUS_HIDE = 4; + protected static final int READ_STATUS_SENDING = 5; + + public UserIconView leftUserIcon; + public UserIconView rightUserIcon; + public TextView usernameText; + public LinearLayout msgContentLinear; + public ImageView messageStatusImage; + public ImageView fileStatusImage; + public UnreadCountTextView unreadAudioText; + public LinearLayout extraInfoArea; + protected FrameLayout translationContentFrameLayout; + private ImageView translationLoadingImage; + protected LinearLayout translationResultLayout; + private RotateAnimation translationRotateAnimation; + + public boolean isForwardMode = false; + public boolean isMessageDetailMode = false; + public boolean isMultiSelectMode = false; + public boolean isOptimize = true; + public boolean isShowSelfAvatar = false; + + protected TimeInLineTextLayout timeInLineTextLayout; + protected MinimalistMessageLayout rootLayout; + protected ReplyPreviewView replyPreviewView; + + private List mDataSource = new ArrayList<>(); + // 是否显示翻译的内容。合并转发的消息详情界面不用展示翻译内容。 + // Whether to display the translated content. The merged-forwarded message details activity does not display the translated content. + protected boolean isNeedShowTranslation = true; + protected boolean isShowRead = false; + private BaseFragment fragment; + private RecyclerView recyclerView; + + public MessageContentHolder(View itemView) { + super(itemView); + rootLayout = (MinimalistMessageLayout) itemView; + leftUserIcon = itemView.findViewById(R.id.left_user_icon_view); + rightUserIcon = itemView.findViewById(R.id.right_user_icon_view); + usernameText = itemView.findViewById(R.id.user_name_tv); + msgContentLinear = itemView.findViewById(R.id.msg_content_ll); + messageStatusImage = itemView.findViewById(R.id.message_status_iv); + fileStatusImage = itemView.findViewById(R.id.file_status_iv); + unreadAudioText = itemView.findViewById(R.id.unread_audio_text); + replyPreviewView = itemView.findViewById(R.id.msg_reply_preview); + extraInfoArea = itemView.findViewById(R.id.extra_info_area); + translationContentFrameLayout = itemView.findViewById(R.id.translate_content_fl); + } + + public void setFragment(BaseFragment fragment) { + this.fragment = fragment; + } + + public void setRecyclerView(RecyclerView recyclerView) { + this.recyclerView = recyclerView; + } + + public void setDataSource(List dataSource) { + if (dataSource == null || dataSource.isEmpty()) { + mDataSource = null; + } + + List mediaSource = new ArrayList<>(); + for(TUIMessageBean messageBean : dataSource) { + int type = messageBean.getMsgType(); + if (type == V2TIMMessage.V2TIM_ELEM_TYPE_IMAGE || type == V2TIMMessage.V2TIM_ELEM_TYPE_VIDEO) { + mediaSource.add(messageBean); + } + } + mDataSource = mediaSource; + } + + public List getDataSource() { + return mDataSource; + } + + @Override + public void layoutViews(final TUIMessageBean msg, final int position) { + super.layoutViews(msg, position); + + if (isForwardMode || isMessageDetailMode) { + isShowStart = true; + } else { + if (msg.isSelf()) { + isShowStart = false; + } else { + isShowStart = true; + } + } + + setMessageGravity(msg); + + if (isForwardMode || isMessageDetailMode) { + usernameText.setVisibility(View.GONE); + } else { + if (msg.isSelf()) { + if (properties.getRightNameVisibility() == 0) { + usernameText.setVisibility(View.GONE); + } else { + usernameText.setVisibility(properties.getRightNameVisibility()); + } + } else { + if (properties.getLeftNameVisibility() == 0) { + if (msg.isGroup()) { + usernameText.setVisibility(View.GONE); + } else { + usernameText.setVisibility(View.GONE); + } + } else { + usernameText.setVisibility(properties.getLeftNameVisibility()); + } + } + } + if (properties.getNameFontColor() != 0) { + usernameText.setTextColor(properties.getNameFontColor()); + } + if (properties.getNameFontSize() != 0) { + usernameText.setTextSize(properties.getNameFontSize()); + } + + if (!TextUtils.isEmpty(msg.getNameCard())) { + usernameText.setText(msg.getNameCard()); + } else if (!TextUtils.isEmpty(msg.getFriendRemark())) { + usernameText.setText(msg.getFriendRemark()); + } else if (!TextUtils.isEmpty(msg.getNickName())) { + usernameText.setText(msg.getNickName()); + } else { + usernameText.setText(msg.getSender()); + } + + if (isForwardMode) { + chatTimeText.setVisibility(View.GONE); + } + + if (isForwardMode || isMessageDetailMode) { + msgArea.setBackgroundResource(R.drawable.chat_message_popup_stroke_border_left); + messageStatusImage.setVisibility(View.GONE); + } else { + if (msg.isSelf()) { + if (properties.getRightBubble() != null && properties.getRightBubble().getConstantState() != null) { + msgArea.setBackground(properties.getRightBubble().getConstantState().newDrawable()); + } else { + msgArea.setBackgroundResource(R.drawable.chat_message_popup_fill_border_right); + } + } else { + if (properties.getLeftBubble() != null && properties.getLeftBubble().getConstantState() != null) { + msgArea.setBackground(properties.getLeftBubble().getConstantState().newDrawable()); + } else { + msgArea.setBackgroundResource(R.drawable.chat_message_popup_stroke_border_left); + } + } + + if (onItemClickListener != null) { + msgContentFrame.setOnLongClickListener(new View.OnLongClickListener() { + @Override + public boolean onLongClick(View v) { + onItemClickListener.onMessageLongClick(msgArea, position, msg); + return true; + } + }); + + msgArea.setOnLongClickListener(new View.OnLongClickListener() { + @Override + public boolean onLongClick(View v) { + onItemClickListener.onMessageLongClick(msgArea, position, msg); + return true; + } + }); + + leftUserIcon.setOnClickListener(new View.OnClickListener() { + @Override + public void onClick(View view) { + onItemClickListener.onUserIconClick(view, position, msg); + } + }); + leftUserIcon.setOnLongClickListener(new View.OnLongClickListener() { + @Override + public boolean onLongClick(View view) { + onItemClickListener.onUserIconLongClick(view, position, msg); + return true; + } + }); + rightUserIcon.setOnClickListener(new View.OnClickListener() { + @Override + public void onClick(View view) { + onItemClickListener.onUserIconClick(view, position, msg); + } + }); + } + + if (msg.getStatus() == TUIMessageBean.MSG_STATUS_SEND_FAIL) { + messageStatusImage.setVisibility(View.VISIBLE); + + messageStatusImage.setOnClickListener(new View.OnClickListener() { + @Override + public void onClick(View v) { + if (onItemClickListener != null) { + onItemClickListener.onSendFailBtnClick(messageStatusImage, position, msg); + } + } + }); + } else { + msgContentFrame.setOnClickListener(new View.OnClickListener() { + @Override + public void onClick(View v) { + if (onItemClickListener != null) { + onItemClickListener.onMessageClick(msgContentFrame, position, msg); + } + } + }); + messageStatusImage.setVisibility(View.GONE); + } + } + + if (isForwardMode || isMessageDetailMode) { + rootLayout.setIsStart(true); + msgContentLinear.removeView(msgAreaAndReply); + msgContentLinear.addView(msgAreaAndReply); + } else { + if (msg.isSelf()) { + msgContentLinear.removeView(msgAreaAndReply); + msgContentLinear.addView(msgAreaAndReply); + } else { + msgContentLinear.removeView(msgAreaAndReply); + msgContentLinear.addView(msgAreaAndReply, 0); + } + } + setGravity(isShowStart); + rootLayout.setIsStart(isShowStart); + + msgContentLinear.setVisibility(View.VISIBLE); + + if (!isForwardMode && !isMessageDetailMode) { + setTimeInLineStatus(msg); + setShowReadStatusClickListener(msg); + } + + if (timeInLineTextLayout != null) { + timeInLineTextLayout.setTimeText(DateTimeUtil.getHMTimeString(new Date(msg.getMessageTime() * 1000))); + } + + extraInfoArea.setVisibility(View.GONE); + setReplyContent(msg); + setReactContent(msg); + if (isNeedShowTranslation) { + setTranslationContent(msg); + } + setMessageAreaPadding(); + if (floatMode) { + itemView.setPadding(0, 0, 0, 0); + leftUserIcon.setVisibility(View.GONE); + rightUserIcon.setVisibility(View.GONE); + usernameText.setVisibility(View.GONE); + messageStatusImage.setVisibility(View.GONE); + replyPreviewView.setVisibility(View.GONE); + reactView.setVisibility(View.GONE); + chatTimeText.setVisibility(View.GONE); + } + if (isMessageDetailMode) { + replyPreviewView.setVisibility(View.GONE); + } + + optimizeAvatarAndPadding(position, msg); + loadAvatar(msg); + layoutVariableViews(msg, position); + } + + public void setTranslationContent(TUIMessageBean msg) { + HashMap param = new HashMap<>(); + param.put(TUIConstants.TUIChat.MESSAGE_BEAN, msg); + param.put(TUIConstants.TUIChat.CHAT_RECYCLER_VIEW, recyclerView); + param.put(TUIConstants.TUIChat.FRAGMENT, fragment); + + TUICore.raiseExtension(TUIConstants.TUITranslation.Extension.TranslationView.MINIMALIST_EXTENSION_ID, translationContentFrameLayout, param); + } + + private void loadAvatar(TUIMessageBean msg) { + if (msg.isUseMsgReceiverAvatar()) { + String userId = ""; + if (TextUtils.equals(msg.getSender(), V2TIMManager.getInstance().getLoginUser())) { + userId = msg.getUserId(); + } else { + userId = V2TIMManager.getInstance().getLoginUser(); + } + List idList = new ArrayList<>(); + idList.add(userId); + V2TIMManager.getInstance().getUsersInfo(idList, new V2TIMValueCallback>() { + @Override + public void onSuccess(List v2TIMUserFullInfos) { + V2TIMUserFullInfo userInfo = v2TIMUserFullInfos.get(0); + if (userInfo == null) { + setupAvatar("", msg.isSelf()); + } else { + setupAvatar(userInfo.getFaceUrl(), msg.isSelf()); + } + } + + @Override + public void onError(int code, String desc) { + setupAvatar("", msg.isSelf()); + } + }); + } else { + setupAvatar(msg.getFaceUrl(), msg.isSelf()); + } + } + + private void setupAvatar(String faceUrl, boolean right) { + if (!TextUtils.isEmpty(faceUrl)) { + List urllist = new ArrayList<>(); + urllist.add(faceUrl); + if (isForwardMode || isMessageDetailMode) { + leftUserIcon.setIconUrls(urllist); + } else { + if (right) { + rightUserIcon.setIconUrls(urllist); + } else { + leftUserIcon.setIconUrls(urllist); + } + } + } else { + rightUserIcon.setIconUrls(null); + leftUserIcon.setIconUrls(null); + } + + if (isShowSelfAvatar) { + rightUserIcon.setVisibility(View.VISIBLE); + } else { + rightUserIcon.setVisibility(View.GONE); + } + } + + + + protected boolean isNeedChangedBackground() { + return true; + } + + private void optimizeAvatarAndPadding(int position, TUIMessageBean messageBean) { + if (mAdapter == null) { + return; + } + if (isMessageDetailMode || !isOptimize) { + return; + } + TUIMessageBean nextMessage = mAdapter.getItem(position + 1); + boolean isShowAvatar = true; + boolean needChangedBackground = isNeedChangedBackground(); + if (nextMessage != null) { + if (TextUtils.equals(messageBean.getSender(), nextMessage.getSender())) { + boolean longPeriod = nextMessage.getMessageTime() - messageBean.getMessageTime() >= 5 * 60; + if (!isShowAvatar(nextMessage) + && nextMessage.getStatus() != TUIMessageBean.MSG_STATUS_REVOKE + && !longPeriod) { + isShowAvatar = false; + } + } + } + + int horizontalPadding = ScreenUtil.dip2px(16); + int verticalPadding = ScreenUtil.dip2px(25f); + if (isShowAvatar) { + if (isShowStart) { + leftUserIcon.setVisibility(View.VISIBLE); + rightUserIcon.setVisibility(View.INVISIBLE); + if (needChangedBackground) { + msgArea.setBackgroundResource(R.drawable.chat_message_popup_stroke_border_left); + } + } else { + leftUserIcon.setVisibility(View.INVISIBLE); + rightUserIcon.setVisibility(View.VISIBLE); + if (needChangedBackground) { + msgArea.setBackgroundResource(R.drawable.chat_message_popup_fill_border_right); + } + } + } else { + if (needChangedBackground) { + if (isShowStart) { + msgArea.setBackgroundResource(R.drawable.chat_message_popup_stroke_border); + } else { + msgArea.setBackgroundResource(R.drawable.chat_message_popup_fill_border); + } + } + horizontalPadding = ScreenUtil.dip2px(16); + verticalPadding = ScreenUtil.dip2px(2); + leftUserIcon.setVisibility(View.INVISIBLE); + rightUserIcon.setVisibility(View.INVISIBLE); + } + rootLayout.setPadding(horizontalPadding, 0, horizontalPadding, verticalPadding); + optimizeMessageContent(isShowAvatar); + } + + protected void optimizeMessageContent(boolean isShowAvatar) {} + + private void setMessageGravity(TUIMessageBean messageBean) { + if (isForwardMode || isMessageDetailMode) { + leftUserIcon.setVisibility(View.VISIBLE); + rightUserIcon.setVisibility(View.GONE); + } else { + if (messageBean.isSelf()) { + msgContentLinear.setGravity(Gravity.END); + leftUserIcon.setVisibility(View.GONE); + rightUserIcon.setVisibility(View.VISIBLE); + extraInfoArea.setGravity(Gravity.END); + } else { + msgContentLinear.setGravity(Gravity.START); + leftUserIcon.setVisibility(View.VISIBLE); + rightUserIcon.setVisibility(View.GONE); + extraInfoArea.setGravity(Gravity.START); + } + } + if (properties.getAvatar() != 0) { + leftUserIcon.setDefaultImageResId(properties.getAvatar()); + rightUserIcon.setDefaultImageResId(properties.getAvatar()); + } else { + leftUserIcon.setDefaultImageResId(TUIThemeManager.getAttrResId(leftUserIcon.getContext(), com.tencent.qcloud.tuikit.timcommon.R.attr.core_default_user_icon)); + rightUserIcon.setDefaultImageResId(TUIThemeManager.getAttrResId(rightUserIcon.getContext(), com.tencent.qcloud.tuikit.timcommon.R.attr.core_default_user_icon)); + } + if (properties.getAvatarRadius() != 0) { + leftUserIcon.setRadius(properties.getAvatarRadius()); + rightUserIcon.setRadius(properties.getAvatarRadius()); + } else { + int radius = ScreenUtil.dip2px(4); + leftUserIcon.setRadius(radius); + rightUserIcon.setRadius(radius); + } + if (properties.getAvatarSize() != null && properties.getAvatarSize().length == 2) { + ViewGroup.LayoutParams params = leftUserIcon.getLayoutParams(); + params.width = properties.getAvatarSize()[0]; + params.height = properties.getAvatarSize()[1]; + leftUserIcon.setLayoutParams(params); + + params = rightUserIcon.getLayoutParams(); + params.width = properties.getAvatarSize()[0]; + params.height = properties.getAvatarSize()[1]; + rightUserIcon.setLayoutParams(params); + } + } + + private void setTimeInLineStatus(TUIMessageBean msg) { + if (isShowRead) { + if (msg.isSelf()) { + if (TUIMessageBean.MSG_STATUS_SEND_SUCCESS == msg.getStatus()) { + if (!msg.isNeedReadReceipt()) { + setReadStatus(READ_STATUS_HIDE); + } else { + processReadStatus(msg); + } + } else if (TUIMessageBean.MSG_STATUS_SENDING == msg.getStatus()) { + setReadStatus(READ_STATUS_SENDING); + } else { + setReadStatus(READ_STATUS_HIDE); + } + } else { + setReadStatus(READ_STATUS_HIDE); + } + } + } + + protected void setReadStatus(int readStatus) { + if (timeInLineTextLayout != null) { + int statusIconResID = 0; + switch (readStatus) { + case READ_STATUS_UNREAD: { + statusIconResID = R.drawable.chat_minimalist_message_status_send_no_read; + break; + } + case READ_STATUS_PART_READ: { + statusIconResID = R.drawable.chat_minimalist_message_status_send_part_read; + break; + } + case READ_STATUS_ALL_READ: { + statusIconResID = R.drawable.chat_minimalist_message_status_send_all_read; + break; + } + case READ_STATUS_SENDING: { + statusIconResID = R.drawable.chat_minimalist_status_loading_anim; + break; + } + default: { + } + } + timeInLineTextLayout.setStatusIcon(statusIconResID); + } + } + + protected void setMessageAreaPadding() { + // after setting background, the padding will be reset + int paddingHorizontal = itemView.getResources().getDimensionPixelSize(R.dimen.chat_minimalist_message_area_padding_left_right); + int paddingVertical = itemView.getResources().getDimensionPixelSize(R.dimen.chat_minimalist_message_area_padding_top_bottom); + msgArea.setPadding(paddingHorizontal, paddingVertical, paddingHorizontal, paddingVertical); + } + + protected void setGravity(boolean isStart) { + int gravity = isStart ? Gravity.START : Gravity.END; + msgAreaAndReply.setGravity(gravity); + ViewGroup.LayoutParams layoutParams = msgContentFrame.getLayoutParams(); + if (layoutParams instanceof FrameLayout.LayoutParams) { + ((FrameLayout.LayoutParams) layoutParams).gravity = gravity; + } else if (layoutParams instanceof LinearLayout.LayoutParams) { + ((LinearLayout.LayoutParams) layoutParams).gravity = gravity; + } + msgContentFrame.setLayoutParams(layoutParams); + } + + private void setReplyContent(TUIMessageBean messageBean) { + MessageRepliesBean messageRepliesBean = messageBean.getMessageRepliesBean(); + if (messageRepliesBean != null && messageRepliesBean.getRepliesSize() > 0) { + extraInfoArea.setVisibility(View.VISIBLE); + replyPreviewView.setVisibility(View.VISIBLE); + replyPreviewView.setMessageRepliesBean(messageRepliesBean); + replyPreviewView.setOnClickListener(new View.OnClickListener() { + @Override + public void onClick(View v) { + if (onItemClickListener != null) { + onItemClickListener.onReplyDetailClick(messageBean); + } + } + }); + } else { + replyPreviewView.setVisibility(View.GONE); + } + } + + private void setReactContent(TUIMessageBean messageBean) { + MessageReactBean messageReactBean = messageBean.getMessageReactBean(); + if (messageReactBean != null && messageReactBean.getReactSize() > 0) { + reactView.setVisibility(View.VISIBLE); + reactView.setData(messageReactBean); + reactView.setOnLongClickListener(new View.OnLongClickListener() { + @Override + public boolean onLongClick(View v) { + if (onItemClickListener != null) { + onItemClickListener.onMessageLongClick(msgArea, 0, messageBean); + } + return true; + } + }); + + reactView.setOnClickListener(new View.OnClickListener() { + @Override + public void onClick(View v) { + if (onItemClickListener != null) { + onItemClickListener.onReactOnClick(null, messageBean); + } + } + }); + + if (isForwardMode) { + reactView.setOnLongClickListener(null); + } + } else { + reactView.setVisibility(View.GONE); + reactView.setOnLongClickListener(null); + } + } + + private void processReadStatus(TUIMessageBean msg) { + if (msg.isGroup()) { + if (msg.isAllRead()) { + setReadStatus(READ_STATUS_ALL_READ); + } else if (msg.isUnread()) { + setReadStatus(READ_STATUS_UNREAD); + } else { + long readCount = msg.getReadCount(); + if (readCount > 0) { + setReadStatus(READ_STATUS_PART_READ); + } + } + } else { + if (msg.isPeerRead()) { + setReadStatus(READ_STATUS_ALL_READ); + } else { + setReadStatus(READ_STATUS_UNREAD); + } + } + } + + private void setShowReadStatusClickListener(TUIMessageBean messageBean) { + if (timeInLineTextLayout != null) { + if (messageBean.isSelf()) { + timeInLineTextLayout.setOnStatusAreaClickListener(new View.OnClickListener() { + @Override + public void onClick(View v) { + if (onItemClickListener != null) { + onItemClickListener.onMessageReadStatusClick(v, messageBean); + } + } + }); + } else { + timeInLineTextLayout.setOnStatusAreaClickListener(null); + } + timeInLineTextLayout.setOnStatusAreaLongClickListener(new View.OnLongClickListener() { + @Override + public boolean onLongClick(View v) { + if (onItemClickListener != null) { + onItemClickListener.onMessageLongClick(msgArea, 0, messageBean); + } + return true; + } + }); + + timeInLineTextLayout.setOnLongClickListener(new View.OnLongClickListener() { + @Override + public boolean onLongClick(View v) { + if (onItemClickListener != null) { + onItemClickListener.onMessageLongClick(msgArea, 0, messageBean); + } + return true; + } + }); + } + } + + public abstract void layoutVariableViews(final TUIMessageBean msg, final int position); + + public void onRecycled() {} + + public void setNeedShowTranslation(boolean needShowTranslation) { + isNeedShowTranslation = needShowTranslation; + } + + public void setShowRead(boolean showRead) { + isShowRead = showRead; + } +} diff --git a/TIMCommon/timcommon/src/main/java/com/tencent/qcloud/tuikit/timcommon/minimalistui/widget/message/MessageStatusTimeView.java b/TIMCommon/timcommon/src/main/java/com/tencent/qcloud/tuikit/timcommon/minimalistui/widget/message/MessageStatusTimeView.java new file mode 100644 index 0000000..353bad0 --- /dev/null +++ b/TIMCommon/timcommon/src/main/java/com/tencent/qcloud/tuikit/timcommon/minimalistui/widget/message/MessageStatusTimeView.java @@ -0,0 +1,63 @@ +package com.tencent.qcloud.tuikit.timcommon.minimalistui.widget.message; + +import android.content.Context; +import android.graphics.drawable.Animatable; +import android.graphics.drawable.Drawable; +import android.util.AttributeSet; +import android.view.LayoutInflater; +import android.widget.FrameLayout; +import android.widget.ImageView; +import android.widget.TextView; + +import androidx.annotation.NonNull; +import androidx.annotation.Nullable; + +import com.tencent.qcloud.tuikit.timcommon.R; + +public class MessageStatusTimeView extends FrameLayout { + private TextView timeView; + private ImageView statusIconView; + + public MessageStatusTimeView(@NonNull Context context) { + super(context); + init(context, null); + } + + public MessageStatusTimeView(@NonNull Context context, @Nullable AttributeSet attrs) { + super(context, attrs); + init(context, attrs); + } + + public MessageStatusTimeView(@NonNull Context context, @Nullable AttributeSet attrs, int defStyleAttr) { + super(context, attrs, defStyleAttr); + init(context, attrs); + } + + private void init(Context context, AttributeSet attrs) { + LayoutInflater.from(context).inflate(R.layout.chat_minimalist_text_status_layout, this); + statusIconView = findViewById(R.id.status_icon); + timeView = findViewById(R.id.time); + } + + public void setStatusIcon(int resID) { + if (resID == 0) { + statusIconView.setVisibility(GONE); + } else { + statusIconView.setBackgroundResource(resID); + Drawable drawable = statusIconView.getBackground(); + if (drawable instanceof Animatable) { + ((Animatable) drawable).start(); + } + statusIconView.setVisibility(VISIBLE); + } + } + + public void setTimeText(CharSequence charSequence) { + timeView.setText(charSequence); + } + + public void setTimeColor(int color) { + timeView.setTextColor(color); + } + +} diff --git a/TIMCommon/timcommon/src/main/java/com/tencent/qcloud/tuikit/timcommon/minimalistui/widget/message/MinimalistMessageLayout.java b/TIMCommon/timcommon/src/main/java/com/tencent/qcloud/tuikit/timcommon/minimalistui/widget/message/MinimalistMessageLayout.java new file mode 100644 index 0000000..61fca6e --- /dev/null +++ b/TIMCommon/timcommon/src/main/java/com/tencent/qcloud/tuikit/timcommon/minimalistui/widget/message/MinimalistMessageLayout.java @@ -0,0 +1,165 @@ +package com.tencent.qcloud.tuikit.timcommon.minimalistui.widget.message; + +import android.content.Context; +import android.graphics.Canvas; +import android.graphics.Paint; +import android.graphics.Path; +import android.graphics.Rect; +import android.os.Build; +import android.util.AttributeSet; +import android.view.View; +import android.widget.RelativeLayout; + +import androidx.annotation.Nullable; +import androidx.annotation.RequiresApi; + +import com.tencent.qcloud.tuikit.timcommon.R; + +public class MinimalistMessageLayout extends RelativeLayout { + private View msgArea; + private View quoteArea; + private View translationArea; + private View replyArea; + + private boolean isStart = false; + private Paint paint; + private Path quotePath; + private Path translationPath; + private Path replyPath; + + private Rect msgAreaRect; + private Rect translationRect; + private Rect quoteRect; + private Rect replyRect; + private float strokeWidth; + + public MinimalistMessageLayout(Context context) { + super(context); + init(); + } + + public MinimalistMessageLayout(Context context, @Nullable AttributeSet attrs) { + super(context, attrs); + init(); + } + + public MinimalistMessageLayout(Context context, @Nullable AttributeSet attrs, int defStyleAttr) { + super(context, attrs, defStyleAttr); + init(); + } + + @RequiresApi(api = Build.VERSION_CODES.LOLLIPOP) + public MinimalistMessageLayout(Context context, AttributeSet attrs, int defStyleAttr, int defStyleRes) { + super(context, attrs, defStyleAttr, defStyleRes); + init(); + } + + private void init() { + setWillNotDraw(false); + quotePath = new Path(); + translationPath = new Path(); + replyPath = new Path(); + msgAreaRect = new Rect(); + translationRect = new Rect(); + quoteRect = new Rect(); + replyRect = new Rect(); + paint = new Paint(); + strokeWidth = getResources().getDimension(R.dimen.chat_minimalist_message_quato_line_width); + paint.setStrokeWidth(strokeWidth); + paint.setStyle(Paint.Style.STROKE); + paint.setAntiAlias(true); + } + + @Override + protected void onDraw(Canvas canvas) { + super.onDraw(canvas); + msgArea = findViewById(R.id.msg_area); + translationArea = findViewById(R.id.translate_content_fl); + quoteArea = findViewById(R.id.quote_content_fl); + replyArea = findViewById(R.id.msg_reply_preview); + drawLine(canvas); + } + + public void setIsStart(boolean isStart) { + this.isStart = isStart; + } + + private Rect getChildRectInParent(View child) { + int[] location = new int[2]; + getLocationInWindow(location); + + Rect rect = new Rect(); + int[] childLocation = new int[2]; + child.getLocationInWindow(childLocation); + int left = childLocation[0] - location[0]; + int top = childLocation[1] - location[1]; + int right = left + child.getWidth(); + int bottom = top + child.getHeight(); + rect.set(left, top, right, bottom); + return rect; + } + + private void drawLine(Canvas canvas) { + canvas.drawColor(0x00FFFFFF); + if (msgArea.getVisibility() == VISIBLE) { + float msgX, msgCenterY; + msgAreaRect = getChildRectInParent(msgArea); + if (isStart) { + paint.setColor(getResources().getColor(R.color.chat_minimalist_left_message_bubble_color)); + msgX = msgAreaRect.left + strokeWidth / 2; + } else { + paint.setColor(getResources().getColor(R.color.chat_minimalist_right_message_bubble_color)); + msgX = msgAreaRect.right - strokeWidth / 2; + } + msgCenterY = msgAreaRect.top + msgAreaRect.height() * 1.0f / 2; + if (translationArea.getVisibility() == VISIBLE) { + float translationX, translationCenterY; + translationRect = getChildRectInParent(translationArea); + if (isStart) { + translationX = translationRect.left; + } else { + translationX = translationRect.right; + } + translationCenterY = translationRect.top + translationRect.height() * 1.0f / 2; + int translationControlRadius = (int) Math.abs(translationX - msgX); + translationPath.reset(); + translationPath.moveTo(msgX, msgCenterY); + translationPath.quadTo(msgX, translationCenterY - translationControlRadius, msgX, translationCenterY - translationControlRadius); + translationPath.quadTo(msgX, translationCenterY, translationX, translationCenterY); + canvas.drawPath(translationPath, paint); + } + if (quoteArea.getVisibility() == VISIBLE) { + float quoteX, quoteCenterY; + quoteRect = getChildRectInParent(quoteArea); + if (isStart) { + quoteX = quoteRect.left; + } else { + quoteX = quoteRect.right; + } + quoteCenterY = quoteRect.top + quoteRect.height() * 1.0f / 2; + int quoteControlRadius = (int) Math.abs(quoteX - msgX); + quotePath.reset(); + quotePath.moveTo(msgX, msgCenterY); + quotePath.quadTo(msgX, quoteCenterY - quoteControlRadius, msgX, quoteCenterY - quoteControlRadius); + quotePath.quadTo(msgX, quoteCenterY, quoteX, quoteCenterY); + canvas.drawPath(quotePath, paint); + } + if (replyArea.getVisibility() == VISIBLE) { + float replyX, replyCenterY; + replyRect = getChildRectInParent(replyArea); + if (isStart) { + replyX = replyRect.left; + } else { + replyX = replyRect.right; + } + replyCenterY = replyRect.top + replyRect.height() * 1.0f / 2; + int replyControlRadius = (int) Math.abs(replyX - msgX); + replyPath.reset(); + replyPath.moveTo(msgX, msgCenterY); + replyPath.quadTo(msgX, replyCenterY - replyControlRadius, msgX, replyCenterY - replyControlRadius); + replyPath.quadTo(msgX, replyCenterY, replyX, replyCenterY); + canvas.drawPath(replyPath, paint); + } + } + } +} diff --git a/TIMCommon/timcommon/src/main/java/com/tencent/qcloud/tuikit/timcommon/minimalistui/widget/message/ReplyPreviewView.java b/TIMCommon/timcommon/src/main/java/com/tencent/qcloud/tuikit/timcommon/minimalistui/widget/message/ReplyPreviewView.java new file mode 100644 index 0000000..e6634ab --- /dev/null +++ b/TIMCommon/timcommon/src/main/java/com/tencent/qcloud/tuikit/timcommon/minimalistui/widget/message/ReplyPreviewView.java @@ -0,0 +1,162 @@ +package com.tencent.qcloud.tuikit.timcommon.minimalistui.widget.message; + +import android.content.Context; +import android.graphics.Bitmap; +import android.graphics.Canvas; +import android.graphics.Color; +import android.graphics.Paint; +import android.graphics.PorterDuff; +import android.graphics.PorterDuffXfermode; +import android.graphics.Rect; +import android.os.Build; +import android.util.AttributeSet; +import android.view.LayoutInflater; +import android.widget.FrameLayout; +import android.widget.ImageView; +import android.widget.TextView; + +import androidx.annotation.NonNull; +import androidx.annotation.Nullable; +import androidx.annotation.RequiresApi; + +import com.bumptech.glide.Glide; +import com.bumptech.glide.load.engine.bitmap_recycle.BitmapPool; +import com.bumptech.glide.load.resource.bitmap.CircleCrop; +import com.bumptech.glide.request.RequestOptions; +import com.tencent.qcloud.tuikit.timcommon.R; +import com.tencent.qcloud.tuikit.timcommon.bean.MessageRepliesBean; +import com.tencent.qcloud.tuikit.timcommon.util.ScreenUtil; + +import java.util.ArrayList; +import java.util.LinkedHashSet; +import java.util.List; +import java.util.Set; + +public class ReplyPreviewView extends FrameLayout { + + private ImageView firstImg; + private ImageView secondImg; + private ImageView thirdImg; + private TextView replyText; + + private MessageRepliesBean messageRepliesBean; + + public ReplyPreviewView(@NonNull Context context) { + super(context); + init(context); + } + + public ReplyPreviewView(@NonNull Context context, @Nullable AttributeSet attrs) { + super(context, attrs); + init(context); + } + + public ReplyPreviewView(@NonNull Context context, @Nullable AttributeSet attrs, int defStyleAttr) { + super(context, attrs, defStyleAttr); + init(context); + } + + public void setMessageRepliesBean(MessageRepliesBean messageRepliesBean) { + this.messageRepliesBean = messageRepliesBean; + setView(); + } + + @RequiresApi(api = Build.VERSION_CODES.LOLLIPOP) + public ReplyPreviewView(@NonNull Context context, @Nullable AttributeSet attrs, int defStyleAttr, int defStyleRes) { + super(context, attrs, defStyleAttr, defStyleRes); + init(context); + } + + private void init(Context context) { + LayoutInflater.from(context).inflate(R.layout.chat_minimalist_reply_preview_layout, this); + firstImg = findViewById(R.id.first_avatar); + secondImg = findViewById(R.id.second_avatar); + thirdImg = findViewById(R.id.third_avatar); + replyText = findViewById(R.id.reply_text); + } + + private void setView() { + if (messageRepliesBean != null && messageRepliesBean.getRepliesSize() > 0) { + setVisibility(VISIBLE); + firstImg.setVisibility(GONE); + secondImg.setVisibility(GONE); + thirdImg.setVisibility(GONE); + replyText.setText(getResources().getString(R.string.chat_reply_num, messageRepliesBean.getRepliesSize())); + List repliesBeanList = messageRepliesBean.getReplies(); + List iconList = getReplyUserIconLt(repliesBeanList); + if (iconList == null || iconList.isEmpty()) { + return; + } + String firstIconUrl = iconList.get(0); + String secondIconUrl = null; + String thirdIconUrl = null; + + if (iconList.size() > 1) { + secondIconUrl = iconList.get(1); + } + if (iconList.size() > 2) { + thirdIconUrl = iconList.get(2); + } + + firstImg.setVisibility(VISIBLE); + loadAvatar(firstImg, firstIconUrl); + if (secondIconUrl != null) { + secondImg.setVisibility(VISIBLE); + loadAvatar(secondImg, secondIconUrl); + } + + if (iconList.size() == 3 && thirdIconUrl != null) { + thirdImg.setVisibility(VISIBLE); + loadAvatar(thirdImg, thirdIconUrl); + } else if (iconList.size() > 3){ + thirdImg.setVisibility(VISIBLE); + loadAvatar(thirdImg, R.drawable.chat_reply_more_icon); + } + } else { + setVisibility(GONE); + } + } + + private List getReplyUserIconLt(List repliesBeanList) { + Set iconUrlSet = new LinkedHashSet<>(); + for (MessageRepliesBean.ReplyBean replyBean : repliesBeanList) { + iconUrlSet.add(replyBean.getSenderFaceUrl()); + if (iconUrlSet.size() >= 3) { + break; + } + } + return new ArrayList<>(iconUrlSet); + } + + private void loadAvatar(ImageView imageView, Object url) { + Glide.with(getContext()) + .load(url) + .centerCrop() + .apply(new RequestOptions() + .transform(new ReplyRingCircleCrop()) + .error(com.tencent.qcloud.tuikit.timcommon.R.drawable.core_default_user_icon_light)) + .into(imageView); + } + + static class ReplyRingCircleCrop extends CircleCrop { + @Override + protected Bitmap transform(@NonNull BitmapPool pool, @NonNull Bitmap toTransform, int outWidth, int outHeight) { + Bitmap outBitmap = pool.get(outWidth, outHeight, Bitmap.Config.ARGB_8888); + int borderWidth = ScreenUtil.dip2px(1); + int innerWidth = outWidth - 2 * borderWidth; + int innerHeight = outHeight - 2 * borderWidth; + Bitmap bitmap = super.transform(pool, toTransform, innerWidth, innerHeight); + Canvas canvas = new Canvas(outBitmap); + Paint paint = new Paint(Paint.ANTI_ALIAS_FLAG | Paint.FILTER_BITMAP_FLAG); + paint.setColor(Color.WHITE); + canvas.drawCircle(outWidth / 2, outHeight / 2, innerWidth / 2, paint); + paint.setXfermode(new PorterDuffXfermode(PorterDuff.Mode.SRC_IN)); + Rect rect = new Rect(borderWidth, borderWidth, outWidth - borderWidth, outHeight - borderWidth); + canvas.drawBitmap(bitmap, null, rect, paint); + paint.setXfermode(new PorterDuffXfermode(PorterDuff.Mode.DST_OVER)); + canvas.drawCircle(outWidth / 2, outHeight / 2, outWidth / 2, paint); + return outBitmap; + } + } + +} diff --git a/TIMCommon/timcommon/src/main/java/com/tencent/qcloud/tuikit/timcommon/minimalistui/widget/message/TUIReplyQuoteView.java b/TIMCommon/timcommon/src/main/java/com/tencent/qcloud/tuikit/timcommon/minimalistui/widget/message/TUIReplyQuoteView.java new file mode 100644 index 0000000..5b5cd11 --- /dev/null +++ b/TIMCommon/timcommon/src/main/java/com/tencent/qcloud/tuikit/timcommon/minimalistui/widget/message/TUIReplyQuoteView.java @@ -0,0 +1,32 @@ +package com.tencent.qcloud.tuikit.timcommon.minimalistui.widget.message; + +import android.content.Context; +import android.view.LayoutInflater; +import android.widget.FrameLayout; + +import com.tencent.qcloud.tuikit.timcommon.bean.TUIReplyQuoteBean; + +public abstract class TUIReplyQuoteView extends FrameLayout { + + public abstract int getLayoutResourceId(); + + public TUIReplyQuoteView(Context context) { + super(context); + int resId = getLayoutResourceId(); + if (resId != 0) { + LayoutInflater.from(context).inflate(resId, this, true); + } + } + + public abstract void onDrawReplyQuote(TUIReplyQuoteBean quoteBean); + + /** + * 原始消息发送者是否为自己 , 用于不同 UI 展示 + * + * Whether the original message sender is himself, used for different UI displays + * + * @param isSelf + */ + public void setSelf(boolean isSelf) {} + +} diff --git a/TIMCommon/timcommon/src/main/java/com/tencent/qcloud/tuikit/timcommon/minimalistui/widget/message/TimeInLineTextLayout.java b/TIMCommon/timcommon/src/main/java/com/tencent/qcloud/tuikit/timcommon/minimalistui/widget/message/TimeInLineTextLayout.java new file mode 100644 index 0000000..322df5b --- /dev/null +++ b/TIMCommon/timcommon/src/main/java/com/tencent/qcloud/tuikit/timcommon/minimalistui/widget/message/TimeInLineTextLayout.java @@ -0,0 +1,136 @@ +package com.tencent.qcloud.tuikit.timcommon.minimalistui.widget.message; + +import android.content.Context; +import android.graphics.Color; +import android.text.Layout; +import android.util.AttributeSet; +import android.util.TypedValue; +import android.widget.FrameLayout; +import android.widget.TextView; + +import androidx.annotation.NonNull; +import androidx.annotation.Nullable; + +import com.tencent.qcloud.tuikit.timcommon.R; + + +public class TimeInLineTextLayout extends FrameLayout { + + private TextView textView; + private MessageStatusTimeView statusArea; + + public TimeInLineTextLayout(@NonNull Context context) { + super(context); + init(context, null); + } + + public TimeInLineTextLayout(@NonNull Context context, @Nullable AttributeSet attrs) { + super(context, attrs); + init(context, attrs); + } + + public TimeInLineTextLayout(@NonNull Context context, @Nullable AttributeSet attrs, int defStyleAttr) { + super(context, attrs, defStyleAttr); + init(context, attrs); + } + + private void init(Context context, AttributeSet attrs) { + textView = new TextView(context, null, R.style.ChatMinimalistMessageTextStyle); + textView.setTextColor(Color.BLACK); + textView.setTextSize(TypedValue.COMPLEX_UNIT_PX, textView.getResources().getDimension(R.dimen.chat_minimalist_message_text_size)); + LayoutParams textViewParams = new LayoutParams(LayoutParams.WRAP_CONTENT, LayoutParams.WRAP_CONTENT); + addView(textView, textViewParams); + statusArea = new MessageStatusTimeView(context); + LayoutParams statusAreaParams = new LayoutParams(LayoutParams.WRAP_CONTENT, LayoutParams.WRAP_CONTENT); + addView(statusArea, statusAreaParams); + } + + public void setText(CharSequence charSequence) { + textView.setText(charSequence); + } + + public void setText(int resID) { + textView.setText(resID); + } + + public void setStatusIcon(int resID) { + statusArea.setStatusIcon(resID); + } + + public void setTimeText(CharSequence charSequence) { + statusArea.setTimeText(charSequence); + } + + public void setTimeColor(int color) { + statusArea.setTimeColor(color); + } + + public void setTextColor(int color) { + textView.setTextColor(color); + } + + public void setTextSize(int size) { + textView.setTextSize(size); + } + + public TextView getTextView() { + return textView; + } + + public void setOnStatusAreaClickListener(OnClickListener listener) { + statusArea.setOnClickListener(listener); + } + + public void setOnStatusAreaLongClickListener(OnLongClickListener listener) { + statusArea.setOnLongClickListener(listener); + } + + @Override + protected void onLayout(boolean changed, int left, int top, int right, int bottom) { + int textViewWidth = textView.getMeasuredWidth(); + int textViewHeight = textView.getMeasuredHeight(); + textView.layout(0, 0, textViewWidth, textViewHeight); + int statusAreaWidth = statusArea.getMeasuredWidth(); + int statusAreaHeight = statusArea.getMeasuredHeight(); + statusArea.layout(right - left - statusAreaWidth, bottom - top - statusAreaHeight, right - left, bottom - top); + } + + @Override + protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) { + int maxWidth, maxHeight; + // measure text view + measureChildren(widthMeasureSpec, heightMeasureSpec); + + int textWidth = textView.getMeasuredWidth(); + int textHeight = textView.getMeasuredHeight(); + int lineCount = textView.getLineCount(); + + // get last line's width + int lastLineWidth = 0; + Layout layout = textView.getLayout(); + if (layout != null) { + int start = layout.getLineStart(lineCount - 1); + int end = layout.getLineEnd(lineCount - 1); + float startX = layout.getPrimaryHorizontal(start); + float endX = layout.getSecondaryHorizontal(end); + lastLineWidth = (int) (endX - startX); + } + + int statusAreaWidth = statusArea.getMeasuredWidth(); + int statusAreaHeight = statusArea.getMeasuredHeight(); + MarginLayoutParams lp = (MarginLayoutParams) statusArea.getLayoutParams(); + statusAreaWidth += lp.leftMargin + lp.rightMargin; + + int layoutWidth = MeasureSpec.getSize(widthMeasureSpec); + // switch a new line + if (lastLineWidth + statusAreaWidth > layoutWidth) { + maxHeight = textHeight + statusAreaHeight; + maxWidth = Math.max(textWidth, statusAreaWidth); + } else { + maxHeight = Math.max(textHeight, statusAreaHeight); + maxWidth = Math.max(textWidth, lastLineWidth + statusAreaWidth); + } + + setMeasuredDimension(maxWidth, maxHeight); + } +} diff --git a/TIMCommon/timcommon/src/main/java/com/tencent/qcloud/tuikit/timcommon/util/DateTimeUtil.java b/TIMCommon/timcommon/src/main/java/com/tencent/qcloud/tuikit/timcommon/util/DateTimeUtil.java new file mode 100644 index 0000000..6c27881 --- /dev/null +++ b/TIMCommon/timcommon/src/main/java/com/tencent/qcloud/tuikit/timcommon/util/DateTimeUtil.java @@ -0,0 +1,157 @@ +package com.tencent.qcloud.tuikit.timcommon.util; + +import android.content.Context; + +import com.tencent.qcloud.tuicore.TUIConfig; +import com.tencent.qcloud.tuicore.TUIThemeManager; +import com.tencent.qcloud.tuikit.timcommon.R; + +import java.text.ParseException; +import java.text.SimpleDateFormat; +import java.util.Calendar; +import java.util.Date; +import java.util.Locale; + + +public class DateTimeUtil { + + private final static long minute = 60 * 1000; + private final static long hour = 60 * minute; + private final static long day = 24 * hour; + private final static long week = 7 * day; + private final static long month = 31 * day; + private final static long year = 12 * month; + + /** + * return format text for time + * you can see https://docs.oracle.com/javase/8/docs/api/java/util/Formatter.html + * today:HH:MM + * this week:Sunday, Friday .. + * this year:MM/DD + * before this year:YYYY/MM/DD + * @param date current time + * @return format text + */ + public static String getTimeFormatText(Date date) { + if (date == null) { + return ""; + } + Context context = TUIConfig.getAppContext(); + Locale locale; + if (context == null) { + locale = Locale.getDefault(); + } else { + locale = TUIThemeManager.getInstance().getLocale(context); + } + String timeText; + Calendar calendar = Calendar.getInstance(); + calendar.set(Calendar.HOUR_OF_DAY, 0); + calendar.set(Calendar.MINUTE, 0); + calendar.set(Calendar.SECOND, 0); + calendar.set(Calendar.MILLISECOND, 0); + long dayStartTimeInMillis = calendar.getTimeInMillis(); + calendar = Calendar.getInstance(); + calendar.set(Calendar.DAY_OF_WEEK, 1); + calendar.set(Calendar.HOUR_OF_DAY, 0); + calendar.set(Calendar.MINUTE, 0); + calendar.set(Calendar.SECOND, 0); + calendar.set(Calendar.MILLISECOND, 0); + long weekStartTimeInMillis = calendar.getTimeInMillis(); + calendar = Calendar.getInstance(); + calendar.set(Calendar.DAY_OF_YEAR, 1); + calendar.set(Calendar.HOUR_OF_DAY, 0); + calendar.set(Calendar.MINUTE, 0); + calendar.set(Calendar.SECOND, 0); + calendar.set(Calendar.MILLISECOND, 0); + long yearStartTimeInMillis = calendar.getTimeInMillis(); + long outTimeMillis = date.getTime(); + if (outTimeMillis < yearStartTimeInMillis) { + timeText = String.format(locale, "%tD", date); + } else if (outTimeMillis < weekStartTimeInMillis) { + timeText = String.format(locale, "%1$tm/%1$td", date); + } else if (outTimeMillis < dayStartTimeInMillis) { + timeText = String.format(locale, "%tA", date); + } else { + timeText = String.format(locale, "%tR", date); + } + return timeText; + } + + /** + * HH:MM + * @param date current time + * @return format text e.g. "12:12" + */ + public static String getHMTimeString(Date date) { + if (date == null) { + return ""; + } + Context context = TUIConfig.getAppContext(); + Locale locale; + if (context == null) { + locale = Locale.getDefault(); + } else { + locale = TUIThemeManager.getInstance().getLocale(context); + } + return String.format(locale, "%tR", date); + } + + public static String formatSeconds(long seconds) { + Context context = TUIConfig.getAppContext(); + String timeStr = seconds + context.getString(R.string.date_second_short); + if (seconds > 60) { + long second = seconds % 60; + long min = seconds / 60; + timeStr = min + context.getString(R.string.date_minute_short) + second + context.getString(R.string.date_second_short); + if (min > 60) { + min = (seconds / 60) % 60; + long hour = (seconds / 60) / 60; + timeStr = hour + context.getString(R.string.date_hour_short) + min + context.getString(R.string.date_minute_short) + second + context.getString(R.string.date_second_short); + if (hour % 24 == 0) { + long day = (((seconds / 60) / 60) / 24); + timeStr = day + context.getString(R.string.date_day_short); + } else if (hour > 24) { + hour = ((seconds / 60) / 60) % 24; + long day = (((seconds / 60) / 60) / 24); + timeStr = day + context.getString(R.string.date_day_short) + hour + context.getString(R.string.date_hour_short) + min + context.getString(R.string.date_minute_short) + second + context.getString(R.string.date_second_short); + } + } + } + return timeStr; + } + + public static String formatSecondsTo00(int timeSeconds) { + int second = timeSeconds % 60; + int minuteTemp = timeSeconds / 60; + if (minuteTemp > 0) { + int minute = minuteTemp % 60; + int hour = minuteTemp / 60; + if (hour > 0) { + return (hour >= 10 ? (hour + "") : ("0" + hour)) + ":" + (minute >= 10 ? (minute + "") : ("0" + minute)) + + ":" + (second >= 10 ? (second + "") : ("0" + second)); + } else { + return (minute >= 10 ? (minute + "") : ("0" + minute)) + ":" + + (second >= 10 ? (second + "") : ("0" + second)); + } + } else { + return "00:" + (second >= 10 ? (second + "") : ("0" + second)); + } + } + + public static long getStringToDate(String dateString, String pattern) { + SimpleDateFormat dateFormat = new SimpleDateFormat(pattern); + Date date = new Date(); + try{ + date = dateFormat.parse(dateString); + } catch(ParseException e) { + // TODO Auto-generated catch block + e.printStackTrace(); + } + return date.getTime(); + } + + public static String getTimeStringFromDate(Date date, String pattern) { + SimpleDateFormat simpleDateFormat = new SimpleDateFormat(pattern); + return simpleDateFormat.format(date); + } +} diff --git a/TIMCommon/timcommon/src/main/java/com/tencent/qcloud/tuikit/timcommon/util/FileProvider.java b/TIMCommon/timcommon/src/main/java/com/tencent/qcloud/tuikit/timcommon/util/FileProvider.java new file mode 100644 index 0000000..45f8d4c --- /dev/null +++ b/TIMCommon/timcommon/src/main/java/com/tencent/qcloud/tuikit/timcommon/util/FileProvider.java @@ -0,0 +1,4 @@ +package com.tencent.qcloud.tuikit.timcommon.util; + +public class FileProvider extends androidx.core.content.FileProvider { +} diff --git a/TIMCommon/timcommon/src/main/java/com/tencent/qcloud/tuikit/timcommon/util/FileUtil.java b/TIMCommon/timcommon/src/main/java/com/tencent/qcloud/tuikit/timcommon/util/FileUtil.java new file mode 100644 index 0000000..2276260 --- /dev/null +++ b/TIMCommon/timcommon/src/main/java/com/tencent/qcloud/tuikit/timcommon/util/FileUtil.java @@ -0,0 +1,499 @@ +package com.tencent.qcloud.tuikit.timcommon.util; + +import android.content.ContentResolver; +import android.content.ContentUris; +import android.content.Context; +import android.content.Intent; +import android.database.Cursor; +import android.graphics.Bitmap; +import android.net.Uri; +import android.os.Build; +import android.os.Environment; +import android.provider.DocumentsContract; +import android.provider.MediaStore; +import android.provider.OpenableColumns; +import android.text.TextUtils; +import android.util.Log; +import android.webkit.MimeTypeMap; + +import androidx.annotation.NonNull; +import androidx.annotation.Nullable; + +import com.tencent.qcloud.tuicore.TUIConfig; +import com.tencent.qcloud.tuicore.TUILogin; +import com.tencent.qcloud.tuikit.timcommon.R; + +import java.io.BufferedOutputStream; +import java.io.File; +import java.io.FileOutputStream; +import java.io.IOException; +import java.io.InputStream; +import java.text.DecimalFormat; +import java.text.SimpleDateFormat; +import java.util.Date; +import java.util.Locale; + +public class FileUtil { + + + public static final String DOCUMENTS_DIR = "documents"; + + public static final String FILE_PROVIDER_AUTH = ".timcommon.fileprovider"; + + public static final int SIZETYPE_B = 1; + public static final int SIZETYPE_KB = 2; + public static final int SIZETYPE_MB = 3; + public static final int SIZETYPE_GB = 4; + + public static String saveBitmap(String dir, Bitmap b) { + String jpegName = TUIConfig.getMediaDir() + "picture_" + new SimpleDateFormat("yyyyMMdd_HHmmss", Locale.getDefault()).format(new Date()) + ".jpg"; + try { + FileOutputStream fout = new FileOutputStream(jpegName); + BufferedOutputStream bos = new BufferedOutputStream(fout); + b.compress(Bitmap.CompressFormat.JPEG, 100, bos); + bos.flush(); + bos.close(); + return jpegName; + } catch (IOException e) { + e.printStackTrace(); + return ""; + } + } + + public static boolean deleteFile(String url) { + boolean result = false; + File file = new File(url); + if (file.exists()) { + result = file.delete(); + } + return result; + } + + public static String getPathFromUri(Uri uri) { + String path = ""; + try { + int sdkVersion = Build.VERSION.SDK_INT; + if (sdkVersion >= 19) { + path = getPathByCopyFile(TUILogin.getAppContext(), uri); + } else { + path = getRealFilePath(uri); + } + } catch (Exception e) { + e.printStackTrace(); + } + if (path == null) { + path = ""; + } + return path; + } + + public static String getRealFilePath(Uri uri) { + if (null == uri) { + return null; + } + final String scheme = uri.getScheme(); + String data = null; + if (scheme == null) { + data = uri.getPath(); + } else if (ContentResolver.SCHEME_FILE.equals(scheme)) { + data = uri.getPath(); + } else if (ContentResolver.SCHEME_CONTENT.equals(scheme)) { + Cursor cursor = TUILogin.getAppContext().getContentResolver().query(uri, new String[]{MediaStore.Images.ImageColumns.DATA}, null, null, null); + if (null != cursor) { + if (cursor.moveToFirst()) { + int index = cursor.getColumnIndex(MediaStore.Images.ImageColumns.DATA); + if (index > -1) { + data = cursor.getString(index); + } + } + cursor.close(); + } + } + return data; + } + + public static Uri getUriFromPath(String path) { + try { + if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.N) { + return FileProvider.getUriForFile(TUIConfig.getAppContext(), TUIConfig.getAppContext().getApplicationInfo().packageName + FILE_PROVIDER_AUTH, new File(path)); + } else { + return Uri.fromFile(new File(path)); + } + + } catch (Exception e) { + e.printStackTrace(); + } + return null; + } + + /** + * 专为Android4.4以上设计的从Uri获取文件路径 + * + * Get file path from Uri specially designed for Android4.4 and above + */ + public static String getPath(final Context context, final Uri uri) { + + final boolean isKitKat = Build.VERSION.SDK_INT >= Build.VERSION_CODES.KITKAT; + + // DocumentProvider + if (isKitKat && DocumentsContract.isDocumentUri(context, uri)) { + // ExternalStorageProvider + if (isExternalStorageDocument(uri)) { + + final String docId = DocumentsContract.getDocumentId(uri); + final String[] split = docId.split(":"); + final String type = split[0]; + + if ("primary".equalsIgnoreCase(type)) { + return Environment.getExternalStorageDirectory() + "/" + split[1]; + } else { + return getPathByCopyFile(context, uri); + } + } + // DownloadsProvider + else if (isDownloadsDocument(uri)) { + + final String id = DocumentsContract.getDocumentId(uri); + if (id.startsWith("raw:")) { + final String path = id.replaceFirst("raw:", ""); + return path; + } + String[] contentUriPrefixesToTry = new String[]{ + "content://downloads/public_downloads", + "content://downloads/my_downloads", + "content://downloads/all_downloads" + }; + + for (String contentUriPrefix : contentUriPrefixesToTry) { + Uri contentUri = ContentUris.withAppendedId(Uri.parse(contentUriPrefix), Long.parseLong(id)); + try { + String path = getDataColumn(context, contentUri, null, null); + if (path != null && Build.VERSION.SDK_INT < 29) { + return path; + } + } catch (Exception e) { + e.printStackTrace(); + } + } + + // 在某些android8+的手机上,无法获取路径,所以用拷贝的方式,获取新文件名,然后把文件发出去 + // On some android8+ mobile phones, the path cannot be obtained, so the new file name is obtained by copying, and then the file is sent out + return getPathByCopyFile(context, uri); + } + // MediaProvider + else if (isMediaDocument(uri)) { + + final String docId = DocumentsContract.getDocumentId(uri); + final String[] split = docId.split(":"); + final String type = split[0]; + + Uri contentUri = null; + if ("image".equals(type)) { + contentUri = MediaStore.Images.Media.EXTERNAL_CONTENT_URI; + } else if ("video".equals(type)) { + contentUri = MediaStore.Video.Media.EXTERNAL_CONTENT_URI; + } else if ("audio".equals(type)) { + contentUri = MediaStore.Audio.Media.EXTERNAL_CONTENT_URI; + } + + final String selection = "_id=?"; + final String[] selectionArgs = new String[]{split[1]}; + + String path = getDataColumn(context, contentUri, selection, selectionArgs); + if (TextUtils.isEmpty(path) || Build.VERSION.SDK_INT >= 29) { + path = getPathByCopyFile(context, uri); + } + return path; + } + } + // MediaStore (and general) + else if ("content".equalsIgnoreCase(uri.getScheme())) { + String path = getDataColumn(context, uri, null, null); + if (TextUtils.isEmpty(path) || Build.VERSION.SDK_INT >= 29) { + // 在某些华为android9+的手机上,无法获取路径,所以用拷贝的方式,获取新文件名,然后把文件发出去 + path = getPathByCopyFile(context, uri); + } + return path; + } + // File + else if ("file".equalsIgnoreCase(uri.getScheme())) { + return uri.getPath(); + } + + return null; + } + + private static String getPathByCopyFile(Context context, Uri uri) { + String fileName = getFileName(context, uri); + File cacheDir = getDocumentCacheDir(context); + File file = generateFileName(fileName, cacheDir); + String destinationPath = null; + if (file != null) { + destinationPath = file.getAbsolutePath(); + boolean saveSuccess = saveFileFromUri(context, uri, destinationPath); + if (!saveSuccess) { + file.delete(); + return null; + } + } + + return destinationPath; + } + + @Nullable + public static File generateFileName(@Nullable String name, File directory) { + if (name == null) { + return null; + } + + File file = new File(directory, name); + + if (file.exists()) { + String fileName = name; + String extension = ""; + int dotIndex = name.lastIndexOf('.'); + if (dotIndex > 0) { + fileName = name.substring(0, dotIndex); + extension = name.substring(dotIndex); + } + + int index = 0; + + while (file.exists()) { + index++; + name = fileName + '(' + index + ')' + extension; + file = new File(directory, name); + } + } + + try { + if (!file.createNewFile()) { + return null; + } + } catch (IOException e) { + e.printStackTrace(); + return null; + } + + + return file; + } + + public static String getFileName(@NonNull Context context, Uri uri) { + String mimeType = context.getContentResolver().getType(uri); + String filename = null; + + if (mimeType == null && context != null) { + filename = getName(uri.toString()); + } else { + Cursor returnCursor = context.getContentResolver().query(uri, null, + null, null, null); + if (returnCursor != null) { + int nameIndex = returnCursor.getColumnIndex(OpenableColumns.DISPLAY_NAME); + returnCursor.moveToFirst(); + filename = returnCursor.getString(nameIndex); + returnCursor.close(); + } + } + + return filename; + } + + private static String getName(String filename) { + if (filename == null) { + return null; + } + int index = filename.lastIndexOf('/'); + return filename.substring(index + 1); + } + + public static File getDocumentCacheDir(@NonNull Context context) { + File dir = new File(context.getCacheDir(), DOCUMENTS_DIR); + if (!dir.exists()) { + dir.mkdirs(); + } + + return dir; + } + + private static boolean saveFileFromUri(Context context, Uri uri, String destinationPath) { + InputStream is = null; + BufferedOutputStream bos = null; + try { + is = context.getContentResolver().openInputStream(uri); + bos = new BufferedOutputStream(new FileOutputStream(destinationPath, false)); + byte[] buf = new byte[1024]; + + int actualBytes; + while ((actualBytes = is.read(buf)) != -1) { + bos.write(buf, 0, actualBytes); + } + } catch (IOException e) { + e.printStackTrace(); + return false; + } finally { + try { + if (is != null) is.close(); + if (bos != null) bos.close(); + } catch (IOException e) { + e.printStackTrace(); + } + } + return true; + } + + /** + * Get the value of the data column for this Uri. This is useful for + * MediaStore Uris, and other file-based ContentProviders. + * + * @param context The context. + * @param uri The Uri to query. + * @param selection (Optional) Filter used in the query. + * @param selectionArgs (Optional) Selection arguments used in the query. + * @return The value of the _data column, which is typically a file path. + */ + public static String getDataColumn(Context context, Uri uri, String selection, + String[] selectionArgs) { + + Cursor cursor = null; + final String column = "_data"; + final String[] projection = {column}; + + try { + cursor = context.getContentResolver().query(uri, projection, selection, selectionArgs, + null); + if (cursor != null && cursor.moveToFirst()) { + final int column_index = cursor.getColumnIndexOrThrow(column); + return cursor.getString(column_index); + } + } catch (Exception e) { + e.printStackTrace(); + } finally { + if (cursor != null) { + cursor.close(); + } + } + return null; + } + + /** + * @param uri The Uri to check. + * @return Whether the Uri authority is ExternalStorageProvider. + */ + public static boolean isExternalStorageDocument(Uri uri) { + return "com.android.externalstorage.documents".equals(uri.getAuthority()); + } + + /** + * @param uri The Uri to check. + * @return Whether the Uri authority is DownloadsProvider. + */ + public static boolean isDownloadsDocument(Uri uri) { + return "com.android.providers.downloads.documents".equals(uri.getAuthority()); + } + + /** + * @param uri The Uri to check. + * @return Whether the Uri authority is MediaProvider. + */ + public static boolean isMediaDocument(Uri uri) { + return "com.android.providers.media.documents".equals(uri.getAuthority()); + } + + + /** + * 转换文件大小 + * + * Convert file size to string + * + * @param fileS + * @return + */ + public static String formatFileSize(long fileS) { + DecimalFormat df = new DecimalFormat("#.00"); + String fileSizeString = ""; + String wrongSize = "0B"; + if (fileS == 0) { + return wrongSize; + } + if (fileS < 1024) { + fileSizeString = df.format((double) fileS) + "B"; + } else if (fileS < 1048576) { + fileSizeString = df.format((double) fileS / 1024) + "KB"; + } else if (fileS < 1073741824) { + fileSizeString = df.format((double) fileS / 1048576) + "MB"; + } else { + fileSizeString = df.format((double) fileS / 1073741824) + "GB"; + } + return fileSizeString; + } + + + // 修复 android.webkit.MimeTypeMap 的 getFileExtensionFromUrl 方法不支持中文的问题 + // fix the problem that getFileExtensionFromUrl does not support Chinese + public static String getFileExtensionFromUrl(String url) { + if (!TextUtils.isEmpty(url)) { + int fragment = url.lastIndexOf('#'); + if (fragment > 0) { + url = url.substring(0, fragment); + } + + int query = url.lastIndexOf('?'); + if (query > 0) { + url = url.substring(0, query); + } + + int filenamePos = url.lastIndexOf('/'); + String filename = + 0 <= filenamePos ? url.substring(filenamePos + 1) : url; + + // if the filename contains special characters, we don't + // consider it valid for our matching purposes: + // 去掉正则表达式判断以添加中文支持 +// if (!filename.isEmpty() && Pattern.matches("[a-zA-Z_0-9\\.\\-\\(\\)\\%]+", filename)) + if (!filename.isEmpty()) { + int dotPos = filename.lastIndexOf('.'); + if (0 <= dotPos) { + return filename.substring(dotPos + 1).toLowerCase(); + } + } + } + + return ""; + } + + public static void openFile(String path, String fileName) { + Uri uri = FileProvider.getUriForFile(TUIConfig.getAppContext(), + TUIConfig.getAppContext().getApplicationInfo().packageName + FILE_PROVIDER_AUTH, + new File(path)); + if (uri == null) { + Log.e("FileUtil", "openFile failed , uri is null"); + return; + } + String fileExtension; + if (TextUtils.isEmpty(fileName)) { + fileExtension = FileUtil.getFileExtensionFromUrl(path); + } else { + fileExtension = FileUtil.getFileExtensionFromUrl(fileName); + } + String mimeType = MimeTypeMap.getSingleton().getMimeTypeFromExtension(fileExtension); + Intent intent = new Intent(Intent.ACTION_VIEW); + intent.addFlags(Intent.FLAG_GRANT_READ_URI_PERMISSION); + intent.setDataAndType(uri, mimeType); + try { + Intent chooserIntent = Intent.createChooser(intent, TUIConfig.getAppContext().getString(R.string.open_file_tips)); + chooserIntent.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK); + TUIConfig.getAppContext().startActivity(chooserIntent); + } catch (Exception e) { + Log.e("FileUtil", "openFile failed , " + e.getMessage()); + } + } + + public static long getFileSize(String path) { + File file = new File(path); + if (file.exists()) { + return file.length(); + } + return 0; + } +} diff --git a/TIMCommon/timcommon/src/main/java/com/tencent/qcloud/tuikit/timcommon/util/ImageUtil.java b/TIMCommon/timcommon/src/main/java/com/tencent/qcloud/tuikit/timcommon/util/ImageUtil.java new file mode 100644 index 0000000..9bdd63b --- /dev/null +++ b/TIMCommon/timcommon/src/main/java/com/tencent/qcloud/tuikit/timcommon/util/ImageUtil.java @@ -0,0 +1,465 @@ +package com.tencent.qcloud.tuikit.timcommon.util; + +import android.graphics.Bitmap; +import android.graphics.BitmapFactory; +import android.graphics.Canvas; +import android.graphics.Matrix; +import android.graphics.Paint; +import android.graphics.PorterDuff; +import android.graphics.PorterDuffXfermode; +import android.graphics.Rect; +import android.graphics.RectF; +import android.media.ExifInterface; +import android.net.Uri; +import android.text.TextUtils; + +import com.tencent.qcloud.tuicore.TUIConfig; +import com.tencent.qcloud.tuicore.TUILogin; +import com.tencent.qcloud.tuicore.util.SPUtils; + +import java.io.ByteArrayInputStream; +import java.io.ByteArrayOutputStream; +import java.io.File; +import java.io.FileNotFoundException; +import java.io.FileOutputStream; +import java.io.IOException; +import java.io.InputStream; + + +public class ImageUtil { + public final static String SP_IMAGE = "_conversation_group_face"; + + /** + * @param outFile + * @param bitmap + * @return + */ + public static File storeBitmap(File outFile, Bitmap bitmap) { + if (!outFile.exists() || outFile.isDirectory()) { + outFile.getParentFile().mkdirs(); + } + FileOutputStream fOut = null; + try { + outFile.deleteOnExit(); + outFile.createNewFile(); + fOut = new FileOutputStream(outFile); + bitmap.compress(Bitmap.CompressFormat.JPEG, 100, fOut); + fOut.flush(); + } catch (IOException e1) { + outFile.deleteOnExit(); + } finally { + if (null != fOut) { + try { + fOut.close(); + } catch (IOException e) { + e.printStackTrace(); + outFile.deleteOnExit(); + } + } + } + return outFile; + } + + public static Bitmap getBitmapFormPath(Uri uri) { + Bitmap bitmap = null; + try { + InputStream input = TUIConfig.getAppContext().getContentResolver().openInputStream(uri); + BitmapFactory.Options onlyBoundsOptions = new BitmapFactory.Options(); + onlyBoundsOptions.inJustDecodeBounds = true; + onlyBoundsOptions.inDither = true;//optional + onlyBoundsOptions.inPreferredConfig = Bitmap.Config.ARGB_8888;//optional + BitmapFactory.decodeStream(input, null, onlyBoundsOptions); + input.close(); + int originalWidth = onlyBoundsOptions.outWidth; + int originalHeight = onlyBoundsOptions.outHeight; + if ((originalWidth == -1) || (originalHeight == -1)) + return null; + float hh = 800f; + float ww = 480f; + int degree = getBitmapDegree(uri); + if (degree == 90 || degree == 270) { + hh = 480; + ww = 800; + } + // 缩放比。由于是固定比例缩放,只用高或者宽其中一个数据进行计算即可 + // zoom ratio. Since it is a fixed scale scaling, only one data of height or width can be used for calculation. + int be = 1; + if (originalWidth > originalHeight && originalWidth > ww) { + be = (int) (originalWidth / ww); + } else if (originalWidth < originalHeight && originalHeight > hh) { + be = (int) (originalHeight / hh); + } + if (be <= 0) + be = 1; + BitmapFactory.Options bitmapOptions = new BitmapFactory.Options(); + bitmapOptions.inSampleSize = be; + bitmapOptions.inDither = true; + bitmapOptions.inPreferredConfig = Bitmap.Config.ARGB_8888; + input = TUIConfig.getAppContext().getContentResolver().openInputStream(uri); + bitmap = BitmapFactory.decodeStream(input, null, bitmapOptions); + + input.close(); + compressImage(bitmap); + bitmap = rotateBitmapByDegree(bitmap, degree); + + } catch (Exception e) { + e.printStackTrace(); + } + return bitmap; + } + + public static Bitmap getBitmapFormPath(String path) { + if (TextUtils.isEmpty(path)) { + return null; + } + return getBitmapFormPath(Uri.fromFile(new File(path))); + } + + public static Bitmap compressImage(Bitmap image) { + + ByteArrayOutputStream baos = new ByteArrayOutputStream(); + image.compress(Bitmap.CompressFormat.JPEG, 100, baos); + int options = 100; + while (baos.toByteArray().length / 1024 > 100) { + baos.reset(); + image.compress(Bitmap.CompressFormat.JPEG, options, baos); + options -= 10; + } + ByteArrayInputStream isBm = new ByteArrayInputStream(baos.toByteArray()); + Bitmap bitmap = BitmapFactory.decodeStream(isBm, null, null); + return bitmap; + } + + /** + * 读取图片的旋转的角度 + * + * Read the rotation angle of the image + */ + public static int getBitmapDegree(Uri uri) { + int degree = 0; + try { + ExifInterface exifInterface = new ExifInterface(FileUtil.getPathFromUri(uri)); + int orientation = exifInterface.getAttributeInt(ExifInterface.TAG_ORIENTATION, + ExifInterface.ORIENTATION_NORMAL); + switch (orientation) { + case ExifInterface.ORIENTATION_ROTATE_90: + degree = 90; + break; + case ExifInterface.ORIENTATION_ROTATE_180: + degree = 180; + break; + case ExifInterface.ORIENTATION_ROTATE_270: + degree = 270; + break; + } + } catch (IOException e) { + e.printStackTrace(); + } + return degree; + } + + /** + * 读取图片的旋转的角度 + * + * Read the rotation angle of the image + */ + public static int getBitmapDegree(String fileName) { + int degree = 0; + try { + ExifInterface exifInterface = new ExifInterface(fileName); + int orientation = exifInterface.getAttributeInt(ExifInterface.TAG_ORIENTATION, + ExifInterface.ORIENTATION_NORMAL); + switch (orientation) { + case ExifInterface.ORIENTATION_ROTATE_90: + degree = 90; + break; + case ExifInterface.ORIENTATION_ROTATE_180: + degree = 180; + break; + case ExifInterface.ORIENTATION_ROTATE_270: + degree = 270; + break; + } + } catch (IOException e) { + e.printStackTrace(); + } + return degree; + } + + /** + * 将图片按照某个角度进行旋转 + * + * @param bm 需要旋转的图片 + * @param degree 旋转角度 + * @return 旋转后的图片 + * + * + * Rotate the image by an angle + * + * @param bm image to be rotated + * @param degree Rotation angle + * @return rotated image + */ + public static Bitmap rotateBitmapByDegree(Bitmap bm, int degree) { + Bitmap returnBm = null; + + Matrix matrix = new Matrix(); + matrix.postRotate(degree); + try { + returnBm = Bitmap.createBitmap(bm, 0, 0, bm.getWidth(), bm.getHeight(), matrix, true); + } catch (OutOfMemoryError e) { + } + if (returnBm == null) { + returnBm = bm; + } + if (bm != returnBm) { + bm.recycle(); + } + return returnBm; + } + + public static int[] getImageSize(String path) { + int size[] = new int[2]; + try { + BitmapFactory.Options onlyBoundsOptions = new BitmapFactory.Options(); + onlyBoundsOptions.inJustDecodeBounds = true; + BitmapFactory.decodeFile(path, onlyBoundsOptions); + int originalWidth = onlyBoundsOptions.outWidth; + int originalHeight = onlyBoundsOptions.outHeight; + //size[0] = originalWidth; + //size[1] = originalHeight; + + int degree = getBitmapDegree(path); + if (degree == 0) { + size[0] = originalWidth; + size[1] = originalHeight; + } else { + float hh = 800f; + float ww = 480f; + if (degree == 90 || degree == 270) { + hh = 480; + ww = 800; + } + int be = 1; + if (originalWidth > originalHeight && originalWidth > ww) { + be = (int) (originalWidth / ww); + } else if (originalWidth < originalHeight && originalHeight > hh) { + be = (int) (originalHeight / hh); + } + if (be <= 0) + be = 1; + BitmapFactory.Options bitmapOptions = new BitmapFactory.Options(); + bitmapOptions.inSampleSize = be; + bitmapOptions.inDither = true; + bitmapOptions.inPreferredConfig = Bitmap.Config.ARGB_8888; + Bitmap bitmap = BitmapFactory.decodeFile(path, bitmapOptions); + bitmap = rotateBitmapByDegree(bitmap, degree); + size[0] = bitmap.getWidth(); + size[1] = bitmap.getHeight(); + } + } catch (Exception e) { + e.printStackTrace(); + } + return size; + + } + + + // 图片文件先在本地做旋转,返回旋转之后的图片文件路径 + // The image file is rotated locally, and the path of the image file after rotation is returned. + public static String getImagePathAfterRotate(final Uri uri) { + try { + InputStream is = TUIConfig.getAppContext().getContentResolver() + .openInputStream(uri); + Bitmap originBitmap = BitmapFactory.decodeStream(is, null, null); + int degree = ImageUtil.getBitmapDegree(uri); + if (degree == 0) { + return FileUtil.getPathFromUri(uri); + } else { + Bitmap newBitmap = ImageUtil.rotateBitmapByDegree(originBitmap, degree); + String oldName = FileUtil.getFileName(TUIConfig.getAppContext(), uri); + File newImageFile = FileUtil.generateFileName(oldName, FileUtil.getDocumentCacheDir(TUIConfig.getAppContext())); + if (newImageFile == null) { + return FileUtil.getPathFromUri(uri); + } + ImageUtil.storeBitmap(newImageFile, newBitmap); + newBitmap.recycle(); + return newImageFile.getAbsolutePath(); + } + }catch (FileNotFoundException e) { + return FileUtil.getPathFromUri(uri); + } + } + + /** + * 转换图片成圆形 + * + * @param bitmap 传入Bitmap对象 + * @return + * + * + * Convert image to circle + * + * @param bitmap Pass in a Bitmap object + * @return + */ + public static Bitmap toRoundBitmap(Bitmap bitmap) { + int width = bitmap.getWidth(); + int height = bitmap.getHeight(); + float roundPx; + float left, top, right, bottom, dst_left, dst_top, dst_right, dst_bottom; + if (width <= height) { + roundPx = width / 2; + left = 0; + top = 0; + right = width; + bottom = width; + height = width; + dst_left = 0; + dst_top = 0; + dst_right = width; + dst_bottom = width; + } else { + roundPx = height / 2; + float clip = (width - height) / 2; + left = clip; + right = width - clip; + top = 0; + bottom = height; + width = height; + dst_left = 0; + dst_top = 0; + dst_right = height; + dst_bottom = height; + } + + Bitmap output = Bitmap.createBitmap(width, height, Bitmap.Config.ARGB_8888); + Canvas canvas = new Canvas(output); + + final int color = 0xff424242; + final Paint paint = new Paint(); + final Rect src = new Rect((int) left, (int) top, (int) right, (int) bottom); + final Rect dst = new Rect((int) dst_left, (int) dst_top, (int) dst_right, (int) dst_bottom); + final RectF rectF = new RectF(dst); + + paint.setAntiAlias(true); + + canvas.drawARGB(0, 0, 0, 0); + paint.setColor(color); + + // canvas.drawRoundRect(rectF, roundPx, roundPx, paint); + canvas.drawCircle(roundPx, roundPx, roundPx, paint); + + paint.setXfermode(new PorterDuffXfermode(PorterDuff.Mode.SRC_IN)); + canvas.drawBitmap(bitmap, src, dst, paint); + + return output; + } + + public static boolean isImageDownloaded(String imagePath) { + try { + BitmapFactory.Options options = new BitmapFactory.Options(); + options.inJustDecodeBounds = true; + BitmapFactory.decodeFile(imagePath, options); +// ImageDecoder.Source src = ImageDecoder.createSource(mContext.getContentResolver(), +// uri, res); +// return ImageDecoder.decodeDrawable(src, (decoder, info, s) -> { +// decoder.setAllocator(ImageDecoder.ALLOCATOR_SOFTWARE); +// }); + + return true; + } catch (Exception e) { + e.printStackTrace(); + } + return false; + } + + /** + * 加载高分辨率图片需要做下适配 + * + * @param imagePath 图片路径 + * @return Bitmap 调整后的位图 + * + * + * Loading high-resolution images requires adaptation + * + * @param imagePath + * @return Bitmap + */ + public static Bitmap adaptBitmapFormPath(String imagePath, int reqWidth, int reqHeight){ + try { + BitmapFactory.Options options = new BitmapFactory.Options(); + options.inJustDecodeBounds = true; + BitmapFactory.decodeFile(imagePath, options); + + options.inSampleSize = calculateInSampleSize(options, reqWidth, reqHeight); + + options.inJustDecodeBounds = false; + return BitmapFactory.decodeFile(imagePath, options); + } catch (Exception e) { + e.printStackTrace(); + } + + return null; + } + + private static int calculateInSampleSize( + BitmapFactory.Options options, int reqWidth, int reqHeight) { + // Raw height and width of image + final int height = options.outHeight; + final int width = options.outWidth; + int inSampleSize = 1; + + if (height > reqHeight || width > reqWidth) { + + final int halfHeight = height / 2; + final int halfWidth = width / 2; + + // Calculate the largest inSampleSize value that is a power of 2 and keeps both + // height and width larger than the requested height and width. + while ((halfHeight / inSampleSize) >= reqHeight + && (halfWidth / inSampleSize) >= reqWidth) { + inSampleSize *= 2; + } + } + + return inSampleSize; + } + + + /** + * 根据图片 UUID 和 类型得到图片文件路径 + * @param uuid 图片 UUID + * @param imageType 图片类型 V2TIMImageElem.V2TIM_IMAGE_TYPE_THUMB , V2TIMImageElem.V2TIM_IMAGE_TYPE_ORIGIN , + * V2TIMImageElem.V2TIM_IMAGE_TYPE_LARGE + * @return 图片文件路径 + * + * + * + * + * Get the image file path based on the image UUID and type + * @param uuid + * @param imageType V2TIMImageElem.V2TIM_IMAGE_TYPE_THUMB , V2TIMImageElem.V2TIM_IMAGE_TYPE_ORIGIN , + * V2TIMImageElem.V2TIM_IMAGE_TYPE_LARGE + * @return path + */ + public static String generateImagePath(String uuid, int imageType) { + return TUIConfig.getImageDownloadDir() + uuid + "_" + imageType; + } + + + public static String getGroupConversationAvatar(String conversationID) { + SPUtils spUtils = SPUtils.getInstance(TUILogin.getSdkAppId() + SP_IMAGE); + final String savedIcon = spUtils.getString(conversationID, ""); + if (!TextUtils.isEmpty(savedIcon) && new File(savedIcon).isFile() && new File(savedIcon).exists()) { + return savedIcon; + } + return ""; + } + + public static void setGroupConversationAvatar(String conversationId, String url) { + SPUtils spUtils = SPUtils.getInstance(TUILogin.getSdkAppId() + SP_IMAGE); + spUtils.put(conversationId, url); + } + +} diff --git a/TIMCommon/timcommon/src/main/java/com/tencent/qcloud/tuikit/timcommon/util/MessageBuilder.java b/TIMCommon/timcommon/src/main/java/com/tencent/qcloud/tuikit/timcommon/util/MessageBuilder.java new file mode 100644 index 0000000..6495940 --- /dev/null +++ b/TIMCommon/timcommon/src/main/java/com/tencent/qcloud/tuikit/timcommon/util/MessageBuilder.java @@ -0,0 +1,34 @@ +package com.tencent.qcloud.tuikit.timcommon.util; + +import android.text.TextUtils; + +import com.google.gson.Gson; +import com.google.gson.JsonSyntaxException; +import com.tencent.qcloud.tuikit.timcommon.bean.TUIMessageBean; + +import java.util.HashMap; + +public class MessageBuilder { + private static final String TAG = MessageBuilder.class.getSimpleName(); + + public static void mergeCloudCustomData(TUIMessageBean messageBean, String key, Object data) { + String cloudCustomData = messageBean.getV2TIMMessage().getCloudCustomData(); + Gson gson = new Gson(); + HashMap hashMap = null; + if (TextUtils.isEmpty(cloudCustomData)) { + hashMap = new HashMap(); + } else { + try { + hashMap = gson.fromJson(cloudCustomData, HashMap.class); + } catch (JsonSyntaxException e) { + TIMCommonLog.e(TAG, " mergeCloudCustomData error " + e.getMessage()); + } + } + if (hashMap != null) { + hashMap.put(key, data); + cloudCustomData = gson.toJson(hashMap); + } + messageBean.getV2TIMMessage().setCloudCustomData(cloudCustomData); + } + +} diff --git a/TIMCommon/timcommon/src/main/java/com/tencent/qcloud/tuikit/timcommon/util/MessageParser.java b/TIMCommon/timcommon/src/main/java/com/tencent/qcloud/tuikit/timcommon/util/MessageParser.java new file mode 100644 index 0000000..bac1e4b --- /dev/null +++ b/TIMCommon/timcommon/src/main/java/com/tencent/qcloud/tuikit/timcommon/util/MessageParser.java @@ -0,0 +1,100 @@ +package com.tencent.qcloud.tuikit.timcommon.util; + +import android.text.TextUtils; + +import com.google.gson.Gson; +import com.google.gson.JsonSyntaxException; +import com.tencent.imsdk.v2.V2TIMMessage; +import com.tencent.qcloud.tuikit.timcommon.bean.MessageFeature; +import com.tencent.qcloud.tuikit.timcommon.bean.MessageReactBean; +import com.tencent.qcloud.tuikit.timcommon.bean.MessageRepliesBean; +import com.tencent.qcloud.tuikit.timcommon.bean.TUIMessageBean; + +import java.util.HashMap; +import java.util.Map; + +public class MessageParser { + private static final String TAG = MessageParser.class.getSimpleName(); + + public static MessageReactBean parseMessageReact(TUIMessageBean messageBean) { + V2TIMMessage message = messageBean.getV2TIMMessage(); + String cloudCustomData = message.getCloudCustomData(); + + try { + Gson gson = new Gson(); + HashMap hashMap = gson.fromJson(cloudCustomData, HashMap.class); + if (hashMap != null) { + Object reactContentObj = hashMap.get(TIMCommonConstants.MESSAGE_REACT_KEY); + MessageReactBean reactBean = null; + if (reactContentObj instanceof Map) { + reactBean = gson.fromJson(gson.toJson(reactContentObj), MessageReactBean.class); + } + if (reactBean != null) { + if (reactBean.getVersion() > MessageReactBean.VERSION) { + return null; + } + return reactBean; + } + } + } catch (JsonSyntaxException e) { + TIMCommonLog.e(TAG, " getCustomJsonMap error "); + } + return null; + } + + public static MessageRepliesBean parseMessageReplies(TUIMessageBean messageBean) { + V2TIMMessage message = messageBean.getV2TIMMessage(); + String cloudCustomData = message.getCloudCustomData(); + + try { + Gson gson = new Gson(); + HashMap hashMap = gson.fromJson(cloudCustomData, HashMap.class); + if (hashMap != null) { + Object repliesContentObj = hashMap.get(TIMCommonConstants.MESSAGE_REPLIES_KEY); + MessageRepliesBean repliesBean = null; + if (repliesContentObj instanceof Map) { + repliesBean = gson.fromJson(gson.toJson(repliesContentObj), MessageRepliesBean.class); + } + if (repliesBean != null) { + if (repliesBean.getVersion() > MessageRepliesBean.VERSION) { + return null; + } + return repliesBean; + } + } + } catch (JsonSyntaxException e) { + TIMCommonLog.e(TAG, " getCustomJsonMap error "); + } + return null; + } + + + public static MessageFeature isSupportTyping(TUIMessageBean messageBean) { + String cloudCustomData = messageBean.getV2TIMMessage().getCloudCustomData(); + if (TextUtils.isEmpty(cloudCustomData)) { + return null; + } + try { + Gson gson = new Gson(); + HashMap featureHashMap = gson.fromJson(cloudCustomData, HashMap.class); + if (featureHashMap != null) { + Object featureContentObj = featureHashMap.get(TIMCommonConstants.MESSAGE_FEATURE_KEY); + MessageFeature messageFeature = null; + if (featureContentObj instanceof Map) { + messageFeature = gson.fromJson(gson.toJson(featureContentObj), MessageFeature.class); + } + if (messageFeature != null) { + if (messageFeature.getVersion() > MessageFeature.VERSION) { + return null; + } + + return messageFeature; + } + } + } catch (JsonSyntaxException e) { + TIMCommonLog.e(TAG, " isSupportTyping exception e = " + e); + } + return null; + } + +} diff --git a/TIMCommon/timcommon/src/main/java/com/tencent/qcloud/tuikit/timcommon/util/PopWindowUtil.java b/TIMCommon/timcommon/src/main/java/com/tencent/qcloud/tuikit/timcommon/util/PopWindowUtil.java new file mode 100644 index 0000000..f6e5f68 --- /dev/null +++ b/TIMCommon/timcommon/src/main/java/com/tencent/qcloud/tuikit/timcommon/util/PopWindowUtil.java @@ -0,0 +1,45 @@ +package com.tencent.qcloud.tuikit.timcommon.util; + +import android.app.Activity; +import android.app.AlertDialog; +import android.graphics.Color; +import android.graphics.drawable.ColorDrawable; +import android.os.Build; +import android.view.Gravity; +import android.view.View; +import android.view.WindowManager; +import android.widget.LinearLayout; +import android.widget.PopupWindow; + +import com.tencent.qcloud.tuikit.timcommon.R; + +public class PopWindowUtil { + + public static AlertDialog buildFullScreenDialog(Activity activity) { + if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.JELLY_BEAN_MR1) { + if (activity.isDestroyed()) + return null; + } + AlertDialog.Builder builder = new AlertDialog.Builder(activity, R.style.TUIKit_AlertDialogStyle); + builder.setTitle(""); + builder.setCancelable(true); + AlertDialog dialog = builder.create(); + dialog.getWindow().setDimAmount(0); + dialog.setCanceledOnTouchOutside(true); + dialog.show(); + dialog.getWindow().setLayout(LinearLayout.LayoutParams.MATCH_PARENT, LinearLayout.LayoutParams.MATCH_PARENT); + dialog.getWindow().setBackgroundDrawable(new ColorDrawable(Color.TRANSPARENT)); + return dialog; + } + + public static PopupWindow popupWindow(View windowView, View parent, int x, int y) { + PopupWindow popup = new PopupWindow(windowView, WindowManager.LayoutParams.WRAP_CONTENT, WindowManager.LayoutParams.WRAP_CONTENT); +// int[] position = calculatePopWindowPos(windowView, parent, x, y); + popup.setOutsideTouchable(true); + popup.setFocusable(true); + popup.setBackgroundDrawable(new ColorDrawable(0xAEEEEE00)); + popup.showAtLocation(windowView, Gravity.CENTER | Gravity.TOP, x, y); + return popup; + } + +} diff --git a/TIMCommon/timcommon/src/main/java/com/tencent/qcloud/tuikit/timcommon/util/ScreenUtil.java b/TIMCommon/timcommon/src/main/java/com/tencent/qcloud/tuikit/timcommon/util/ScreenUtil.java new file mode 100644 index 0000000..960effe --- /dev/null +++ b/TIMCommon/timcommon/src/main/java/com/tencent/qcloud/tuikit/timcommon/util/ScreenUtil.java @@ -0,0 +1,85 @@ +package com.tencent.qcloud.tuikit.timcommon.util; + +import android.content.Context; +import android.util.DisplayMetrics; +import android.util.Log; +import android.util.TypedValue; +import android.view.Display; +import android.view.WindowManager; + +import com.tencent.qcloud.tuicore.TUIConfig; + +public class ScreenUtil { + + private static final String TAG = ScreenUtil.class.getSimpleName(); + + public static int getScreenHeight(Context context) { + DisplayMetrics metric = new DisplayMetrics(); + WindowManager wm = (WindowManager) context.getSystemService(Context.WINDOW_SERVICE); + wm.getDefaultDisplay().getMetrics(metric); + return metric.heightPixels; + } + + public static int getScreenWidth(Context context) { + DisplayMetrics metric = new DisplayMetrics(); + WindowManager wm = (WindowManager) context.getSystemService(Context.WINDOW_SERVICE); + wm.getDefaultDisplay().getMetrics(metric); + return metric.widthPixels; + } + + public static int getPxByDp(float dp) { + float scale = TUIConfig.getAppContext().getResources().getDisplayMetrics().density; + return (int) (dp * scale + 0.5f); + } + + public static int getRealScreenHeight(Context context) { + WindowManager wm = (WindowManager) context.getSystemService(Context.WINDOW_SERVICE); + Display display = wm.getDefaultDisplay(); + DisplayMetrics dm = new DisplayMetrics(); + display.getRealMetrics(dm); + return dm.heightPixels; + } + + public static int getRealScreenWidth(Context context) { + WindowManager wm = (WindowManager) context.getSystemService(Context.WINDOW_SERVICE); + Display display = wm.getDefaultDisplay(); + DisplayMetrics dm = new DisplayMetrics(); + display.getRealMetrics(dm); + return dm.widthPixels; + } + + public static int getNavigationBarHeight(Context context){ + int resourceId = context.getResources().getIdentifier("navigation_bar_height", "dimen", "android"); + if (resourceId > 0) { + return context.getResources().getDimensionPixelSize(resourceId); + } + return 0; + } + + public static int[] scaledSize(int containerWidth, int containerHeight, int realWidth, int realHeight) { + Log.i(TAG, "scaledSize containerWidth: " + containerWidth + " containerHeight: " + containerHeight + + " realWidth: " + realWidth + " realHeight: " + realHeight); + float deviceRate = (float) containerWidth / (float) containerHeight; + float rate = (float) realWidth / (float) realHeight; + int width = 0; + int height = 0; + if (rate < deviceRate) { + height = containerHeight; + width = (int) (containerHeight * rate); + } else { + width = containerWidth; + height = (int) (containerWidth / rate); + } + return new int[]{width, height}; + } + + public static int dip2px(float dpValue) { + final float scale = TUIConfig.getAppContext().getResources().getDisplayMetrics().density; + return (int) (dpValue * scale + 0.5f); + } + + public static float dp2px(float dpValue, DisplayMetrics displayMetrics) { + return TypedValue.applyDimension(TypedValue.COMPLEX_UNIT_DIP, dpValue, displayMetrics); + } + +} diff --git a/TIMCommon/timcommon/src/main/java/com/tencent/qcloud/tuikit/timcommon/util/SoftKeyBoardUtil.java b/TIMCommon/timcommon/src/main/java/com/tencent/qcloud/tuikit/timcommon/util/SoftKeyBoardUtil.java new file mode 100644 index 0000000..a522890 --- /dev/null +++ b/TIMCommon/timcommon/src/main/java/com/tencent/qcloud/tuikit/timcommon/util/SoftKeyBoardUtil.java @@ -0,0 +1,63 @@ +package com.tencent.qcloud.tuikit.timcommon.util; + +import android.content.Context; +import android.graphics.Rect; +import android.os.IBinder; +import android.util.DisplayMetrics; +import android.view.View; +import android.view.Window; +import android.view.WindowManager; +import android.view.inputmethod.InputMethodManager; + +import com.tencent.qcloud.tuicore.TUIConfig; + +public class SoftKeyBoardUtil { + + public static void hideKeyBoard(IBinder token) { + InputMethodManager imm = (InputMethodManager) TUIConfig.getAppContext().getSystemService(Context.INPUT_METHOD_SERVICE); + if (imm != null) { + imm.hideSoftInputFromWindow(token, 0); + } + } + + public static void showKeyBoard(Window window) { + InputMethodManager imm = (InputMethodManager) TUIConfig.getAppContext().getSystemService(Context.INPUT_METHOD_SERVICE); + if (imm != null) { + if (!isSoftInputShown(window)) { + imm.toggleSoftInput(0, 0); + } + } + } + + public static void hideKeyBoard(Window window) { + InputMethodManager imm = (InputMethodManager) TUIConfig.getAppContext().getSystemService(Context.INPUT_METHOD_SERVICE); + if (imm != null) { + if (isSoftInputShown(window)) { + imm.toggleSoftInput(0, 0); + } + } + } + + private static boolean isSoftInputShown(Window window) { + View decorView = window.getDecorView(); + int screenHeight = decorView.getHeight(); + Rect rect = new Rect(); + decorView.getWindowVisibleDisplayFrame(rect); + return screenHeight - rect.bottom - getNavigateBarHeight(window.getWindowManager()) >= 0; + } + + private static int getNavigateBarHeight(WindowManager windowManager) { + DisplayMetrics metrics = new DisplayMetrics(); + windowManager.getDefaultDisplay().getMetrics(metrics); + int usableHeight = metrics.heightPixels; + windowManager.getDefaultDisplay().getRealMetrics(metrics); + int realHeight = metrics.heightPixels; + if (realHeight > usableHeight) { + return realHeight - usableHeight; + } else { + return 0; + } + } + +} + diff --git a/TIMCommon/timcommon/src/main/java/com/tencent/qcloud/tuikit/timcommon/util/TIMCommonConstants.java b/TIMCommon/timcommon/src/main/java/com/tencent/qcloud/tuikit/timcommon/util/TIMCommonConstants.java new file mode 100644 index 0000000..871378b --- /dev/null +++ b/TIMCommon/timcommon/src/main/java/com/tencent/qcloud/tuikit/timcommon/util/TIMCommonConstants.java @@ -0,0 +1,14 @@ +package com.tencent.qcloud.tuikit.timcommon.util; + +public class TIMCommonConstants { + public static final String MESSAGE_REPLY_KEY = "messageReply"; + public static final String MESSAGE_REPLIES_KEY = "messageReplies"; + public static final String MESSAGE_REACT_KEY = "messageReact"; + public static final String MESSAGE_FEATURE_KEY = "messageFeature"; + public static final String CHAT_SETTINGS_SP_NAME = "chat_settings_sp"; + public static final String CHAT_REPLY_GUIDE_SHOW_SP_KEY = "chat_reply_guide_show"; + + public static String covert2HTMLString(String original) { + return "\"" + original + "\""; + } +} diff --git a/TIMCommon/timcommon/src/main/java/com/tencent/qcloud/tuikit/timcommon/util/TIMCommonLog.java b/TIMCommon/timcommon/src/main/java/com/tencent/qcloud/tuikit/timcommon/util/TIMCommonLog.java new file mode 100644 index 0000000..afb52f7 --- /dev/null +++ b/TIMCommon/timcommon/src/main/java/com/tencent/qcloud/tuikit/timcommon/util/TIMCommonLog.java @@ -0,0 +1,85 @@ +package com.tencent.qcloud.tuikit.timcommon.util; + +import com.tencent.imsdk.common.IMLog; + +public class TIMCommonLog extends IMLog { + + private static final String PRE = "TIMCommon-"; + + private static String mixTag(String tag) { + return PRE + tag; + } + + /** + * 打印INFO级别日志 + * + * print INFO level log + * + * @param strTag TAG + * @param strInfo 消息 + */ + public static void v(String strTag, String strInfo) { + IMLog.v(mixTag(strTag), strInfo); + } + + /** + * 打印DEBUG级别日志 + * + * print DEBUG level log + * + * @param strTag TAG + * @param strInfo 消息 + */ + public static void d(String strTag, String strInfo) { + IMLog.d(mixTag(strTag), strInfo); + } + + /** + * 打印INFO级别日志 + * + * print INFO level log + * + * @param strTag TAG + * @param strInfo 消息 + */ + public static void i(String strTag, String strInfo) { + IMLog.i(mixTag(strTag), strInfo); + } + + /** + * 打印WARN级别日志 + * + * print WARN level log + * + * @param strTag TAG + * @param strInfo 消息 + */ + public static void w(String strTag, String strInfo) { + IMLog.w(mixTag(strTag), strInfo); + } + + /** + * 打印WARN级别日志 + * + * print WARN level log + * + * @param strTag TAG + * @param strInfo 消息 + */ + public static void w(String strTag, String strInfo, Throwable e) { + IMLog.w(mixTag(strTag), strInfo + e.getMessage()); + } + + /** + * 打印ERROR级别日志 + * + * print ERROR level log + * + * @param strTag TAG + * @param strInfo 消息 + */ + public static void e(String strTag, String strInfo) { + IMLog.e(mixTag(strTag), strInfo); + } + +} diff --git a/TIMCommon/timcommon/src/main/java/com/tencent/qcloud/tuikit/timcommon/util/TUIUtil.java b/TIMCommon/timcommon/src/main/java/com/tencent/qcloud/tuikit/timcommon/util/TUIUtil.java new file mode 100644 index 0000000..ec8cb5b --- /dev/null +++ b/TIMCommon/timcommon/src/main/java/com/tencent/qcloud/tuikit/timcommon/util/TUIUtil.java @@ -0,0 +1,57 @@ +package com.tencent.qcloud.tuikit.timcommon.util; + +import android.app.Application; +import android.content.Context; +import android.os.Build; +import android.text.TextUtils; + +import com.tencent.imsdk.v2.V2TIMManager; +import com.tencent.qcloud.tuikit.timcommon.R; +import com.tencent.qcloud.tuicore.TUIThemeManager; + +import java.lang.reflect.Method; + +public class TUIUtil { + private static String currentProcessName = ""; + + public static String getProcessName() { + if (!TextUtils.isEmpty(currentProcessName)) { + return currentProcessName; + } + + if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.P) { + currentProcessName = Application.getProcessName(); + return currentProcessName; + } + + try { + final Method declaredMethod = Class.forName("android.app.ActivityThread", false, Application.class.getClassLoader()) + .getDeclaredMethod("currentProcessName", (Class[]) new Class[0]); + declaredMethod.setAccessible(true); + final Object invoke = declaredMethod.invoke(null, new Object[0]); + if (invoke instanceof String) { + currentProcessName = (String) invoke; + } + } catch (Throwable e) { + e.printStackTrace(); + } + + return currentProcessName; + } + + public static int getDefaultGroupIconResIDByGroupType(Context context, String groupType) { + if (context == null || TextUtils.isEmpty(groupType)) { + return R.drawable.core_default_group_icon_community; + } + if (TextUtils.equals(groupType, V2TIMManager.GROUP_TYPE_WORK)) { + return TUIThemeManager.getAttrResId(context, R.attr.core_default_group_icon_work); + } else if (TextUtils.equals(groupType, V2TIMManager.GROUP_TYPE_MEETING)) { + return TUIThemeManager.getAttrResId(context, R.attr.core_default_group_icon_meeting); + } else if (TextUtils.equals(groupType, V2TIMManager.GROUP_TYPE_PUBLIC)) { + return TUIThemeManager.getAttrResId(context, R.attr.core_default_group_icon_public); + } else if (TextUtils.equals(groupType, V2TIMManager.GROUP_TYPE_COMMUNITY)) { + return TUIThemeManager.getAttrResId(context, R.attr.core_default_group_icon_community); + } + return R.drawable.core_default_group_icon_community; + } +} diff --git a/TIMCommon/timcommon/src/main/java/com/tencent/qcloud/tuikit/timcommon/util/ThreadUtils.java b/TIMCommon/timcommon/src/main/java/com/tencent/qcloud/tuikit/timcommon/util/ThreadUtils.java new file mode 100644 index 0000000..05b0925 --- /dev/null +++ b/TIMCommon/timcommon/src/main/java/com/tencent/qcloud/tuikit/timcommon/util/ThreadUtils.java @@ -0,0 +1,37 @@ +package com.tencent.qcloud.tuikit.timcommon.util; + +import android.os.Handler; +import android.os.Looper; + +import java.util.concurrent.ExecutorService; +import java.util.concurrent.Executors; + +public class ThreadUtils { + private static final Handler handler = new Handler(Looper.getMainLooper()); + private static final ExecutorService executors = Executors.newCachedThreadPool(); + private ThreadUtils() {} + + public static void execute(Runnable runnable) { + executors.execute(runnable); + } + + public static boolean isOnMainThread() { + return Thread.currentThread() == Looper.getMainLooper().getThread(); + } + + public static void runOnUiThread(Runnable runnable) { + if (isOnMainThread()) { + runnable.run(); + } else { + postOnUiThread(runnable); + } + } + + public static boolean postOnUiThread(Runnable runnable) { + return handler.post(runnable); + } + + public static boolean postOnUiThreadDelayed(Runnable runnable, long delayMillis) { + return handler.postDelayed(runnable, delayMillis); + } +} diff --git a/TIMCommon/timcommon/src/main/res-light/drawable-xxhdpi/core_default_group_icon_community_light.png b/TIMCommon/timcommon/src/main/res-light/drawable-xxhdpi/core_default_group_icon_community_light.png new file mode 100644 index 0000000..67d36c2 Binary files /dev/null and b/TIMCommon/timcommon/src/main/res-light/drawable-xxhdpi/core_default_group_icon_community_light.png differ diff --git a/TIMCommon/timcommon/src/main/res-light/drawable-xxhdpi/core_default_group_icon_meeting_light.png b/TIMCommon/timcommon/src/main/res-light/drawable-xxhdpi/core_default_group_icon_meeting_light.png new file mode 100644 index 0000000..f32ec47 Binary files /dev/null and b/TIMCommon/timcommon/src/main/res-light/drawable-xxhdpi/core_default_group_icon_meeting_light.png differ diff --git a/TIMCommon/timcommon/src/main/res-light/drawable-xxhdpi/core_default_group_icon_public_light.png b/TIMCommon/timcommon/src/main/res-light/drawable-xxhdpi/core_default_group_icon_public_light.png new file mode 100644 index 0000000..03f988e Binary files /dev/null and b/TIMCommon/timcommon/src/main/res-light/drawable-xxhdpi/core_default_group_icon_public_light.png differ diff --git a/TIMCommon/timcommon/src/main/res-light/drawable-xxhdpi/core_default_group_icon_work_light.png b/TIMCommon/timcommon/src/main/res-light/drawable-xxhdpi/core_default_group_icon_work_light.png new file mode 100644 index 0000000..9002d45 Binary files /dev/null and b/TIMCommon/timcommon/src/main/res-light/drawable-xxhdpi/core_default_group_icon_work_light.png differ diff --git a/TIMCommon/timcommon/src/main/res-light/drawable-xxhdpi/core_default_user_icon_light.png b/TIMCommon/timcommon/src/main/res-light/drawable-xxhdpi/core_default_user_icon_light.png new file mode 100644 index 0000000..49de546 Binary files /dev/null and b/TIMCommon/timcommon/src/main/res-light/drawable-xxhdpi/core_default_user_icon_light.png differ diff --git a/TIMCommon/timcommon/src/main/res-light/drawable-xxhdpi/core_online_status_light.png b/TIMCommon/timcommon/src/main/res-light/drawable-xxhdpi/core_online_status_light.png new file mode 100644 index 0000000..cf07e72 Binary files /dev/null and b/TIMCommon/timcommon/src/main/res-light/drawable-xxhdpi/core_online_status_light.png differ diff --git a/TIMCommon/timcommon/src/main/res-light/drawable-xxhdpi/core_selected_icon_light.png b/TIMCommon/timcommon/src/main/res-light/drawable-xxhdpi/core_selected_icon_light.png new file mode 100644 index 0000000..ae46e89 Binary files /dev/null and b/TIMCommon/timcommon/src/main/res-light/drawable-xxhdpi/core_selected_icon_light.png differ diff --git a/TIMCommon/timcommon/src/main/res-light/drawable-xxhdpi/core_title_bar_back_light.png b/TIMCommon/timcommon/src/main/res-light/drawable-xxhdpi/core_title_bar_back_light.png new file mode 100644 index 0000000..83d658d Binary files /dev/null and b/TIMCommon/timcommon/src/main/res-light/drawable-xxhdpi/core_title_bar_back_light.png differ diff --git a/TIMCommon/timcommon/src/main/res-light/drawable/chat_bubble_other_bg_light.xml b/TIMCommon/timcommon/src/main/res-light/drawable/chat_bubble_other_bg_light.xml new file mode 100644 index 0000000..ad3338f --- /dev/null +++ b/TIMCommon/timcommon/src/main/res-light/drawable/chat_bubble_other_bg_light.xml @@ -0,0 +1,18 @@ + + + + + + + + + + \ No newline at end of file diff --git a/TIMCommon/timcommon/src/main/res-light/drawable/chat_bubble_self_bg_light.xml b/TIMCommon/timcommon/src/main/res-light/drawable/chat_bubble_self_bg_light.xml new file mode 100644 index 0000000..493753c --- /dev/null +++ b/TIMCommon/timcommon/src/main/res-light/drawable/chat_bubble_self_bg_light.xml @@ -0,0 +1,17 @@ + + + + + + + + + \ No newline at end of file diff --git a/TIMCommon/timcommon/src/main/res-light/drawable/chat_reply_icon_light.png b/TIMCommon/timcommon/src/main/res-light/drawable/chat_reply_icon_light.png new file mode 100644 index 0000000..19e4b81 Binary files /dev/null and b/TIMCommon/timcommon/src/main/res-light/drawable/chat_reply_icon_light.png differ diff --git a/TIMCommon/timcommon/src/main/res-light/drawable/core_title_bar_bg_light.xml b/TIMCommon/timcommon/src/main/res-light/drawable/core_title_bar_bg_light.xml new file mode 100644 index 0000000..7a5193b --- /dev/null +++ b/TIMCommon/timcommon/src/main/res-light/drawable/core_title_bar_bg_light.xml @@ -0,0 +1,9 @@ + + + + + + + \ No newline at end of file diff --git a/TIMCommon/timcommon/src/main/res-light/values/light_colors.xml b/TIMCommon/timcommon/src/main/res-light/values/light_colors.xml new file mode 100644 index 0000000..5df62e8 --- /dev/null +++ b/TIMCommon/timcommon/src/main/res-light/values/light_colors.xml @@ -0,0 +1,10 @@ + + + #FF000000 + #ECECEC + @color/core_bubble_bg_color_light + #888888 + + #679CE1 + #888888 + \ No newline at end of file diff --git a/TIMCommon/timcommon/src/main/res-light/values/light_styles.xml b/TIMCommon/timcommon/src/main/res-light/values/light_styles.xml new file mode 100644 index 0000000..16d8503 --- /dev/null +++ b/TIMCommon/timcommon/src/main/res-light/values/light_styles.xml @@ -0,0 +1,28 @@ + + + + + + \ No newline at end of file diff --git a/TIMCommon/timcommon/src/main/res-lively/drawable-xxhdpi/core_default_group_icon_community_lively.png b/TIMCommon/timcommon/src/main/res-lively/drawable-xxhdpi/core_default_group_icon_community_lively.png new file mode 100644 index 0000000..028d844 Binary files /dev/null and b/TIMCommon/timcommon/src/main/res-lively/drawable-xxhdpi/core_default_group_icon_community_lively.png differ diff --git a/TIMCommon/timcommon/src/main/res-lively/drawable-xxhdpi/core_default_group_icon_meeting_lively.png b/TIMCommon/timcommon/src/main/res-lively/drawable-xxhdpi/core_default_group_icon_meeting_lively.png new file mode 100644 index 0000000..5480bcb Binary files /dev/null and b/TIMCommon/timcommon/src/main/res-lively/drawable-xxhdpi/core_default_group_icon_meeting_lively.png differ diff --git a/TIMCommon/timcommon/src/main/res-lively/drawable-xxhdpi/core_default_group_icon_public_lively.png b/TIMCommon/timcommon/src/main/res-lively/drawable-xxhdpi/core_default_group_icon_public_lively.png new file mode 100644 index 0000000..18dd84c Binary files /dev/null and b/TIMCommon/timcommon/src/main/res-lively/drawable-xxhdpi/core_default_group_icon_public_lively.png differ diff --git a/TIMCommon/timcommon/src/main/res-lively/drawable-xxhdpi/core_default_group_icon_work_lively.png b/TIMCommon/timcommon/src/main/res-lively/drawable-xxhdpi/core_default_group_icon_work_lively.png new file mode 100644 index 0000000..32e2fbf Binary files /dev/null and b/TIMCommon/timcommon/src/main/res-lively/drawable-xxhdpi/core_default_group_icon_work_lively.png differ diff --git a/TIMCommon/timcommon/src/main/res-lively/drawable-xxhdpi/core_default_user_icon_lively.png b/TIMCommon/timcommon/src/main/res-lively/drawable-xxhdpi/core_default_user_icon_lively.png new file mode 100644 index 0000000..49de546 Binary files /dev/null and b/TIMCommon/timcommon/src/main/res-lively/drawable-xxhdpi/core_default_user_icon_lively.png differ diff --git a/TIMCommon/timcommon/src/main/res-lively/drawable-xxhdpi/core_online_status_lively.png b/TIMCommon/timcommon/src/main/res-lively/drawable-xxhdpi/core_online_status_lively.png new file mode 100644 index 0000000..69b05d8 Binary files /dev/null and b/TIMCommon/timcommon/src/main/res-lively/drawable-xxhdpi/core_online_status_lively.png differ diff --git a/TIMCommon/timcommon/src/main/res-lively/drawable-xxhdpi/core_selected_icon_lively.png b/TIMCommon/timcommon/src/main/res-lively/drawable-xxhdpi/core_selected_icon_lively.png new file mode 100644 index 0000000..c86e1ac Binary files /dev/null and b/TIMCommon/timcommon/src/main/res-lively/drawable-xxhdpi/core_selected_icon_lively.png differ diff --git a/TIMCommon/timcommon/src/main/res-lively/drawable-xxhdpi/core_title_bar_back_lively.png b/TIMCommon/timcommon/src/main/res-lively/drawable-xxhdpi/core_title_bar_back_lively.png new file mode 100644 index 0000000..2072634 Binary files /dev/null and b/TIMCommon/timcommon/src/main/res-lively/drawable-xxhdpi/core_title_bar_back_lively.png differ diff --git a/TIMCommon/timcommon/src/main/res-lively/drawable/chat_bubble_other_bg_lively.xml b/TIMCommon/timcommon/src/main/res-lively/drawable/chat_bubble_other_bg_lively.xml new file mode 100644 index 0000000..a3cd187 --- /dev/null +++ b/TIMCommon/timcommon/src/main/res-lively/drawable/chat_bubble_other_bg_lively.xml @@ -0,0 +1,22 @@ + + + + + + + + + + + + \ No newline at end of file diff --git a/TIMCommon/timcommon/src/main/res-lively/drawable/chat_bubble_self_bg_lively.xml b/TIMCommon/timcommon/src/main/res-lively/drawable/chat_bubble_self_bg_lively.xml new file mode 100644 index 0000000..b757580 --- /dev/null +++ b/TIMCommon/timcommon/src/main/res-lively/drawable/chat_bubble_self_bg_lively.xml @@ -0,0 +1,17 @@ + + + + + + + + + \ No newline at end of file diff --git a/TIMCommon/timcommon/src/main/res-lively/drawable/chat_reply_icon_lively.png b/TIMCommon/timcommon/src/main/res-lively/drawable/chat_reply_icon_lively.png new file mode 100644 index 0000000..5e9158c Binary files /dev/null and b/TIMCommon/timcommon/src/main/res-lively/drawable/chat_reply_icon_lively.png differ diff --git a/TIMCommon/timcommon/src/main/res-lively/drawable/core_title_bar_bg_lively.xml b/TIMCommon/timcommon/src/main/res-lively/drawable/core_title_bar_bg_lively.xml new file mode 100644 index 0000000..3d6ec23 --- /dev/null +++ b/TIMCommon/timcommon/src/main/res-lively/drawable/core_title_bar_bg_lively.xml @@ -0,0 +1,8 @@ + + + + + + \ No newline at end of file diff --git a/TIMCommon/timcommon/src/main/res-lively/values/lively_colors.xml b/TIMCommon/timcommon/src/main/res-lively/values/lively_colors.xml new file mode 100644 index 0000000..c22b28d --- /dev/null +++ b/TIMCommon/timcommon/src/main/res-lively/values/lively_colors.xml @@ -0,0 +1,10 @@ + + + #FFFFFFFF + #F8F8F9 + @color/core_bubble_bg_color_lively + #FFFFFF + + #ECA08E + #888888 + \ No newline at end of file diff --git a/TIMCommon/timcommon/src/main/res-lively/values/lively_styles.xml b/TIMCommon/timcommon/src/main/res-lively/values/lively_styles.xml new file mode 100644 index 0000000..caf8e02 --- /dev/null +++ b/TIMCommon/timcommon/src/main/res-lively/values/lively_styles.xml @@ -0,0 +1,30 @@ + + + + + + \ No newline at end of file diff --git a/TIMCommon/timcommon/src/main/res-serious/drawable-xxhdpi/core_default_group_icon_community_serious.png b/TIMCommon/timcommon/src/main/res-serious/drawable-xxhdpi/core_default_group_icon_community_serious.png new file mode 100644 index 0000000..1a2e268 Binary files /dev/null and b/TIMCommon/timcommon/src/main/res-serious/drawable-xxhdpi/core_default_group_icon_community_serious.png differ diff --git a/TIMCommon/timcommon/src/main/res-serious/drawable-xxhdpi/core_default_group_icon_meeting_serious.png b/TIMCommon/timcommon/src/main/res-serious/drawable-xxhdpi/core_default_group_icon_meeting_serious.png new file mode 100644 index 0000000..76b9e15 Binary files /dev/null and b/TIMCommon/timcommon/src/main/res-serious/drawable-xxhdpi/core_default_group_icon_meeting_serious.png differ diff --git a/TIMCommon/timcommon/src/main/res-serious/drawable-xxhdpi/core_default_group_icon_public_serious.png b/TIMCommon/timcommon/src/main/res-serious/drawable-xxhdpi/core_default_group_icon_public_serious.png new file mode 100644 index 0000000..a4f3e7b Binary files /dev/null and b/TIMCommon/timcommon/src/main/res-serious/drawable-xxhdpi/core_default_group_icon_public_serious.png differ diff --git a/TIMCommon/timcommon/src/main/res-serious/drawable-xxhdpi/core_default_group_icon_work_serious.png b/TIMCommon/timcommon/src/main/res-serious/drawable-xxhdpi/core_default_group_icon_work_serious.png new file mode 100644 index 0000000..7081399 Binary files /dev/null and b/TIMCommon/timcommon/src/main/res-serious/drawable-xxhdpi/core_default_group_icon_work_serious.png differ diff --git a/TIMCommon/timcommon/src/main/res-serious/drawable-xxhdpi/core_default_user_icon_serious.png b/TIMCommon/timcommon/src/main/res-serious/drawable-xxhdpi/core_default_user_icon_serious.png new file mode 100644 index 0000000..b384546 Binary files /dev/null and b/TIMCommon/timcommon/src/main/res-serious/drawable-xxhdpi/core_default_user_icon_serious.png differ diff --git a/TIMCommon/timcommon/src/main/res-serious/drawable-xxhdpi/core_online_status_serious.png b/TIMCommon/timcommon/src/main/res-serious/drawable-xxhdpi/core_online_status_serious.png new file mode 100644 index 0000000..ec31c4a Binary files /dev/null and b/TIMCommon/timcommon/src/main/res-serious/drawable-xxhdpi/core_online_status_serious.png differ diff --git a/TIMCommon/timcommon/src/main/res-serious/drawable-xxhdpi/core_selected_icon_serious.png b/TIMCommon/timcommon/src/main/res-serious/drawable-xxhdpi/core_selected_icon_serious.png new file mode 100644 index 0000000..3486026 Binary files /dev/null and b/TIMCommon/timcommon/src/main/res-serious/drawable-xxhdpi/core_selected_icon_serious.png differ diff --git a/TIMCommon/timcommon/src/main/res-serious/drawable-xxhdpi/core_title_bar_back_serious.png b/TIMCommon/timcommon/src/main/res-serious/drawable-xxhdpi/core_title_bar_back_serious.png new file mode 100644 index 0000000..2072634 Binary files /dev/null and b/TIMCommon/timcommon/src/main/res-serious/drawable-xxhdpi/core_title_bar_back_serious.png differ diff --git a/TIMCommon/timcommon/src/main/res-serious/drawable/chat_bubble_other_bg_serious.xml b/TIMCommon/timcommon/src/main/res-serious/drawable/chat_bubble_other_bg_serious.xml new file mode 100644 index 0000000..6676701 --- /dev/null +++ b/TIMCommon/timcommon/src/main/res-serious/drawable/chat_bubble_other_bg_serious.xml @@ -0,0 +1,22 @@ + + + + + + + + + + + + \ No newline at end of file diff --git a/TIMCommon/timcommon/src/main/res-serious/drawable/chat_bubble_self_bg_serious.xml b/TIMCommon/timcommon/src/main/res-serious/drawable/chat_bubble_self_bg_serious.xml new file mode 100644 index 0000000..5e21bac --- /dev/null +++ b/TIMCommon/timcommon/src/main/res-serious/drawable/chat_bubble_self_bg_serious.xml @@ -0,0 +1,17 @@ + + + + + + + + + \ No newline at end of file diff --git a/TIMCommon/timcommon/src/main/res-serious/drawable/chat_reply_icon_serious.png b/TIMCommon/timcommon/src/main/res-serious/drawable/chat_reply_icon_serious.png new file mode 100644 index 0000000..19e4b81 Binary files /dev/null and b/TIMCommon/timcommon/src/main/res-serious/drawable/chat_reply_icon_serious.png differ diff --git a/TIMCommon/timcommon/src/main/res-serious/drawable/core_title_bar_bg_serious.xml b/TIMCommon/timcommon/src/main/res-serious/drawable/core_title_bar_bg_serious.xml new file mode 100644 index 0000000..9471fbc --- /dev/null +++ b/TIMCommon/timcommon/src/main/res-serious/drawable/core_title_bar_bg_serious.xml @@ -0,0 +1,9 @@ + + + + + + \ No newline at end of file diff --git a/TIMCommon/timcommon/src/main/res-serious/values/serious_colors.xml b/TIMCommon/timcommon/src/main/res-serious/values/serious_colors.xml new file mode 100644 index 0000000..6c2f1dc --- /dev/null +++ b/TIMCommon/timcommon/src/main/res-serious/values/serious_colors.xml @@ -0,0 +1,9 @@ + + + #FFFFFFFF + #F8F8F9 + @color/core_bubble_bg_color_serious + #FFFFFF + #5695E7 + #888888 + \ No newline at end of file diff --git a/TIMCommon/timcommon/src/main/res-serious/values/serious_styles.xml b/TIMCommon/timcommon/src/main/res-serious/values/serious_styles.xml new file mode 100644 index 0000000..702f917 --- /dev/null +++ b/TIMCommon/timcommon/src/main/res-serious/values/serious_styles.xml @@ -0,0 +1,30 @@ + + + + + + \ No newline at end of file diff --git a/TIMCommon/timcommon/src/main/res/anim/beginner_guide_in_anim.xml b/TIMCommon/timcommon/src/main/res/anim/beginner_guide_in_anim.xml new file mode 100644 index 0000000..94ce416 --- /dev/null +++ b/TIMCommon/timcommon/src/main/res/anim/beginner_guide_in_anim.xml @@ -0,0 +1,7 @@ + + + + \ No newline at end of file diff --git a/TIMCommon/timcommon/src/main/res/anim/beginner_guide_out_anim.xml b/TIMCommon/timcommon/src/main/res/anim/beginner_guide_out_anim.xml new file mode 100644 index 0000000..aa23020 --- /dev/null +++ b/TIMCommon/timcommon/src/main/res/anim/beginner_guide_out_anim.xml @@ -0,0 +1,7 @@ + + + + \ No newline at end of file diff --git a/TIMCommon/timcommon/src/main/res/anim/core_popup_in_anim.xml b/TIMCommon/timcommon/src/main/res/anim/core_popup_in_anim.xml new file mode 100644 index 0000000..74069b9 --- /dev/null +++ b/TIMCommon/timcommon/src/main/res/anim/core_popup_in_anim.xml @@ -0,0 +1,8 @@ + + + + \ No newline at end of file diff --git a/TIMCommon/timcommon/src/main/res/anim/core_popup_out_anim.xml b/TIMCommon/timcommon/src/main/res/anim/core_popup_out_anim.xml new file mode 100644 index 0000000..7dd9512 --- /dev/null +++ b/TIMCommon/timcommon/src/main/res/anim/core_popup_out_anim.xml @@ -0,0 +1,8 @@ + + + + \ No newline at end of file diff --git a/TIMCommon/timcommon/src/main/res/drawable/alert_bg.9.png b/TIMCommon/timcommon/src/main/res/drawable/alert_bg.9.png new file mode 100644 index 0000000..84a042e Binary files /dev/null and b/TIMCommon/timcommon/src/main/res/drawable/alert_bg.9.png differ diff --git a/TIMCommon/timcommon/src/main/res/drawable/arrow_right.png b/TIMCommon/timcommon/src/main/res/drawable/arrow_right.png new file mode 100644 index 0000000..b4fd76e Binary files /dev/null and b/TIMCommon/timcommon/src/main/res/drawable/arrow_right.png differ diff --git a/TIMCommon/timcommon/src/main/res/drawable/chat_bubble_other_transparent_bg.xml b/TIMCommon/timcommon/src/main/res/drawable/chat_bubble_other_transparent_bg.xml new file mode 100644 index 0000000..a29c9ec --- /dev/null +++ b/TIMCommon/timcommon/src/main/res/drawable/chat_bubble_other_transparent_bg.xml @@ -0,0 +1,12 @@ + + + + + + + + \ No newline at end of file diff --git a/TIMCommon/timcommon/src/main/res/drawable/chat_bubble_self_transparent_bg.xml b/TIMCommon/timcommon/src/main/res/drawable/chat_bubble_self_transparent_bg.xml new file mode 100644 index 0000000..e89b11e --- /dev/null +++ b/TIMCommon/timcommon/src/main/res/drawable/chat_bubble_self_transparent_bg.xml @@ -0,0 +1,16 @@ + + + + + + + + \ No newline at end of file diff --git a/TIMCommon/timcommon/src/main/res/drawable/chat_checkbox_selector.xml b/TIMCommon/timcommon/src/main/res/drawable/chat_checkbox_selector.xml new file mode 100644 index 0000000..8333620 --- /dev/null +++ b/TIMCommon/timcommon/src/main/res/drawable/chat_checkbox_selector.xml @@ -0,0 +1,8 @@ + + + + + + + + \ No newline at end of file diff --git a/TIMCommon/timcommon/src/main/res/drawable/chat_gray_round_rect_bg.xml b/TIMCommon/timcommon/src/main/res/drawable/chat_gray_round_rect_bg.xml new file mode 100644 index 0000000..900bad8 --- /dev/null +++ b/TIMCommon/timcommon/src/main/res/drawable/chat_gray_round_rect_bg.xml @@ -0,0 +1,8 @@ + + + + + + + \ No newline at end of file diff --git a/TIMCommon/timcommon/src/main/res/drawable/chat_message_popup_fill_border.xml b/TIMCommon/timcommon/src/main/res/drawable/chat_message_popup_fill_border.xml new file mode 100644 index 0000000..b4c5cdf --- /dev/null +++ b/TIMCommon/timcommon/src/main/res/drawable/chat_message_popup_fill_border.xml @@ -0,0 +1,8 @@ + + + + + + + \ No newline at end of file diff --git a/TIMCommon/timcommon/src/main/res/drawable/chat_message_popup_fill_border_right.xml b/TIMCommon/timcommon/src/main/res/drawable/chat_message_popup_fill_border_right.xml new file mode 100644 index 0000000..306aac3 --- /dev/null +++ b/TIMCommon/timcommon/src/main/res/drawable/chat_message_popup_fill_border_right.xml @@ -0,0 +1,8 @@ + + + + + + + \ No newline at end of file diff --git a/TIMCommon/timcommon/src/main/res/drawable/chat_message_popup_stroke_border.xml b/TIMCommon/timcommon/src/main/res/drawable/chat_message_popup_stroke_border.xml new file mode 100644 index 0000000..9b0dba2 --- /dev/null +++ b/TIMCommon/timcommon/src/main/res/drawable/chat_message_popup_stroke_border.xml @@ -0,0 +1,9 @@ + + + + + + + + \ No newline at end of file diff --git a/TIMCommon/timcommon/src/main/res/drawable/chat_message_popup_stroke_border_left.xml b/TIMCommon/timcommon/src/main/res/drawable/chat_message_popup_stroke_border_left.xml new file mode 100644 index 0000000..b1547a6 --- /dev/null +++ b/TIMCommon/timcommon/src/main/res/drawable/chat_message_popup_stroke_border_left.xml @@ -0,0 +1,9 @@ + + + + + + + + \ No newline at end of file diff --git a/TIMCommon/timcommon/src/main/res/drawable/chat_message_popup_stroke_border_right.xml b/TIMCommon/timcommon/src/main/res/drawable/chat_message_popup_stroke_border_right.xml new file mode 100644 index 0000000..a95f4c0 --- /dev/null +++ b/TIMCommon/timcommon/src/main/res/drawable/chat_message_popup_stroke_border_right.xml @@ -0,0 +1,9 @@ + + + + + + + + \ No newline at end of file diff --git a/TIMCommon/timcommon/src/main/res/drawable/chat_minimalist_anim_loading00.png b/TIMCommon/timcommon/src/main/res/drawable/chat_minimalist_anim_loading00.png new file mode 100644 index 0000000..062ce9d Binary files /dev/null and b/TIMCommon/timcommon/src/main/res/drawable/chat_minimalist_anim_loading00.png differ diff --git a/TIMCommon/timcommon/src/main/res/drawable/chat_minimalist_anim_loading01.png b/TIMCommon/timcommon/src/main/res/drawable/chat_minimalist_anim_loading01.png new file mode 100644 index 0000000..41c1724 Binary files /dev/null and b/TIMCommon/timcommon/src/main/res/drawable/chat_minimalist_anim_loading01.png differ diff --git a/TIMCommon/timcommon/src/main/res/drawable/chat_minimalist_anim_loading02.png b/TIMCommon/timcommon/src/main/res/drawable/chat_minimalist_anim_loading02.png new file mode 100644 index 0000000..23aac51 Binary files /dev/null and b/TIMCommon/timcommon/src/main/res/drawable/chat_minimalist_anim_loading02.png differ diff --git a/TIMCommon/timcommon/src/main/res/drawable/chat_minimalist_anim_loading03.png b/TIMCommon/timcommon/src/main/res/drawable/chat_minimalist_anim_loading03.png new file mode 100644 index 0000000..32cc0f6 Binary files /dev/null and b/TIMCommon/timcommon/src/main/res/drawable/chat_minimalist_anim_loading03.png differ diff --git a/TIMCommon/timcommon/src/main/res/drawable/chat_minimalist_anim_loading04.png b/TIMCommon/timcommon/src/main/res/drawable/chat_minimalist_anim_loading04.png new file mode 100644 index 0000000..ef8899f Binary files /dev/null and b/TIMCommon/timcommon/src/main/res/drawable/chat_minimalist_anim_loading04.png differ diff --git a/TIMCommon/timcommon/src/main/res/drawable/chat_minimalist_anim_loading05.png b/TIMCommon/timcommon/src/main/res/drawable/chat_minimalist_anim_loading05.png new file mode 100644 index 0000000..4141cdd Binary files /dev/null and b/TIMCommon/timcommon/src/main/res/drawable/chat_minimalist_anim_loading05.png differ diff --git a/TIMCommon/timcommon/src/main/res/drawable/chat_minimalist_anim_loading06.png b/TIMCommon/timcommon/src/main/res/drawable/chat_minimalist_anim_loading06.png new file mode 100644 index 0000000..9002deb Binary files /dev/null and b/TIMCommon/timcommon/src/main/res/drawable/chat_minimalist_anim_loading06.png differ diff --git a/TIMCommon/timcommon/src/main/res/drawable/chat_minimalist_anim_loading07.png b/TIMCommon/timcommon/src/main/res/drawable/chat_minimalist_anim_loading07.png new file mode 100644 index 0000000..8443261 Binary files /dev/null and b/TIMCommon/timcommon/src/main/res/drawable/chat_minimalist_anim_loading07.png differ diff --git a/TIMCommon/timcommon/src/main/res/drawable/chat_minimalist_anim_loading08.png b/TIMCommon/timcommon/src/main/res/drawable/chat_minimalist_anim_loading08.png new file mode 100644 index 0000000..7879c4b Binary files /dev/null and b/TIMCommon/timcommon/src/main/res/drawable/chat_minimalist_anim_loading08.png differ diff --git a/TIMCommon/timcommon/src/main/res/drawable/chat_minimalist_anim_loading09.png b/TIMCommon/timcommon/src/main/res/drawable/chat_minimalist_anim_loading09.png new file mode 100644 index 0000000..da9929a Binary files /dev/null and b/TIMCommon/timcommon/src/main/res/drawable/chat_minimalist_anim_loading09.png differ diff --git a/TIMCommon/timcommon/src/main/res/drawable/chat_minimalist_anim_loading10.png b/TIMCommon/timcommon/src/main/res/drawable/chat_minimalist_anim_loading10.png new file mode 100644 index 0000000..3e7c121 Binary files /dev/null and b/TIMCommon/timcommon/src/main/res/drawable/chat_minimalist_anim_loading10.png differ diff --git a/TIMCommon/timcommon/src/main/res/drawable/chat_minimalist_anim_loading11.png b/TIMCommon/timcommon/src/main/res/drawable/chat_minimalist_anim_loading11.png new file mode 100644 index 0000000..7b0c309 Binary files /dev/null and b/TIMCommon/timcommon/src/main/res/drawable/chat_minimalist_anim_loading11.png differ diff --git a/TIMCommon/timcommon/src/main/res/drawable/chat_minimalist_anim_loading12.png b/TIMCommon/timcommon/src/main/res/drawable/chat_minimalist_anim_loading12.png new file mode 100644 index 0000000..884cdff Binary files /dev/null and b/TIMCommon/timcommon/src/main/res/drawable/chat_minimalist_anim_loading12.png differ diff --git a/TIMCommon/timcommon/src/main/res/drawable/chat_minimalist_anim_loading13.png b/TIMCommon/timcommon/src/main/res/drawable/chat_minimalist_anim_loading13.png new file mode 100644 index 0000000..917feba Binary files /dev/null and b/TIMCommon/timcommon/src/main/res/drawable/chat_minimalist_anim_loading13.png differ diff --git a/TIMCommon/timcommon/src/main/res/drawable/chat_minimalist_anim_loading14.png b/TIMCommon/timcommon/src/main/res/drawable/chat_minimalist_anim_loading14.png new file mode 100644 index 0000000..b5f1ad3 Binary files /dev/null and b/TIMCommon/timcommon/src/main/res/drawable/chat_minimalist_anim_loading14.png differ diff --git a/TIMCommon/timcommon/src/main/res/drawable/chat_minimalist_anim_loading15.png b/TIMCommon/timcommon/src/main/res/drawable/chat_minimalist_anim_loading15.png new file mode 100644 index 0000000..8cd3a40 Binary files /dev/null and b/TIMCommon/timcommon/src/main/res/drawable/chat_minimalist_anim_loading15.png differ diff --git a/TIMCommon/timcommon/src/main/res/drawable/chat_minimalist_anim_loading16.png b/TIMCommon/timcommon/src/main/res/drawable/chat_minimalist_anim_loading16.png new file mode 100644 index 0000000..f2cc6ee Binary files /dev/null and b/TIMCommon/timcommon/src/main/res/drawable/chat_minimalist_anim_loading16.png differ diff --git a/TIMCommon/timcommon/src/main/res/drawable/chat_minimalist_anim_loading17.png b/TIMCommon/timcommon/src/main/res/drawable/chat_minimalist_anim_loading17.png new file mode 100644 index 0000000..e6305cb Binary files /dev/null and b/TIMCommon/timcommon/src/main/res/drawable/chat_minimalist_anim_loading17.png differ diff --git a/TIMCommon/timcommon/src/main/res/drawable/chat_minimalist_anim_loading18.png b/TIMCommon/timcommon/src/main/res/drawable/chat_minimalist_anim_loading18.png new file mode 100644 index 0000000..06f9008 Binary files /dev/null and b/TIMCommon/timcommon/src/main/res/drawable/chat_minimalist_anim_loading18.png differ diff --git a/TIMCommon/timcommon/src/main/res/drawable/chat_minimalist_anim_loading19.png b/TIMCommon/timcommon/src/main/res/drawable/chat_minimalist_anim_loading19.png new file mode 100644 index 0000000..672181d Binary files /dev/null and b/TIMCommon/timcommon/src/main/res/drawable/chat_minimalist_anim_loading19.png differ diff --git a/TIMCommon/timcommon/src/main/res/drawable/chat_minimalist_anim_loading20.png b/TIMCommon/timcommon/src/main/res/drawable/chat_minimalist_anim_loading20.png new file mode 100644 index 0000000..e4ba599 Binary files /dev/null and b/TIMCommon/timcommon/src/main/res/drawable/chat_minimalist_anim_loading20.png differ diff --git a/TIMCommon/timcommon/src/main/res/drawable/chat_minimalist_anim_loading21.png b/TIMCommon/timcommon/src/main/res/drawable/chat_minimalist_anim_loading21.png new file mode 100644 index 0000000..afc12d5 Binary files /dev/null and b/TIMCommon/timcommon/src/main/res/drawable/chat_minimalist_anim_loading21.png differ diff --git a/TIMCommon/timcommon/src/main/res/drawable/chat_minimalist_anim_loading22.png b/TIMCommon/timcommon/src/main/res/drawable/chat_minimalist_anim_loading22.png new file mode 100644 index 0000000..ca5d98a Binary files /dev/null and b/TIMCommon/timcommon/src/main/res/drawable/chat_minimalist_anim_loading22.png differ diff --git a/TIMCommon/timcommon/src/main/res/drawable/chat_minimalist_anim_loading23.png b/TIMCommon/timcommon/src/main/res/drawable/chat_minimalist_anim_loading23.png new file mode 100644 index 0000000..ebc5872 Binary files /dev/null and b/TIMCommon/timcommon/src/main/res/drawable/chat_minimalist_anim_loading23.png differ diff --git a/TIMCommon/timcommon/src/main/res/drawable/chat_minimalist_anim_loading24.png b/TIMCommon/timcommon/src/main/res/drawable/chat_minimalist_anim_loading24.png new file mode 100644 index 0000000..b08f26b Binary files /dev/null and b/TIMCommon/timcommon/src/main/res/drawable/chat_minimalist_anim_loading24.png differ diff --git a/TIMCommon/timcommon/src/main/res/drawable/chat_minimalist_anim_loading25.png b/TIMCommon/timcommon/src/main/res/drawable/chat_minimalist_anim_loading25.png new file mode 100644 index 0000000..5830e1c Binary files /dev/null and b/TIMCommon/timcommon/src/main/res/drawable/chat_minimalist_anim_loading25.png differ diff --git a/TIMCommon/timcommon/src/main/res/drawable/chat_minimalist_anim_loading26.png b/TIMCommon/timcommon/src/main/res/drawable/chat_minimalist_anim_loading26.png new file mode 100644 index 0000000..819db1a Binary files /dev/null and b/TIMCommon/timcommon/src/main/res/drawable/chat_minimalist_anim_loading26.png differ diff --git a/TIMCommon/timcommon/src/main/res/drawable/chat_minimalist_anim_loading27.png b/TIMCommon/timcommon/src/main/res/drawable/chat_minimalist_anim_loading27.png new file mode 100644 index 0000000..f9653c1 Binary files /dev/null and b/TIMCommon/timcommon/src/main/res/drawable/chat_minimalist_anim_loading27.png differ diff --git a/TIMCommon/timcommon/src/main/res/drawable/chat_minimalist_anim_loading28.png b/TIMCommon/timcommon/src/main/res/drawable/chat_minimalist_anim_loading28.png new file mode 100644 index 0000000..842fef4 Binary files /dev/null and b/TIMCommon/timcommon/src/main/res/drawable/chat_minimalist_anim_loading28.png differ diff --git a/TIMCommon/timcommon/src/main/res/drawable/chat_minimalist_anim_loading29.png b/TIMCommon/timcommon/src/main/res/drawable/chat_minimalist_anim_loading29.png new file mode 100644 index 0000000..6025ba7 Binary files /dev/null and b/TIMCommon/timcommon/src/main/res/drawable/chat_minimalist_anim_loading29.png differ diff --git a/TIMCommon/timcommon/src/main/res/drawable/chat_minimalist_anim_loading30.png b/TIMCommon/timcommon/src/main/res/drawable/chat_minimalist_anim_loading30.png new file mode 100644 index 0000000..a6ebf21 Binary files /dev/null and b/TIMCommon/timcommon/src/main/res/drawable/chat_minimalist_anim_loading30.png differ diff --git a/TIMCommon/timcommon/src/main/res/drawable/chat_minimalist_anim_loading31.png b/TIMCommon/timcommon/src/main/res/drawable/chat_minimalist_anim_loading31.png new file mode 100644 index 0000000..281a9e3 Binary files /dev/null and b/TIMCommon/timcommon/src/main/res/drawable/chat_minimalist_anim_loading31.png differ diff --git a/TIMCommon/timcommon/src/main/res/drawable/chat_minimalist_anim_loading32.png b/TIMCommon/timcommon/src/main/res/drawable/chat_minimalist_anim_loading32.png new file mode 100644 index 0000000..0dc299c Binary files /dev/null and b/TIMCommon/timcommon/src/main/res/drawable/chat_minimalist_anim_loading32.png differ diff --git a/TIMCommon/timcommon/src/main/res/drawable/chat_minimalist_anim_loading33.png b/TIMCommon/timcommon/src/main/res/drawable/chat_minimalist_anim_loading33.png new file mode 100644 index 0000000..7ebe0b1 Binary files /dev/null and b/TIMCommon/timcommon/src/main/res/drawable/chat_minimalist_anim_loading33.png differ diff --git a/TIMCommon/timcommon/src/main/res/drawable/chat_minimalist_anim_loading34.png b/TIMCommon/timcommon/src/main/res/drawable/chat_minimalist_anim_loading34.png new file mode 100644 index 0000000..58f97e5 Binary files /dev/null and b/TIMCommon/timcommon/src/main/res/drawable/chat_minimalist_anim_loading34.png differ diff --git a/TIMCommon/timcommon/src/main/res/drawable/chat_minimalist_anim_loading35.png b/TIMCommon/timcommon/src/main/res/drawable/chat_minimalist_anim_loading35.png new file mode 100644 index 0000000..040edf7 Binary files /dev/null and b/TIMCommon/timcommon/src/main/res/drawable/chat_minimalist_anim_loading35.png differ diff --git a/TIMCommon/timcommon/src/main/res/drawable/chat_minimalist_anim_loading36.png b/TIMCommon/timcommon/src/main/res/drawable/chat_minimalist_anim_loading36.png new file mode 100644 index 0000000..2135b47 Binary files /dev/null and b/TIMCommon/timcommon/src/main/res/drawable/chat_minimalist_anim_loading36.png differ diff --git a/TIMCommon/timcommon/src/main/res/drawable/chat_minimalist_anim_loading37.png b/TIMCommon/timcommon/src/main/res/drawable/chat_minimalist_anim_loading37.png new file mode 100644 index 0000000..f5582bf Binary files /dev/null and b/TIMCommon/timcommon/src/main/res/drawable/chat_minimalist_anim_loading37.png differ diff --git a/TIMCommon/timcommon/src/main/res/drawable/chat_minimalist_anim_loading38.png b/TIMCommon/timcommon/src/main/res/drawable/chat_minimalist_anim_loading38.png new file mode 100644 index 0000000..e6a366c Binary files /dev/null and b/TIMCommon/timcommon/src/main/res/drawable/chat_minimalist_anim_loading38.png differ diff --git a/TIMCommon/timcommon/src/main/res/drawable/chat_minimalist_anim_loading39.png b/TIMCommon/timcommon/src/main/res/drawable/chat_minimalist_anim_loading39.png new file mode 100644 index 0000000..bf0cc02 Binary files /dev/null and b/TIMCommon/timcommon/src/main/res/drawable/chat_minimalist_anim_loading39.png differ diff --git a/TIMCommon/timcommon/src/main/res/drawable/chat_minimalist_anim_loading40.png b/TIMCommon/timcommon/src/main/res/drawable/chat_minimalist_anim_loading40.png new file mode 100644 index 0000000..43a4f7e Binary files /dev/null and b/TIMCommon/timcommon/src/main/res/drawable/chat_minimalist_anim_loading40.png differ diff --git a/TIMCommon/timcommon/src/main/res/drawable/chat_minimalist_anim_loading41.png b/TIMCommon/timcommon/src/main/res/drawable/chat_minimalist_anim_loading41.png new file mode 100644 index 0000000..a0960b5 Binary files /dev/null and b/TIMCommon/timcommon/src/main/res/drawable/chat_minimalist_anim_loading41.png differ diff --git a/TIMCommon/timcommon/src/main/res/drawable/chat_minimalist_anim_loading42.png b/TIMCommon/timcommon/src/main/res/drawable/chat_minimalist_anim_loading42.png new file mode 100644 index 0000000..d52b140 Binary files /dev/null and b/TIMCommon/timcommon/src/main/res/drawable/chat_minimalist_anim_loading42.png differ diff --git a/TIMCommon/timcommon/src/main/res/drawable/chat_minimalist_anim_loading43.png b/TIMCommon/timcommon/src/main/res/drawable/chat_minimalist_anim_loading43.png new file mode 100644 index 0000000..c2aae37 Binary files /dev/null and b/TIMCommon/timcommon/src/main/res/drawable/chat_minimalist_anim_loading43.png differ diff --git a/TIMCommon/timcommon/src/main/res/drawable/chat_minimalist_anim_loading44.png b/TIMCommon/timcommon/src/main/res/drawable/chat_minimalist_anim_loading44.png new file mode 100644 index 0000000..a7d64da Binary files /dev/null and b/TIMCommon/timcommon/src/main/res/drawable/chat_minimalist_anim_loading44.png differ diff --git a/TIMCommon/timcommon/src/main/res/drawable/chat_minimalist_file_download_icon.png b/TIMCommon/timcommon/src/main/res/drawable/chat_minimalist_file_download_icon.png new file mode 100644 index 0000000..022c26b Binary files /dev/null and b/TIMCommon/timcommon/src/main/res/drawable/chat_minimalist_file_download_icon.png differ diff --git a/TIMCommon/timcommon/src/main/res/drawable/chat_minimalist_message_status_send_all_read.png b/TIMCommon/timcommon/src/main/res/drawable/chat_minimalist_message_status_send_all_read.png new file mode 100644 index 0000000..02f2508 Binary files /dev/null and b/TIMCommon/timcommon/src/main/res/drawable/chat_minimalist_message_status_send_all_read.png differ diff --git a/TIMCommon/timcommon/src/main/res/drawable/chat_minimalist_message_status_send_failed.png b/TIMCommon/timcommon/src/main/res/drawable/chat_minimalist_message_status_send_failed.png new file mode 100644 index 0000000..2b1f0c8 Binary files /dev/null and b/TIMCommon/timcommon/src/main/res/drawable/chat_minimalist_message_status_send_failed.png differ diff --git a/TIMCommon/timcommon/src/main/res/drawable/chat_minimalist_message_status_send_no_read.png b/TIMCommon/timcommon/src/main/res/drawable/chat_minimalist_message_status_send_no_read.png new file mode 100644 index 0000000..ec9f7eb Binary files /dev/null and b/TIMCommon/timcommon/src/main/res/drawable/chat_minimalist_message_status_send_no_read.png differ diff --git a/TIMCommon/timcommon/src/main/res/drawable/chat_minimalist_message_status_send_part_read.png b/TIMCommon/timcommon/src/main/res/drawable/chat_minimalist_message_status_send_part_read.png new file mode 100644 index 0000000..1b90013 Binary files /dev/null and b/TIMCommon/timcommon/src/main/res/drawable/chat_minimalist_message_status_send_part_read.png differ diff --git a/TIMCommon/timcommon/src/main/res/drawable/chat_minimalist_status_loading_anim.xml b/TIMCommon/timcommon/src/main/res/drawable/chat_minimalist_status_loading_anim.xml new file mode 100644 index 0000000..964ff92 --- /dev/null +++ b/TIMCommon/timcommon/src/main/res/drawable/chat_minimalist_status_loading_anim.xml @@ -0,0 +1,94 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/TIMCommon/timcommon/src/main/res/drawable/chat_quote_guide.png b/TIMCommon/timcommon/src/main/res/drawable/chat_quote_guide.png new file mode 100644 index 0000000..b153933 Binary files /dev/null and b/TIMCommon/timcommon/src/main/res/drawable/chat_quote_guide.png differ diff --git a/TIMCommon/timcommon/src/main/res/drawable/chat_react_bg.xml b/TIMCommon/timcommon/src/main/res/drawable/chat_react_bg.xml new file mode 100644 index 0000000..52af45a --- /dev/null +++ b/TIMCommon/timcommon/src/main/res/drawable/chat_react_bg.xml @@ -0,0 +1,8 @@ + + + + + + + \ No newline at end of file diff --git a/TIMCommon/timcommon/src/main/res/drawable/chat_reply_guide.png b/TIMCommon/timcommon/src/main/res/drawable/chat_reply_guide.png new file mode 100644 index 0000000..be44c5a Binary files /dev/null and b/TIMCommon/timcommon/src/main/res/drawable/chat_reply_guide.png differ diff --git a/TIMCommon/timcommon/src/main/res/drawable/chat_reply_more_icon.png b/TIMCommon/timcommon/src/main/res/drawable/chat_reply_more_icon.png new file mode 100644 index 0000000..e2e29fb Binary files /dev/null and b/TIMCommon/timcommon/src/main/res/drawable/chat_reply_more_icon.png differ diff --git a/TIMCommon/timcommon/src/main/res/drawable/chat_unselected_icon.png b/TIMCommon/timcommon/src/main/res/drawable/chat_unselected_icon.png new file mode 100644 index 0000000..2258889 Binary files /dev/null and b/TIMCommon/timcommon/src/main/res/drawable/chat_unselected_icon.png differ diff --git a/TIMCommon/timcommon/src/main/res/drawable/check_box_selected.png b/TIMCommon/timcommon/src/main/res/drawable/check_box_selected.png new file mode 100644 index 0000000..273e525 Binary files /dev/null and b/TIMCommon/timcommon/src/main/res/drawable/check_box_selected.png differ diff --git a/TIMCommon/timcommon/src/main/res/drawable/core_close_icon.png b/TIMCommon/timcommon/src/main/res/drawable/core_close_icon.png new file mode 100644 index 0000000..56a93e7 Binary files /dev/null and b/TIMCommon/timcommon/src/main/res/drawable/core_close_icon.png differ diff --git a/TIMCommon/timcommon/src/main/res/drawable/core_default_group_icon_community.png b/TIMCommon/timcommon/src/main/res/drawable/core_default_group_icon_community.png new file mode 100644 index 0000000..32344cb Binary files /dev/null and b/TIMCommon/timcommon/src/main/res/drawable/core_default_group_icon_community.png differ diff --git a/TIMCommon/timcommon/src/main/res/drawable/core_delete_icon.png b/TIMCommon/timcommon/src/main/res/drawable/core_delete_icon.png new file mode 100644 index 0000000..29b021b Binary files /dev/null and b/TIMCommon/timcommon/src/main/res/drawable/core_delete_icon.png differ diff --git a/TIMCommon/timcommon/src/main/res/drawable/core_edit_cursor.xml b/TIMCommon/timcommon/src/main/res/drawable/core_edit_cursor.xml new file mode 100644 index 0000000..03016a8 --- /dev/null +++ b/TIMCommon/timcommon/src/main/res/drawable/core_edit_cursor.xml @@ -0,0 +1,6 @@ + + + + + + \ No newline at end of file diff --git a/TIMCommon/timcommon/src/main/res/drawable/core_edit_text_bg.xml b/TIMCommon/timcommon/src/main/res/drawable/core_edit_text_bg.xml new file mode 100644 index 0000000..68c4f6a --- /dev/null +++ b/TIMCommon/timcommon/src/main/res/drawable/core_edit_text_bg.xml @@ -0,0 +1,5 @@ + + + + + \ No newline at end of file diff --git a/TIMCommon/timcommon/src/main/res/drawable/core_icon_offline_status.png b/TIMCommon/timcommon/src/main/res/drawable/core_icon_offline_status.png new file mode 100644 index 0000000..93f4010 Binary files /dev/null and b/TIMCommon/timcommon/src/main/res/drawable/core_icon_offline_status.png differ diff --git a/TIMCommon/timcommon/src/main/res/drawable/core_list_divider.xml b/TIMCommon/timcommon/src/main/res/drawable/core_list_divider.xml new file mode 100644 index 0000000..59cf049 --- /dev/null +++ b/TIMCommon/timcommon/src/main/res/drawable/core_list_divider.xml @@ -0,0 +1,11 @@ + + + + + + + + \ No newline at end of file diff --git a/TIMCommon/timcommon/src/main/res/drawable/core_minimalist_back_icon.png b/TIMCommon/timcommon/src/main/res/drawable/core_minimalist_back_icon.png new file mode 100644 index 0000000..fbc961b Binary files /dev/null and b/TIMCommon/timcommon/src/main/res/drawable/core_minimalist_back_icon.png differ diff --git a/TIMCommon/timcommon/src/main/res/drawable/core_permission_dialog_bg.xml b/TIMCommon/timcommon/src/main/res/drawable/core_permission_dialog_bg.xml new file mode 100644 index 0000000..6328472 --- /dev/null +++ b/TIMCommon/timcommon/src/main/res/drawable/core_permission_dialog_bg.xml @@ -0,0 +1,7 @@ + + + + + + \ No newline at end of file diff --git a/TIMCommon/timcommon/src/main/res/drawable/core_positive_btn_bg.xml b/TIMCommon/timcommon/src/main/res/drawable/core_positive_btn_bg.xml new file mode 100644 index 0000000..9063d53 --- /dev/null +++ b/TIMCommon/timcommon/src/main/res/drawable/core_positive_btn_bg.xml @@ -0,0 +1,7 @@ + + + + + + + \ No newline at end of file diff --git a/TIMCommon/timcommon/src/main/res/drawable/core_positive_btn_disable_bg.xml b/TIMCommon/timcommon/src/main/res/drawable/core_positive_btn_disable_bg.xml new file mode 100644 index 0000000..a8b242b --- /dev/null +++ b/TIMCommon/timcommon/src/main/res/drawable/core_positive_btn_disable_bg.xml @@ -0,0 +1,11 @@ + + + + + + + + \ No newline at end of file diff --git a/TIMCommon/timcommon/src/main/res/drawable/core_positive_btn_normal_bg.xml b/TIMCommon/timcommon/src/main/res/drawable/core_positive_btn_normal_bg.xml new file mode 100644 index 0000000..745901d --- /dev/null +++ b/TIMCommon/timcommon/src/main/res/drawable/core_positive_btn_normal_bg.xml @@ -0,0 +1,11 @@ + + + + + + + + \ No newline at end of file diff --git a/TIMCommon/timcommon/src/main/res/drawable/core_positive_btn_pressed_bg.xml b/TIMCommon/timcommon/src/main/res/drawable/core_positive_btn_pressed_bg.xml new file mode 100644 index 0000000..f53a321 --- /dev/null +++ b/TIMCommon/timcommon/src/main/res/drawable/core_positive_btn_pressed_bg.xml @@ -0,0 +1,11 @@ + + + + + + + + \ No newline at end of file diff --git a/TIMCommon/timcommon/src/main/res/drawable/core_search_icon.png b/TIMCommon/timcommon/src/main/res/drawable/core_search_icon.png new file mode 100644 index 0000000..e95fc8b Binary files /dev/null and b/TIMCommon/timcommon/src/main/res/drawable/core_search_icon.png differ diff --git a/TIMCommon/timcommon/src/main/res/drawable/message_send_fail.png b/TIMCommon/timcommon/src/main/res/drawable/message_send_fail.png new file mode 100644 index 0000000..cb0b524 Binary files /dev/null and b/TIMCommon/timcommon/src/main/res/drawable/message_send_fail.png differ diff --git a/TIMCommon/timcommon/src/main/res/drawable/minimalist_switch_thumb.xml b/TIMCommon/timcommon/src/main/res/drawable/minimalist_switch_thumb.xml new file mode 100644 index 0000000..d21a142 --- /dev/null +++ b/TIMCommon/timcommon/src/main/res/drawable/minimalist_switch_thumb.xml @@ -0,0 +1,25 @@ + + + + + + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/TIMCommon/timcommon/src/main/res/drawable/minimalist_switch_track.xml b/TIMCommon/timcommon/src/main/res/drawable/minimalist_switch_track.xml new file mode 100644 index 0000000..3f0c1d4 --- /dev/null +++ b/TIMCommon/timcommon/src/main/res/drawable/minimalist_switch_track.xml @@ -0,0 +1,18 @@ + + + + + + + + + + + + + + + + + + diff --git a/TIMCommon/timcommon/src/main/res/drawable/minimalist_translation_area_bg.xml b/TIMCommon/timcommon/src/main/res/drawable/minimalist_translation_area_bg.xml new file mode 100644 index 0000000..64d5036 --- /dev/null +++ b/TIMCommon/timcommon/src/main/res/drawable/minimalist_translation_area_bg.xml @@ -0,0 +1,9 @@ + + + + + + + + \ No newline at end of file diff --git a/TIMCommon/timcommon/src/main/res/drawable/popup_card_bg.xml b/TIMCommon/timcommon/src/main/res/drawable/popup_card_bg.xml new file mode 100644 index 0000000..325f6cd --- /dev/null +++ b/TIMCommon/timcommon/src/main/res/drawable/popup_card_bg.xml @@ -0,0 +1,13 @@ + + + + + + + + \ No newline at end of file diff --git a/TIMCommon/timcommon/src/main/res/drawable/quote_message_area_bg.xml b/TIMCommon/timcommon/src/main/res/drawable/quote_message_area_bg.xml new file mode 100644 index 0000000..87bd817 --- /dev/null +++ b/TIMCommon/timcommon/src/main/res/drawable/quote_message_area_bg.xml @@ -0,0 +1,9 @@ + + + + + + + + \ No newline at end of file diff --git a/TIMCommon/timcommon/src/main/res/drawable/selected_border.xml b/TIMCommon/timcommon/src/main/res/drawable/selected_border.xml new file mode 100644 index 0000000..dd04892 --- /dev/null +++ b/TIMCommon/timcommon/src/main/res/drawable/selected_border.xml @@ -0,0 +1,9 @@ + + + + + + + + \ No newline at end of file diff --git a/TIMCommon/timcommon/src/main/res/drawable/switch_thumb.xml b/TIMCommon/timcommon/src/main/res/drawable/switch_thumb.xml new file mode 100644 index 0000000..a76dc22 --- /dev/null +++ b/TIMCommon/timcommon/src/main/res/drawable/switch_thumb.xml @@ -0,0 +1,5 @@ + + + + + \ No newline at end of file diff --git a/TIMCommon/timcommon/src/main/res/drawable/switch_thumb_blue.xml b/TIMCommon/timcommon/src/main/res/drawable/switch_thumb_blue.xml new file mode 100644 index 0000000..2fba5a6 --- /dev/null +++ b/TIMCommon/timcommon/src/main/res/drawable/switch_thumb_blue.xml @@ -0,0 +1,14 @@ + + + + + + + + + + + + + + \ No newline at end of file diff --git a/TIMCommon/timcommon/src/main/res/drawable/switch_thumb_gray.xml b/TIMCommon/timcommon/src/main/res/drawable/switch_thumb_gray.xml new file mode 100644 index 0000000..280eaff --- /dev/null +++ b/TIMCommon/timcommon/src/main/res/drawable/switch_thumb_gray.xml @@ -0,0 +1,16 @@ + + + + + + + + + + + + + + \ No newline at end of file diff --git a/TIMCommon/timcommon/src/main/res/drawable/switch_track.xml b/TIMCommon/timcommon/src/main/res/drawable/switch_track.xml new file mode 100644 index 0000000..986f41b --- /dev/null +++ b/TIMCommon/timcommon/src/main/res/drawable/switch_track.xml @@ -0,0 +1,5 @@ + + + + + diff --git a/TIMCommon/timcommon/src/main/res/drawable/switch_track_blue.xml b/TIMCommon/timcommon/src/main/res/drawable/switch_track_blue.xml new file mode 100644 index 0000000..51e0eb2 --- /dev/null +++ b/TIMCommon/timcommon/src/main/res/drawable/switch_track_blue.xml @@ -0,0 +1,13 @@ + + + + + + + + + \ No newline at end of file diff --git a/TIMCommon/timcommon/src/main/res/drawable/switch_track_gray.xml b/TIMCommon/timcommon/src/main/res/drawable/switch_track_gray.xml new file mode 100644 index 0000000..ee7467c --- /dev/null +++ b/TIMCommon/timcommon/src/main/res/drawable/switch_track_gray.xml @@ -0,0 +1,16 @@ + + + + + + + + + + \ No newline at end of file diff --git a/TIMCommon/timcommon/src/main/res/drawable/trans_bg.png b/TIMCommon/timcommon/src/main/res/drawable/trans_bg.png new file mode 100644 index 0000000..0a4c3cd Binary files /dev/null and b/TIMCommon/timcommon/src/main/res/drawable/trans_bg.png differ diff --git a/TIMCommon/timcommon/src/main/res/drawable/translation_area_bg.xml b/TIMCommon/timcommon/src/main/res/drawable/translation_area_bg.xml new file mode 100644 index 0000000..198b891 --- /dev/null +++ b/TIMCommon/timcommon/src/main/res/drawable/translation_area_bg.xml @@ -0,0 +1,9 @@ + + + + + + + + \ No newline at end of file diff --git a/TIMCommon/timcommon/src/main/res/layout/chat_flow_react_item_layout.xml b/TIMCommon/timcommon/src/main/res/layout/chat_flow_react_item_layout.xml new file mode 100644 index 0000000..4e8b1bb --- /dev/null +++ b/TIMCommon/timcommon/src/main/res/layout/chat_flow_react_item_layout.xml @@ -0,0 +1,39 @@ + + + + + + + + + + \ No newline at end of file diff --git a/TIMCommon/timcommon/src/main/res/layout/chat_minimalist_react_item_layout.xml b/TIMCommon/timcommon/src/main/res/layout/chat_minimalist_react_item_layout.xml new file mode 100644 index 0000000..a41b7f9 --- /dev/null +++ b/TIMCommon/timcommon/src/main/res/layout/chat_minimalist_react_item_layout.xml @@ -0,0 +1,15 @@ + + + + + + \ No newline at end of file diff --git a/TIMCommon/timcommon/src/main/res/layout/chat_minimalist_react_preview_layout.xml b/TIMCommon/timcommon/src/main/res/layout/chat_minimalist_react_preview_layout.xml new file mode 100644 index 0000000..461c82f --- /dev/null +++ b/TIMCommon/timcommon/src/main/res/layout/chat_minimalist_react_preview_layout.xml @@ -0,0 +1,35 @@ + + + + + + + + + + \ No newline at end of file diff --git a/TIMCommon/timcommon/src/main/res/layout/chat_minimalist_reply_preview_layout.xml b/TIMCommon/timcommon/src/main/res/layout/chat_minimalist_reply_preview_layout.xml new file mode 100644 index 0000000..e98ec46 --- /dev/null +++ b/TIMCommon/timcommon/src/main/res/layout/chat_minimalist_reply_preview_layout.xml @@ -0,0 +1,53 @@ + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/TIMCommon/timcommon/src/main/res/layout/chat_minimalist_text_status_layout.xml b/TIMCommon/timcommon/src/main/res/layout/chat_minimalist_text_status_layout.xml new file mode 100644 index 0000000..701a258 --- /dev/null +++ b/TIMCommon/timcommon/src/main/res/layout/chat_minimalist_text_status_layout.xml @@ -0,0 +1,29 @@ + + + + + + + + diff --git a/TIMCommon/timcommon/src/main/res/layout/core_activity_image_select_layout.xml b/TIMCommon/timcommon/src/main/res/layout/core_activity_image_select_layout.xml new file mode 100644 index 0000000..28decc8 --- /dev/null +++ b/TIMCommon/timcommon/src/main/res/layout/core_activity_image_select_layout.xml @@ -0,0 +1,25 @@ + + + + + + + + + \ No newline at end of file diff --git a/TIMCommon/timcommon/src/main/res/layout/core_minimalist_activity_image_select_layout.xml b/TIMCommon/timcommon/src/main/res/layout/core_minimalist_activity_image_select_layout.xml new file mode 100644 index 0000000..1fc21e1 --- /dev/null +++ b/TIMCommon/timcommon/src/main/res/layout/core_minimalist_activity_image_select_layout.xml @@ -0,0 +1,24 @@ + + + + + + + + + \ No newline at end of file diff --git a/TIMCommon/timcommon/src/main/res/layout/core_minimalist_selection_activity.xml b/TIMCommon/timcommon/src/main/res/layout/core_minimalist_selection_activity.xml new file mode 100644 index 0000000..dc97cfc --- /dev/null +++ b/TIMCommon/timcommon/src/main/res/layout/core_minimalist_selection_activity.xml @@ -0,0 +1,28 @@ + + + + + + + + + diff --git a/TIMCommon/timcommon/src/main/res/layout/core_pop_menu.xml b/TIMCommon/timcommon/src/main/res/layout/core_pop_menu.xml new file mode 100644 index 0000000..1232a7e --- /dev/null +++ b/TIMCommon/timcommon/src/main/res/layout/core_pop_menu.xml @@ -0,0 +1,16 @@ + + + + + + \ No newline at end of file diff --git a/TIMCommon/timcommon/src/main/res/layout/core_select_image_item_layout.xml b/TIMCommon/timcommon/src/main/res/layout/core_select_image_item_layout.xml new file mode 100644 index 0000000..d1bd79c --- /dev/null +++ b/TIMCommon/timcommon/src/main/res/layout/core_select_image_item_layout.xml @@ -0,0 +1,63 @@ + + + + + + + +