Skip to content

Commit e9507f9

Browse files
authored
Merge pull request #54 from harperhpliu/main
【Android】Chat 4.2 release
2 parents 6b77d5f + 4786152 commit e9507f9

758 files changed

Lines changed: 72891 additions & 0 deletions

File tree

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

chat/demo/.gitignore

Lines changed: 17 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,17 @@
1+
.cursor/
2+
*.iml
3+
.gradle
4+
.kotlin
5+
/local.properties
6+
/.idea/caches
7+
/.idea/libraries
8+
/.idea/modules.xml
9+
/.idea/workspace.xml
10+
/.idea/navEditor.xml
11+
/.idea/assetWizardSettings.xml
12+
.DS_Store
13+
/build
14+
/captures
15+
.externalNativeBuild
16+
.cxx
17+
local.properties

chat/demo/app/build.gradle.kts

Lines changed: 54 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,54 @@
1+
plugins {
2+
id("com.android.application")
3+
id("org.jetbrains.kotlin.android")
4+
}
5+
6+
android {
7+
namespace = "io.trtc.tuikit.chat.app"
8+
compileSdk = 35
9+
10+
defaultConfig {
11+
applicationId = "com.tencent.qcloud.tim.tuikit"
12+
minSdk = 23
13+
targetSdk = 35
14+
versionCode = 1
15+
versionName = "1.0"
16+
17+
testInstrumentationRunner = "androidx.test.runner.AndroidJUnitRunner"
18+
}
19+
20+
buildTypes {
21+
release {
22+
isMinifyEnabled = false
23+
proguardFiles(getDefaultProguardFile("proguard-android-optimize.txt"), "proguard-rules.pro")
24+
}
25+
}
26+
compileOptions {
27+
sourceCompatibility = JavaVersion.VERSION_17
28+
targetCompatibility = JavaVersion.VERSION_17
29+
}
30+
kotlinOptions {
31+
jvmTarget = "17"
32+
}
33+
}
34+
35+
dependencies {
36+
implementation(project(":uikit"))
37+
implementation(project(":atomic_x"))
38+
implementation(project(":tuicallkit-kt"))
39+
implementation("com.tencent.imsdk:imsdk-plus:latest.release")
40+
implementation("com.tencent.liteav.tuikit:tuicore:9.0.+") {
41+
exclude("com.tencent.imsdk", "imsdk-plus")
42+
}
43+
44+
implementation("com.tencent:mmkv:2.4.0")
45+
implementation("androidx.core:core-ktx:1.10.1")
46+
implementation("androidx.appcompat:appcompat:1.6.1")
47+
implementation("com.google.android.material:material:1.12.0")
48+
implementation("androidx.constraintlayout:constraintlayout:2.1.4")
49+
implementation("androidx.activity:activity:1.8.2")
50+
implementation("androidx.lifecycle:lifecycle-runtime-ktx:2.6.2")
51+
implementation("androidx.viewpager2:viewpager2:1.1.0")
52+
implementation("org.jetbrains.kotlinx:kotlinx-coroutines-android:1.7.3")
53+
54+
}

chat/demo/app/proguard-rules.pro

Lines changed: 23 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,23 @@
1+
# Add project specific ProGuard rules here.
2+
# You can control the set of applied configuration files using the
3+
# proguardFiles setting in build.gradle.
4+
#
5+
# For more details, see
6+
# http://developer.android.com/guide/developing/tools/proguard.html
7+
#
8+
# If your project uses WebView with JS, uncomment the following
9+
# and specify the fully qualified class name to the JavaScript interface
10+
# class:
11+
#-keepclassmembers class fqcn.of.javascript.interface.for.webview {
12+
# public *;
13+
#}
14+
#
15+
# Uncomment this to preserve the line number information for
16+
# debugging stack traces.
17+
#-keepattributes SourceFile,LineNumberTable
18+
#
19+
# If you keep the line number information, uncomment this to
20+
# hide the original source file name.
21+
#-renamesourcefileattribute SourceFile
22+
-keep class com.tencent.qcloud.** { *; }
23+
-keep class com.tencent.timpush.** { *; }
Lines changed: 48 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,48 @@
1+
<?xml version="1.0" encoding="utf-8"?>
2+
<manifest xmlns:android="http://schemas.android.com/apk/res/android">
3+
4+
<uses-permission android:name="android.permission.INTERNET" />
5+
<uses-permission android:name="android.permission.ACCESS_NETWORK_STATE" />
6+
7+
<application
8+
android:name="io.trtc.tuikit.chat.demo.Application"
9+
android:icon="@drawable/ic_launcher"
10+
android:label="@string/app_name"
11+
android:roundIcon="@drawable/ic_launcher"
12+
android:supportsRtl="true"
13+
android:theme="@style/Theme.ChatView">
14+
15+
<activity
16+
android:name="io.trtc.tuikit.chat.demo.login.LoginActivity"
17+
android:exported="true">
18+
<intent-filter>
19+
<action android:name="android.intent.action.MAIN" />
20+
<category android:name="android.intent.category.LAUNCHER" />
21+
</intent-filter>
22+
</activity>
23+
24+
<activity
25+
android:name="io.trtc.tuikit.chat.demo.MainActivity"
26+
android:exported="true"
27+
android:windowSoftInputMode="adjustNothing" />
28+
29+
<activity
30+
android:name="io.trtc.tuikit.chat.demo.chat.ChatActivity"
31+
android:exported="false"
32+
android:windowSoftInputMode="adjustNothing" />
33+
34+
<activity
35+
android:name="io.trtc.tuikit.chat.demo.chat.ChatSettingActivity"
36+
android:exported="false" />
37+
38+
<activity
39+
android:name="io.trtc.tuikit.chat.demo.SelfDetailActivity"
40+
android:exported="false" />
41+
42+
<activity
43+
android:name="io.trtc.tuikit.chat.demo.search.SearchActivity"
44+
android:exported="false"
45+
android:windowSoftInputMode="adjustNothing" />
46+
47+
</application>
48+
</manifest>
Lines changed: 13 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,13 @@
1+
package io.trtc.tuikit.chat.demo
2+
3+
object AppConstants {
4+
5+
const val KEY_ENABLE_READ_RECEIPT = "enable_read_receipt"
6+
const val KEY_THEME_MODE = "theme_mode"
7+
const val KEY_APP_LANGUAGE = "app_language"
8+
const val KEY_LOGIN_USER = "LoginUser"
9+
10+
const val THEME_MODE_SYSTEM = 0
11+
const val THEME_MODE_LIGHT = 1
12+
const val THEME_MODE_DARK = 2
13+
}
Lines changed: 66 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,66 @@
1+
package io.trtc.tuikit.chat.demo
2+
3+
import android.app.Application
4+
import android.content.res.Configuration
5+
import androidx.appcompat.app.AppCompatDelegate
6+
import androidx.core.os.LocaleListCompat
7+
import com.tencent.mmkv.MMKV
8+
import io.trtc.tuikit.chat.uikit.components.config.AppBuilder
9+
import io.trtc.tuikit.chat.uikit.components.config.AppBuilderConfig
10+
import io.trtc.tuikit.atomicx.theme.Theme
11+
import io.trtc.tuikit.atomicx.theme.ThemeStore
12+
13+
class Application : Application() {
14+
15+
override fun onCreate() {
16+
super.onCreate()
17+
MMKV.initialize(this)
18+
AppBuilder.loadConfig(this)
19+
20+
applyLanguageFromSettings()
21+
22+
MMKV.defaultMMKV().decodeBool(AppConstants.KEY_ENABLE_READ_RECEIPT, false).also {
23+
AppBuilderConfig.enableReadReceipt = it
24+
}
25+
26+
applyThemeFromSettings()
27+
}
28+
29+
override fun onConfigurationChanged(newConfig: Configuration) {
30+
super.onConfigurationChanged(newConfig)
31+
val themeMode = MMKV.defaultMMKV().decodeInt(AppConstants.KEY_THEME_MODE, AppConstants.THEME_MODE_SYSTEM)
32+
if (themeMode == AppConstants.THEME_MODE_SYSTEM) {
33+
applySystemTheme(newConfig)
34+
}
35+
}
36+
37+
private fun applyThemeFromSettings() {
38+
val themeMode = MMKV.defaultMMKV().decodeInt(AppConstants.KEY_THEME_MODE, AppConstants.THEME_MODE_SYSTEM)
39+
val themeStore = ThemeStore.shared(this)
40+
when (themeMode) {
41+
AppConstants.THEME_MODE_LIGHT -> themeStore.setTheme(Theme.lightTheme(this))
42+
AppConstants.THEME_MODE_DARK -> themeStore.setTheme(Theme.darkTheme(this))
43+
else -> applySystemTheme(resources.configuration)
44+
}
45+
}
46+
47+
private fun applyLanguageFromSettings() {
48+
val languageTag = MMKV.defaultMMKV().decodeString(AppConstants.KEY_APP_LANGUAGE, "").orEmpty()
49+
val targetLocales = if (languageTag.isBlank()) {
50+
LocaleListCompat.getEmptyLocaleList()
51+
} else {
52+
LocaleListCompat.forLanguageTags(languageTag)
53+
}
54+
if (AppCompatDelegate.getApplicationLocales() != targetLocales) {
55+
AppCompatDelegate.setApplicationLocales(targetLocales)
56+
}
57+
}
58+
59+
private fun applySystemTheme(config: Configuration) {
60+
val isNight = (config.uiMode and Configuration.UI_MODE_NIGHT_MASK) ==
61+
Configuration.UI_MODE_NIGHT_YES
62+
ThemeStore.shared(this).setTheme(
63+
if (isNight) Theme.darkTheme(this) else Theme.lightTheme(this)
64+
)
65+
}
66+
}
Lines changed: 80 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,80 @@
1+
package io.trtc.tuikit.chat.demo
2+
3+
import kotlin.math.min
4+
import kotlin.math.roundToInt
5+
6+
internal data class BadgePosition(
7+
val left: Int,
8+
val top: Int
9+
)
10+
11+
internal data class BadgeDragOffset(
12+
val x: Int,
13+
val y: Int
14+
)
15+
16+
internal object BadgeDragPolicy {
17+
fun isInBadgeTouchArea(
18+
itemWidth: Int,
19+
itemHeight: Int,
20+
touchX: Float,
21+
touchY: Float,
22+
isRtl: Boolean
23+
): Boolean {
24+
if (itemWidth <= 0 || itemHeight <= 0) {
25+
return false
26+
}
27+
val touchAreaSize = min(itemWidth, itemHeight) / 2f
28+
val inTopArea = touchY in 0f..touchAreaSize
29+
val inEndArea = if (isRtl) {
30+
touchX in 0f..touchAreaSize
31+
} else {
32+
touchX in (itemWidth - touchAreaSize)..itemWidth.toFloat()
33+
}
34+
return inTopArea && inEndArea
35+
}
36+
37+
fun shouldClearUnread(startRawY: Float, currentRawY: Float, thresholdPx: Int): Boolean {
38+
return startRawY - currentRawY >= thresholdPx
39+
}
40+
41+
fun badgeVerticalOffset(startRawY: Float, currentRawY: Float, thresholdPx: Int): Int {
42+
val dragUpDistance = (startRawY - currentRawY).coerceIn(0f, thresholdPx.toFloat())
43+
return -dragUpDistance.roundToInt()
44+
}
45+
46+
fun badgeDragOffset(
47+
startRawX: Float,
48+
startRawY: Float,
49+
currentRawX: Float,
50+
currentRawY: Float
51+
): BadgeDragOffset {
52+
val dragUpDistance = (startRawY - currentRawY).coerceAtLeast(0f)
53+
return BadgeDragOffset(
54+
x = (currentRawX - startRawX).roundToInt(),
55+
y = -dragUpDistance.roundToInt()
56+
)
57+
}
58+
59+
fun badgeTopEndPosition(
60+
anchorLeft: Int,
61+
anchorTop: Int,
62+
anchorWidth: Int,
63+
badgeWidth: Int,
64+
badgeHeight: Int,
65+
horizontalOffsetPx: Int,
66+
verticalOffsetPx: Int,
67+
isRtl: Boolean
68+
): BadgePosition {
69+
val badgeCenterX = if (isRtl) {
70+
anchorLeft - horizontalOffsetPx
71+
} else {
72+
anchorLeft + anchorWidth + horizontalOffsetPx
73+
}
74+
val badgeCenterY = anchorTop + verticalOffsetPx
75+
return BadgePosition(
76+
left = badgeCenterX - badgeWidth / 2,
77+
top = badgeCenterY - badgeHeight / 2
78+
)
79+
}
80+
}
Lines changed: 82 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,82 @@
1+
package io.trtc.tuikit.chat.demo
2+
3+
import android.content.res.Configuration
4+
import android.graphics.Color
5+
import android.graphics.drawable.ColorDrawable
6+
import android.os.Bundle
7+
import androidx.activity.enableEdgeToEdge
8+
import androidx.appcompat.app.AppCompatActivity
9+
import androidx.core.view.WindowCompat
10+
import androidx.core.view.WindowInsetsControllerCompat
11+
import com.tencent.mmkv.MMKV
12+
import io.trtc.tuikit.atomicx.theme.Theme
13+
import io.trtc.tuikit.atomicx.theme.ThemeStore
14+
import io.trtc.tuikit.atomicx.theme.tokens.ColorTokens
15+
import kotlinx.coroutines.CoroutineScope
16+
import kotlinx.coroutines.Dispatchers
17+
import kotlinx.coroutines.SupervisorJob
18+
import kotlinx.coroutines.cancel
19+
import kotlinx.coroutines.flow.collectLatest
20+
import kotlinx.coroutines.launch
21+
22+
abstract class BaseActivity : AppCompatActivity() {
23+
24+
private val themeStore by lazy { ThemeStore.shared(this) }
25+
private var themeScope: CoroutineScope? = null
26+
27+
override fun onCreate(savedInstanceState: Bundle?) {
28+
enableEdgeToEdge()
29+
super.onCreate(savedInstanceState)
30+
WindowCompat.setDecorFitsSystemWindows(window, false)
31+
window.statusBarColor = Color.TRANSPARENT
32+
window.navigationBarColor = Color.TRANSPARENT
33+
updateWindowAppearance(themeStore.themeState.value.currentTheme.tokens.color)
34+
}
35+
36+
override fun onStart() {
37+
super.onStart()
38+
themeScope = CoroutineScope(Dispatchers.Main + SupervisorJob())
39+
themeScope?.launch {
40+
themeStore.themeState.collectLatest { state ->
41+
updateWindowAppearance(state.currentTheme.tokens.color)
42+
}
43+
}
44+
}
45+
46+
override fun onStop() {
47+
super.onStop()
48+
themeScope?.cancel()
49+
themeScope = null
50+
}
51+
52+
override fun onConfigurationChanged(newConfig: Configuration) {
53+
super.onConfigurationChanged(newConfig)
54+
val themeMode = MMKV.defaultMMKV().decodeInt(
55+
AppConstants.KEY_THEME_MODE,
56+
AppConstants.THEME_MODE_SYSTEM
57+
)
58+
if (themeMode == AppConstants.THEME_MODE_SYSTEM) {
59+
val isNight = (newConfig.uiMode and Configuration.UI_MODE_NIGHT_MASK) ==
60+
Configuration.UI_MODE_NIGHT_YES
61+
themeStore.setTheme(
62+
if (isNight) Theme.darkTheme(this) else Theme.lightTheme(this)
63+
)
64+
}
65+
}
66+
67+
private fun updateWindowAppearance(colors: ColorTokens) {
68+
window.setBackgroundDrawable(ColorDrawable(colors.bgColorDefault))
69+
val controller = WindowInsetsControllerCompat(window, window.decorView)
70+
val isLight = isColorLight(colors.bgColorOperate)
71+
controller.isAppearanceLightStatusBars = isLight
72+
controller.isAppearanceLightNavigationBars = isLight
73+
}
74+
75+
private fun isColorLight(color: Int): Boolean {
76+
val r = (color shr 16) and 0xFF
77+
val g = (color shr 8) and 0xFF
78+
val b = color and 0xFF
79+
val luminance = (0.299 * r + 0.587 * g + 0.114 * b) / 255
80+
return luminance > 0.5
81+
}
82+
}
Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,6 @@
1+
package io.trtc.tuikit.chat.demo
2+
3+
sealed class Event {
4+
data class ContactDeleted(val contactID: String) : Event()
5+
data class GroupDeleted(val groupID: String) : Event()
6+
}

0 commit comments

Comments
 (0)