commit 1c44b61fc213d22ff955a344bf48fab7a72607b8 Author: yenne Date: Tue Dec 26 11:06:01 2023 +0800 compose微信界面 diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..aa724b7 --- /dev/null +++ b/.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/.idea/.gitignore b/.idea/.gitignore new file mode 100644 index 0000000..359bb53 --- /dev/null +++ b/.idea/.gitignore @@ -0,0 +1,3 @@ +# 默认忽略的文件 +/shelf/ +/workspace.xml diff --git a/.idea/compiler.xml b/.idea/compiler.xml new file mode 100644 index 0000000..b589d56 --- /dev/null +++ b/.idea/compiler.xml @@ -0,0 +1,6 @@ + + + + + + \ No newline at end of file diff --git a/.idea/deploymentTargetDropDown.xml b/.idea/deploymentTargetDropDown.xml new file mode 100644 index 0000000..959afe7 --- /dev/null +++ b/.idea/deploymentTargetDropDown.xml @@ -0,0 +1,29 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/.idea/gradle.xml b/.idea/gradle.xml new file mode 100644 index 0000000..0897082 --- /dev/null +++ b/.idea/gradle.xml @@ -0,0 +1,19 @@ + + + + + + + \ No newline at end of file diff --git a/.idea/inspectionProfiles/Project_Default.xml b/.idea/inspectionProfiles/Project_Default.xml new file mode 100644 index 0000000..44ca2d9 --- /dev/null +++ b/.idea/inspectionProfiles/Project_Default.xml @@ -0,0 +1,41 @@ + + + + \ No newline at end of file diff --git a/.idea/kotlinc.xml b/.idea/kotlinc.xml new file mode 100644 index 0000000..fdf8d99 --- /dev/null +++ b/.idea/kotlinc.xml @@ -0,0 +1,6 @@ + + + + + \ No newline at end of file diff --git a/.idea/migrations.xml b/.idea/migrations.xml new file mode 100644 index 0000000..f8051a6 --- /dev/null +++ b/.idea/migrations.xml @@ -0,0 +1,10 @@ + + + + + + \ No newline at end of file diff --git a/.idea/misc.xml b/.idea/misc.xml new file mode 100644 index 0000000..8978d23 --- /dev/null +++ b/.idea/misc.xml @@ -0,0 +1,9 @@ + + + + + + + + \ No newline at end of file diff --git a/.idea/vcs.xml b/.idea/vcs.xml new file mode 100644 index 0000000..94a25f7 --- /dev/null +++ b/.idea/vcs.xml @@ -0,0 +1,6 @@ + + + + + + \ No newline at end of file diff --git a/app/.gitignore b/app/.gitignore new file mode 100644 index 0000000..42afabf --- /dev/null +++ b/app/.gitignore @@ -0,0 +1 @@ +/build \ No newline at end of file diff --git a/app/build.gradle.kts b/app/build.gradle.kts new file mode 100644 index 0000000..12313ea --- /dev/null +++ b/app/build.gradle.kts @@ -0,0 +1,70 @@ +plugins { + id("com.android.application") + id("org.jetbrains.kotlin.android") +} + +android { + namespace = "com.example.wechat" + compileSdk = 34 + + defaultConfig { + applicationId = "com.example.wechat" + minSdk = 29 + targetSdk = 34 + versionCode = 1 + versionName = "1.0" + + testInstrumentationRunner = "androidx.test.runner.AndroidJUnitRunner" + vectorDrawables { + useSupportLibrary = true + } + } + + buildTypes { + release { + isMinifyEnabled = false + proguardFiles( + getDefaultProguardFile("proguard-android-optimize.txt"), + "proguard-rules.pro" + ) + } + } + compileOptions { + sourceCompatibility = JavaVersion.VERSION_1_8 + targetCompatibility = JavaVersion.VERSION_1_8 + } + kotlinOptions { + jvmTarget = "1.8" + } + buildFeatures { + compose = true + } + composeOptions { + kotlinCompilerExtensionVersion = "1.5.1" + } + packaging { + resources { + excludes += "/META-INF/{AL2.0,LGPL2.1}" + } + } +} + +dependencies { + + implementation("androidx.core:core-ktx:1.12.0") + implementation("androidx.lifecycle:lifecycle-runtime-ktx:2.6.2") + implementation("androidx.activity:activity-compose:1.8.2") + implementation(platform("androidx.compose:compose-bom:2023.08.00")) + implementation("androidx.compose.ui:ui") + implementation("androidx.compose.ui:ui-graphics") + implementation("androidx.compose.ui:ui-tooling-preview") + implementation("androidx.compose.material3:material3") + implementation("androidx.lifecycle:lifecycle-viewmodel-compose:2.6.2") + testImplementation("junit:junit:4.13.2") + androidTestImplementation("androidx.test.ext:junit:1.1.5") + androidTestImplementation("androidx.test.espresso:espresso-core:3.5.1") + androidTestImplementation(platform("androidx.compose:compose-bom:2023.08.00")) + androidTestImplementation("androidx.compose.ui:ui-test-junit4") + debugImplementation("androidx.compose.ui:ui-tooling") + debugImplementation("androidx.compose.ui:ui-test-manifest") +} \ No newline at end of file diff --git a/app/proguard-rules.pro b/app/proguard-rules.pro new file mode 100644 index 0000000..481bb43 --- /dev/null +++ b/app/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/app/src/androidTest/java/com/example/wechat/ExampleInstrumentedTest.kt b/app/src/androidTest/java/com/example/wechat/ExampleInstrumentedTest.kt new file mode 100644 index 0000000..7413fae --- /dev/null +++ b/app/src/androidTest/java/com/example/wechat/ExampleInstrumentedTest.kt @@ -0,0 +1,24 @@ +package com.example.wechat + +import androidx.test.platform.app.InstrumentationRegistry +import androidx.test.ext.junit.runners.AndroidJUnit4 + +import org.junit.Test +import org.junit.runner.RunWith + +import org.junit.Assert.* + +/** + * Instrumented test, which will execute on an Android device. + * + * See [testing documentation](http://d.android.com/tools/testing). + */ +@RunWith(AndroidJUnit4::class) +class ExampleInstrumentedTest { + @Test + fun useAppContext() { + // Context of the app under test. + val appContext = InstrumentationRegistry.getInstrumentation().targetContext + assertEquals("com.example.wechat", appContext.packageName) + } +} \ No newline at end of file diff --git a/app/src/main/AndroidManifest.xml b/app/src/main/AndroidManifest.xml new file mode 100644 index 0000000..3150a2b --- /dev/null +++ b/app/src/main/AndroidManifest.xml @@ -0,0 +1,28 @@ + + + + + + + + + + + + + + \ No newline at end of file diff --git a/app/src/main/java/com/example/wechat/MainActivity.kt b/app/src/main/java/com/example/wechat/MainActivity.kt new file mode 100644 index 0000000..688c46f --- /dev/null +++ b/app/src/main/java/com/example/wechat/MainActivity.kt @@ -0,0 +1,32 @@ +package com.example.wechat + +import android.os.Bundle +import androidx.activity.ComponentActivity +import androidx.activity.compose.setContent +import androidx.activity.viewModels +import androidx.compose.foundation.layout.Box +import com.example.wechat.ui.ChatPage +import com.example.wechat.ui.Home +import com.example.wechat.ui.theme.WeChatTheme + +class MainActivity : ComponentActivity() { + private val viewModel: WeViewModel by viewModels() + + override fun onCreate(savedInstanceState: Bundle?) { + super.onCreate(savedInstanceState) + setContent { + WeChatTheme(viewModel.theme) { + Box { + Home(viewModel) + ChatPage() + } + } + } + } + + override fun onBackPressed() { + if (!viewModel.endChat()) { + super.onBackPressed() + } + } +} \ No newline at end of file diff --git a/app/src/main/java/com/example/wechat/WeViewModel.kt b/app/src/main/java/com/example/wechat/WeViewModel.kt new file mode 100644 index 0000000..b96e340 --- /dev/null +++ b/app/src/main/java/com/example/wechat/WeViewModel.kt @@ -0,0 +1,63 @@ +package com.example.wechat + +import androidx.compose.runtime.getValue +import androidx.compose.runtime.mutableStateListOf +import androidx.compose.runtime.mutableStateOf +import androidx.compose.runtime.setValue +import androidx.lifecycle.ViewModel +import com.example.wechat.data.Chat +import com.example.wechat.data.Msg +import com.example.wechat.data.User +import com.example.wechat.ui.theme.WeChatTheme + +class WeViewModel: ViewModel() { + var chats by mutableStateOf( + listOf( // List + Chat( + friend = User("jskj", "骏升科技", R.drawable.avatar_jskj), + mutableStateListOf( + Msg(User("jskj", "骏升科技", R.drawable.avatar_jskj), "早上好!", "8:20"), + Msg(User.Me, "早", "14:21"), + Msg(User("jskj", "骏升科技", R.drawable.avatar_jskj), "该吃饭了", "12:00"), + Msg(User.Me, "我们去吃饭吧", "12:02"), + Msg(User("jskj", "骏升科技", R.drawable.avatar_jskj), "下班了", "18:00"), + Msg(User.Me, "拜拜", "18:00"), + ) + ), + Chat( + friend = User("yinzhicheng", "尹志成", R.drawable.avatar_yinzhicheng), + mutableStateListOf( + Msg(User("yinzhicheng", "尹志成", R.drawable.avatar_yinzhicheng), "你好", "8:00"), + Msg(User.Me, "Hello!", "8:00"), + Msg(User("yinzhicheng", "尹志成", R.drawable.avatar_yinzhicheng), "请用compose写一个简单的微信界面", "12:00").apply { read = false }, + ) + ), + ) + ) + val contacts by mutableStateOf( + listOf( + User("jskj", "骏升科技", R.drawable.avatar_jskj), + User("yinzhicheng", "尹志成", R.drawable.avatar_yinzhicheng) + ) + ) + var theme by mutableStateOf(WeChatTheme.Theme.Light) + var currentChat: Chat? by mutableStateOf(null) + var chatting by mutableStateOf(false) + + fun startChat(chat: Chat) { + chatting = true + currentChat = chat + } + + fun endChat(): Boolean { + if (chatting) { + chatting = false + return true + } + return false + } + + fun boom(chat: Chat) { + chat.msgs.add(Msg(User.Me, "\uD83D\uDCA3", "15:10").apply { read = true }) + } +} \ No newline at end of file diff --git a/app/src/main/java/com/example/wechat/data/Chat.kt b/app/src/main/java/com/example/wechat/data/Chat.kt new file mode 100644 index 0000000..6ac3fb7 --- /dev/null +++ b/app/src/main/java/com/example/wechat/data/Chat.kt @@ -0,0 +1,12 @@ +package com.example.wechat.data + +import androidx.compose.runtime.getValue +import androidx.compose.runtime.mutableStateOf +import androidx.compose.runtime.setValue + +class Chat(var friend: User, var msgs: MutableList) { +} + +class Msg(val from: User, val text: String, val time: String) { + var read: Boolean by mutableStateOf(true) +} \ No newline at end of file diff --git a/app/src/main/java/com/example/wechat/data/User.kt b/app/src/main/java/com/example/wechat/data/User.kt new file mode 100644 index 0000000..e0d7525 --- /dev/null +++ b/app/src/main/java/com/example/wechat/data/User.kt @@ -0,0 +1,14 @@ +package com.example.wechat.data + +import androidx.annotation.DrawableRes +import com.example.wechat.R + +class User( + val id: String, + val name: String, + @DrawableRes val avatar: Int +) { + companion object { + val Me: User = User("YZC1361450845", "🔱", R.drawable.avatar_me) + } +} \ No newline at end of file diff --git a/app/src/main/java/com/example/wechat/ui/ChatList.kt b/app/src/main/java/com/example/wechat/ui/ChatList.kt new file mode 100644 index 0000000..9adecda --- /dev/null +++ b/app/src/main/java/com/example/wechat/ui/ChatList.kt @@ -0,0 +1,97 @@ +package com.example.wechat.ui + +import androidx.compose.foundation.Image +import androidx.compose.foundation.background +import androidx.compose.foundation.clickable +import androidx.compose.foundation.layout.* +import androidx.compose.foundation.lazy.LazyColumn +import androidx.compose.foundation.lazy.LazyItemScope +import androidx.compose.foundation.lazy.itemsIndexed +import androidx.compose.foundation.shape.RoundedCornerShape +import androidx.compose.material3.Text +import androidx.compose.runtime.Composable +import androidx.compose.ui.Alignment +import androidx.compose.ui.Modifier +import androidx.compose.ui.draw.clip +import androidx.compose.ui.draw.drawWithContent +import androidx.compose.ui.geometry.Offset +import androidx.compose.ui.graphics.Color +import androidx.compose.ui.res.painterResource +import androidx.compose.ui.unit.Dp +import androidx.compose.ui.unit.dp +import androidx.compose.ui.unit.sp +import androidx.lifecycle.viewmodel.compose.viewModel +import com.example.wechat.WeViewModel +import com.example.wechat.data.Chat +import com.example.wechat.ui.theme.WeChatTheme + +@Composable +fun ChatList(chats: List) { + // ListView + // RecyclerView + // onDraw() + Column( + Modifier + .background(WeChatTheme.colors.background) + .fillMaxSize() + ) { + WeTopBar(title = "微信") + LazyColumn(Modifier.background(WeChatTheme.colors.listItem)) { + itemsIndexed(chats) { index, chat -> + ChatListItem(chat) + if (index < chats.lastIndex) { + Divider( + startIndent = 68.dp, + color = WeChatTheme.colors.chatListDivider, + thickness = 0.8f.dp + ) + } + } + } + } +} + +internal fun LazyItemScope.Divider(startIndent: Dp, color: Color, thickness: Dp) { + TODO("Not yet implemented") +} + +@Composable +private fun ChatListItem(chat: Chat) { + val viewModel: WeViewModel = viewModel() + Row( + Modifier + .clickable { + viewModel.startChat(chat) + } + .fillMaxWidth() + ) { + Image( + painterResource(chat.friend.avatar), chat.friend.name, + Modifier + .padding(8.dp) + .size(48.dp) + .unread(!chat.msgs.last().read, WeChatTheme.colors.badge) + .clip(RoundedCornerShape(4.dp)) + ) + Column( + Modifier + .weight(1f) + .align(Alignment.CenterVertically) + ) { + Text(chat.friend.name, fontSize = 17.sp, color = WeChatTheme.colors.textPrimary) + Text(chat.msgs.last().text, fontSize = 14.sp, color = WeChatTheme.colors.textSecondary) + } + Text( + chat.msgs.last().time, + Modifier.padding(8.dp, 8.dp, 12.dp, 8.dp), + fontSize = 11.sp, color = WeChatTheme.colors.textSecondary + ) + } +} + +fun Modifier.unread(show: Boolean, color: Color): Modifier = this.drawWithContent { + drawContent() + if (show) { + drawCircle(color, 5.dp.toPx(), Offset(size.width - 1.dp.toPx(), 1.dp.toPx())) + } +} \ No newline at end of file diff --git a/app/src/main/java/com/example/wechat/ui/ChatPage.kt b/app/src/main/java/com/example/wechat/ui/ChatPage.kt new file mode 100644 index 0000000..3c7da37 --- /dev/null +++ b/app/src/main/java/com/example/wechat/ui/ChatPage.kt @@ -0,0 +1,284 @@ +package com.example.wechat.ui + +import androidx.compose.animation.core.Animatable +import androidx.compose.animation.core.animateFloatAsState +import androidx.compose.animation.core.spring +import androidx.compose.foundation.Image +import androidx.compose.foundation.background +import androidx.compose.foundation.clickable +import androidx.compose.foundation.layout.* +import androidx.compose.foundation.lazy.LazyColumn +import androidx.compose.foundation.shape.RoundedCornerShape +import androidx.compose.foundation.text.BasicTextField +import androidx.compose.material3.Icon +import androidx.compose.material3.MaterialTheme +import androidx.compose.material3.Text +import androidx.compose.runtime.* +import androidx.compose.ui.Alignment +import androidx.compose.ui.Modifier +import androidx.compose.ui.draw.alpha +import androidx.compose.ui.draw.clip +import androidx.compose.ui.draw.drawBehind +import androidx.compose.ui.geometry.RoundRect +import androidx.compose.ui.graphics.Path +import androidx.compose.ui.graphics.SolidColor +import androidx.compose.ui.graphics.TransformOrigin +import androidx.compose.ui.graphics.graphicsLayer +import androidx.compose.ui.layout.layout +import androidx.compose.ui.res.painterResource +import androidx.compose.ui.unit.dp +import androidx.compose.ui.unit.sp +import androidx.lifecycle.viewmodel.compose.viewModel +import com.example.wechat.R +import com.example.wechat.WeViewModel +import com.example.wechat.data.Msg +import com.example.wechat.data.User +import com.example.wechat.ui.theme.WeChatTheme +import kotlinx.coroutines.delay +import kotlin.math.roundToInt + +@Composable +fun ChatPage(modifier: Modifier = Modifier) { + val viewModel: WeViewModel = viewModel() + val offsetPercentX by animateFloatAsState(if (viewModel.chatting) 0f else 1f, label = "") + val chat = viewModel.currentChat + if (chat != null) { + Column( + modifier + .offsetPercent(offsetPercentX) + .background(WeChatTheme.colors.background) + .fillMaxSize() + ) { + WeTopBar(chat.friend.name) { + viewModel.endChat() + } + var shakingTime by remember { + mutableStateOf(0) + } + Box( + Modifier + .background(WeChatTheme.colors.chatPage) + .weight(1f) + ) { + Box( + Modifier + .alpha(WeChatTheme.colors.chatPageBgAlpha) + .fillMaxSize() + ) { + Image( + painterResource(R.drawable.ic_bg_newyear_left), null, + Modifier + .align(Alignment.CenterStart) + .padding(bottom = 100.dp) + ) + Image( + painterResource(R.drawable.ic_bg_newyear_top), null, + Modifier + .align(Alignment.TopEnd) + .padding(horizontal = 24.dp) + ) + Image( + painterResource(R.drawable.ic_bg_newyear_right), null, + Modifier + .align(Alignment.BottomEnd) + .padding(vertical = 200.dp) + ) + } + val shakingOffset = remember { + Animatable(0f) + } + LaunchedEffect(key1 = shakingTime) { + if (shakingTime != 0) { + shakingOffset.animateTo( + 0f, + animationSpec = spring(0.3f, 600f), + initialVelocity = -2000f + ) + } + } + LazyColumn( + Modifier + .fillMaxSize() + .offset(shakingOffset.value.dp, shakingOffset.value.dp) + ) { + items(chat.msgs.size) { index -> + val msg = chat.msgs[index] + MessageItem(msg, shakingTime, chat.msgs.size - index - 1) + } + } + } + ChatBottomBar(onBombClicked = { + viewModel.boom(chat) + shakingTime++ + }) + } + } +} + +@Composable +fun MessageItem(msg: Msg, shakingTime: Int, shakingLevel: Int) { + val shakingAngleBubble = remember { Animatable(0f) } + LaunchedEffect(key1 = shakingTime, block = { + if (shakingTime != 0) { + delay(shakingLevel.toLong() * 30) + shakingAngleBubble.animateTo( + 0f, + animationSpec = spring(0.4f, 500f), + initialVelocity = 1200f / (1 + shakingLevel * 0.4f) + ) + } + }) + if (msg.from == User.Me) { + Row( + Modifier + .fillMaxWidth() + .padding(8.dp), + horizontalArrangement = Arrangement.End + ) { + val bubbleColor = WeChatTheme.colors.bubbleMe + Text( + msg.text, + Modifier + .graphicsLayer( + rotationZ = shakingAngleBubble.value, + transformOrigin = TransformOrigin(1f, 0f) + ) + .drawBehind { + val bubble = Path().apply { + val rect = RoundRect( + 10.dp.toPx(), + 0f, + size.width - 10.dp.toPx(), + size.height, + 4.dp.toPx(), + 4.dp.toPx() + ) + addRoundRect(rect) + moveTo(size.width - 10.dp.toPx(), 15.dp.toPx()) + lineTo(size.width - 5.dp.toPx(), 20.dp.toPx()) + lineTo(size.width - 10.dp.toPx(), 25.dp.toPx()) + close() + } + drawPath(bubble, bubbleColor) + } + .padding(20.dp, 10.dp), + color = WeChatTheme.colors.textPrimaryMe) + Image( + painterResource(msg.from.avatar), + contentDescription = msg.from.name, + Modifier + .graphicsLayer( + rotationZ = shakingAngleBubble.value * 0.6f, + transformOrigin = TransformOrigin(1f, 0f) + ) + .size(40.dp) + .clip(RoundedCornerShape(4.dp)) + ) + } + } else { + Row( + Modifier + .fillMaxWidth() + .padding(8.dp) + ) { + Image( + painterResource(msg.from.avatar), + contentDescription = msg.from.name, + Modifier + .graphicsLayer( + rotationZ = -shakingAngleBubble.value * 0.6f, + transformOrigin = TransformOrigin(0f, 0f) + ) + .size(40.dp) + .clip(RoundedCornerShape(4.dp)) + ) + val bubbleColor = WeChatTheme.colors.bubbleOthers + Text( + msg.text, + Modifier + .graphicsLayer( + rotationZ = -shakingAngleBubble.value, + transformOrigin = TransformOrigin(0f, 0f) + ) + .drawBehind { + val bubble = Path().apply { + val rect = RoundRect( + 10.dp.toPx(), + 0f, + size.width - 10.dp.toPx(), + size.height, + 4.dp.toPx(), + 4.dp.toPx() + ) + addRoundRect(rect) + moveTo(10.dp.toPx(), 15.dp.toPx()) + lineTo(5.dp.toPx(), 20.dp.toPx()) + lineTo(10.dp.toPx(), 25.dp.toPx()) + close() + } + drawPath(bubble, bubbleColor) + } + .padding(20.dp, 10.dp), + color = WeChatTheme.colors.textPrimary) + } + } +} + +@Composable +fun ChatBottomBar(onBombClicked: () -> Unit) { + var editingText by remember { mutableStateOf("") } + Row( + Modifier + .fillMaxWidth() + .background(WeChatTheme.colors.bottomBar) + .padding(4.dp, 0.dp) + ) { + Icon( + painterResource(R.drawable.ic_voice), + contentDescription = null, + Modifier + .align(Alignment.CenterVertically) + .padding(4.dp) + .size(28.dp), + tint = WeChatTheme.colors.icon + ) + BasicTextField( + editingText, { editingText = it }, + Modifier + .weight(1f) + .padding(4.dp, 8.dp) + .height(40.dp) + .clip(MaterialTheme.shapes.small) + .background(WeChatTheme.colors.textFieldBackground) + .padding(start = 8.dp, top = 10.dp, end = 8.dp), + cursorBrush = SolidColor(WeChatTheme.colors.textPrimary) + ) + Text( + "\uD83D\uDCA3", + Modifier + .clickable(onClick = onBombClicked) + .padding(4.dp) + .align(Alignment.CenterVertically), + fontSize = 24.sp + ) + Icon( + painterResource(R.drawable.ic_add), + contentDescription = null, + Modifier + .align(Alignment.CenterVertically) + .padding(4.dp) + .size(28.dp), + tint = WeChatTheme.colors.icon + ) + } +} + +fun Modifier.offsetPercent(offsetPercentX: Float = 0f, offsetPercentY: Float = 0f) = + this.layout { measurable, constraints -> + val placeable = measurable.measure(constraints) + layout(placeable.width, placeable.height) { + val offsetX = (offsetPercentX * placeable.width).roundToInt() + val offsetY = (offsetPercentY * placeable.height).roundToInt() + placeable.placeRelative(offsetX, offsetY) + } + } \ No newline at end of file diff --git a/app/src/main/java/com/example/wechat/ui/Contacts.kt b/app/src/main/java/com/example/wechat/ui/Contacts.kt new file mode 100644 index 0000000..263c25c --- /dev/null +++ b/app/src/main/java/com/example/wechat/ui/Contacts.kt @@ -0,0 +1,143 @@ +package com.example.wechat.ui + +import androidx.compose.foundation.Image +import androidx.compose.foundation.background +import androidx.compose.foundation.layout.* +import androidx.compose.foundation.lazy.LazyColumn +import androidx.compose.foundation.lazy.itemsIndexed +import androidx.compose.foundation.shape.RoundedCornerShape +import androidx.compose.material3.Text +import androidx.compose.runtime.Composable +import androidx.compose.ui.Alignment +import androidx.compose.ui.Modifier +import androidx.compose.ui.draw.clip +import androidx.compose.ui.res.painterResource +import androidx.compose.ui.tooling.preview.Preview +import androidx.compose.ui.unit.dp +import androidx.compose.ui.unit.sp +import androidx.lifecycle.viewmodel.compose.viewModel +import com.example.wechat.R +import com.example.wechat.WeViewModel +import com.example.wechat.data.User +import com.example.wechat.ui.theme.WeChatTheme + +@Composable +fun ContactListTopBar() { + WeTopBar(title = "通讯录") +} + +@Preview(showBackground = true) +@Composable +fun ContactListTopBarPreview() { + ContactListTopBar() +} + +@Composable +fun ContactListItem( + contact: User, + modifier: Modifier = Modifier, +) { + Row( + Modifier + .fillMaxWidth() + ) { + Image( + painterResource(contact.avatar), "avatar", Modifier + .padding(12.dp, 8.dp, 8.dp, 8.dp) + .size(36.dp) + .clip(RoundedCornerShape(4.dp)) + ) + Text( + contact.name, + Modifier + .weight(1f) + .align(Alignment.CenterVertically), + fontSize = 17.sp, + color = WeChatTheme.colors.textPrimary + ) + } +} + +@Composable +fun ContactList(viewModel: WeViewModel = viewModel()) { + Column(Modifier.fillMaxSize()) { + ContactListTopBar() + Box( + Modifier + .background(WeChatTheme.colors.background) + .fillMaxSize() + ) { + ContactList(viewModel.contacts) + } + } +} + +@Composable +fun ContactList(contacts: List) { + LazyColumn( + Modifier + .background(WeChatTheme.colors.listItem) + .fillMaxWidth() + ) { + val buttons = listOf( + User("contact_add", "新的朋友", R.drawable.ic_contact_add), + User("contact_chat", "仅聊天", R.drawable.ic_contact_chat), + User("contact_group", "群聊", R.drawable.ic_contact_group), + User("contact_tag", "标签", R.drawable.ic_contact_tag), + User("contact_official", "公众号", R.drawable.ic_contact_official), + ) + itemsIndexed(buttons) { index, contact -> + ContactListItem(contact) + if (index < buttons.size - 1) { + Divider( + startIndent = 56.dp, + color = WeChatTheme.colors.chatListDivider, + thickness = 0.8f.dp + ) + } + } + item { + Text( + "朋友", + Modifier + .background(WeChatTheme.colors.background) + .fillMaxWidth() + .padding(12.dp, 8.dp), + fontSize = 14.sp, + color = WeChatTheme.colors.onBackground + ) + } + itemsIndexed(contacts) { index, contact -> + ContactListItem(contact) + if (index < contacts.size - 1) { + Divider( + startIndent = 56.dp, + color = WeChatTheme.colors.chatListDivider, + thickness = 0.8f.dp + ) + } + } + } +} + +@Preview(showBackground = true) +@Composable +fun ContactListItemPreview() { + WeChatTheme { + Box { + ContactListItem( + User("jskj", "骏升科技", R.drawable.avatar_jskj) + ) + } + } +} + +@Preview(showBackground = true) +@Composable +fun ContactListPreview() { + val contacts = listOf( + User("jskj", "骏升科技", R.drawable.avatar_jskj), + User("yinzhicheng", "尹志成", R.drawable.avatar_yinzhicheng), + ) + ContactList(contacts) +} \ No newline at end of file diff --git a/app/src/main/java/com/example/wechat/ui/Discovery.kt b/app/src/main/java/com/example/wechat/ui/Discovery.kt new file mode 100644 index 0000000..1ef8fec --- /dev/null +++ b/app/src/main/java/com/example/wechat/ui/Discovery.kt @@ -0,0 +1,163 @@ +package com.example.wechat.ui + +import androidx.annotation.DrawableRes +import androidx.compose.foundation.Image +import androidx.compose.foundation.background +import androidx.compose.foundation.layout.* +import androidx.compose.foundation.shape.RoundedCornerShape +import androidx.compose.material3.Icon +import androidx.compose.material3.Text +import androidx.compose.runtime.Composable +import androidx.compose.ui.Alignment +import androidx.compose.ui.Modifier +import androidx.compose.ui.draw.clip +import androidx.compose.ui.graphics.Color +import androidx.compose.ui.res.painterResource +import androidx.compose.ui.tooling.preview.Preview +import androidx.compose.ui.unit.Dp +import androidx.compose.ui.unit.dp +import androidx.compose.ui.unit.sp +import com.example.wechat.R +import com.example.wechat.ui.theme.WeChatTheme + +@Composable +fun DiscoveryListTopBar() { + WeTopBar(title = "发现") +} + +@Preview(showBackground = true) +@Composable +fun DiscoveryListTopBarPreview() { + DiscoveryListTopBar() +} + +@Composable +fun DiscoveryListItem( + @DrawableRes icon: Int, + title: String, + modifier: Modifier = Modifier, + badge: @Composable (() -> Unit)? = null, + endBadge: @Composable (() -> Unit)? = null +) { + Row( + Modifier + .fillMaxWidth(), + verticalAlignment = Alignment.CenterVertically + ) { + Image( + painterResource(icon), "title", Modifier + .padding(12.dp, 8.dp, 8.dp, 8.dp) + .size(36.dp) + .padding(8.dp) + ) + Text( + title, + fontSize = 17.sp, + color = WeChatTheme.colors.textPrimary + ) + badge?.invoke() + Spacer(Modifier.weight(1f)) + endBadge?.invoke() + Icon( + painterResource(R.drawable.ic_arrow_more), contentDescription = "更多", + Modifier + .padding(0.dp, 0.dp, 12.dp, 0.dp) + .size(16.dp), + tint = WeChatTheme.colors.more + ) + } +} + +@Composable +fun DiscoveryList() { + Column(Modifier.fillMaxSize()) { + DiscoveryListTopBar() + Box( + Modifier + .background(WeChatTheme.colors.background) + .fillMaxSize() + ) { + Column( + Modifier + .background(WeChatTheme.colors.listItem) + .fillMaxWidth() + ) { + DiscoveryListItem(R.drawable.ic_moments, "朋友圈", badge = { + Box( + Modifier + .padding(8.dp) + .clip(RoundedCornerShape(50)) + .size(18.dp) + .background(WeChatTheme.colors.badge) + ) { + Text( + "3", + Modifier.align(Alignment.Center), + fontSize = 12.sp, + color = WeChatTheme.colors.onBadge + ) + } + }, endBadge = { + Image( + painterResource(R.drawable.avatar_jskj), "avatar", Modifier + .padding(8.dp, 0.dp) + .size(32.dp) + .unread(false, WeChatTheme.colors.badge) + .clip(RoundedCornerShape(4.dp)) + ) + }) + Spacer( + Modifier + .background(WeChatTheme.colors.background) + .fillMaxWidth() + .height(8.dp) + ) + DiscoveryListItem(R.drawable.ic_channels, "视频号", endBadge = { + Image( + painterResource(R.drawable.avatar_yinzhicheng), "avatar", Modifier + .padding(8.dp, 0.dp) + .size(32.dp) + .unread(false, WeChatTheme.colors.badge) + .clip(RoundedCornerShape(4.dp)) + ) + Text( + "赞过", Modifier.padding(0.dp, 0.dp, 4.dp, 0.dp), + fontSize = 14.sp, color = WeChatTheme.colors.textSecondary + ) + }) + Spacer( + Modifier + .background(WeChatTheme.colors.background) + .fillMaxWidth() + .height(8.dp) + ) + DiscoveryListItem(R.drawable.ic_ilook, "看一看") + Divider( + startIndent = 56.dp, + color = WeChatTheme.colors.chatListDivider, + thickness = 0.8f.dp + ) + DiscoveryListItem(R.drawable.ic_isearch, "搜一搜") + Spacer( + Modifier + .background(WeChatTheme.colors.background) + .fillMaxWidth() + .height(8.dp) + ) + DiscoveryListItem(R.drawable.ic_nearby, "直播和附近") + } + } + } +} + +fun ColumnScope.Divider(startIndent: Dp, color: Color, thickness: Dp) { + TODO("Not yet implemented") +} + +@Preview(showBackground = true) +@Composable +fun DiscoveryListPreview() { + WeChatTheme { + DiscoveryList() + } +} \ No newline at end of file diff --git a/app/src/main/java/com/example/wechat/ui/Home.kt b/app/src/main/java/com/example/wechat/ui/Home.kt new file mode 100644 index 0000000..802a2a3 --- /dev/null +++ b/app/src/main/java/com/example/wechat/ui/Home.kt @@ -0,0 +1,36 @@ +package com.example.wechat.ui + +import androidx.compose.foundation.ExperimentalFoundationApi +import androidx.compose.foundation.layout.Column +import androidx.compose.foundation.pager.HorizontalPager +import androidx.compose.foundation.pager.rememberPagerState +import androidx.compose.runtime.Composable +import androidx.compose.runtime.rememberCoroutineScope +import androidx.compose.ui.Modifier +import com.example.wechat.WeViewModel +import kotlinx.coroutines.launch + +@OptIn(ExperimentalFoundationApi::class) +@Composable +fun Home(viewModel: WeViewModel) { + Column { + val pagerState = rememberPagerState{4} + HorizontalPager( + modifier = Modifier.weight(1f), + state = pagerState + ) { page -> + when (page) { + 0 -> ChatList(viewModel.chats) + 1 -> ContactList() + 2 -> DiscoveryList() + 3 -> MeList() + } + } + val scope = rememberCoroutineScope() + WeBottomBar(pagerState.currentPage) { page -> + scope.launch { + pagerState.animateScrollToPage(page) + } + } + } +} \ No newline at end of file diff --git a/app/src/main/java/com/example/wechat/ui/MeList.kt b/app/src/main/java/com/example/wechat/ui/MeList.kt new file mode 100644 index 0000000..0955eab --- /dev/null +++ b/app/src/main/java/com/example/wechat/ui/MeList.kt @@ -0,0 +1,180 @@ +package com.example.wechat.ui + +import androidx.annotation.DrawableRes +import androidx.compose.foundation.Image +import androidx.compose.foundation.background +import androidx.compose.foundation.border +import androidx.compose.foundation.layout.* +import androidx.compose.foundation.shape.RoundedCornerShape +import androidx.compose.material3.Icon +import androidx.compose.material3.Text +import androidx.compose.runtime.Composable +import androidx.compose.ui.Alignment +import androidx.compose.ui.Modifier +import androidx.compose.ui.draw.clip +import androidx.compose.ui.res.painterResource +import androidx.compose.ui.text.font.FontWeight +import androidx.compose.ui.tooling.preview.Preview +import androidx.compose.ui.unit.dp +import androidx.compose.ui.unit.sp +import com.example.wechat.data.User +import com.example.wechat.ui.theme.WeChatTheme +import com.example.wechat.R + +@Composable +fun MeListTopBar() { + Row( + Modifier + .background(WeChatTheme.colors.listItem) + .fillMaxWidth() + .height(224.dp) + ) { + Image( + painterResource(id = R.drawable.avatar_me), contentDescription = "Me", + Modifier + .align(Alignment.CenterVertically) + .padding(start = 24.dp) + .clip(RoundedCornerShape(6.dp)) + .size(64.dp) + ) + Column( + Modifier + .weight(1f) + .padding(start = 12.dp) + ) { + Text( + User.Me.name, + Modifier.padding(top = 64.dp), + fontSize = 22.sp, + fontWeight = FontWeight.Bold, + color = WeChatTheme.colors.textPrimary + ) + Text( + "微信号:${User.Me.id}", + Modifier.padding(top = 16.dp), + fontSize = 14.sp, + color = WeChatTheme.colors.textSecondary + ) + Text( + "+ 状态", + Modifier + .padding(top = 16.dp) + .border(1.dp, WeChatTheme.colors.onBackground, RoundedCornerShape(50)) + .padding(8.dp, 2.dp), + fontSize = 16.sp, + color = WeChatTheme.colors.onBackground + ) + } + Icon( + painterResource(id = R.drawable.ic_qrcode), contentDescription = "qrcode", + Modifier + .align(Alignment.CenterVertically) + .padding(end = 20.dp) + .size(14.dp), + tint = WeChatTheme.colors.onBackground + ) + Icon( + painterResource(R.drawable.ic_arrow_more), contentDescription = "更多", + Modifier + .align(Alignment.CenterVertically) + .padding(end = 16.dp) + .size(16.dp), + tint = WeChatTheme.colors.more + ) + } +} + +@Preview(showBackground = true) +@Composable +fun MeListTopBarPreview() { + MeListTopBar() +} + +@Composable +fun MeListItem( + @DrawableRes icon: Int, + title: String, + modifier: Modifier = Modifier, + badge: @Composable (() -> Unit)? = null, + endBadge: @Composable (() -> Unit)? = null +) { + Row( + Modifier + .fillMaxWidth(), + verticalAlignment = Alignment.CenterVertically + ) { + Image( + painterResource(icon), "title", Modifier + .padding(12.dp, 8.dp, 8.dp, 8.dp) + .size(36.dp) + .padding(8.dp) + ) + Text( + title, + fontSize = 17.sp, + color = WeChatTheme.colors.textPrimary + ) + badge?.invoke() + Spacer(Modifier.weight(1f)) + endBadge?.invoke() + Icon( + painterResource(R.drawable.ic_arrow_more), contentDescription = "更多", + Modifier + .padding(0.dp, 0.dp, 12.dp, 0.dp) + .size(16.dp), + tint = WeChatTheme.colors.more + ) + } +} + +@Composable +fun MeList() { + Box( + Modifier + .background(WeChatTheme.colors.background) + .fillMaxSize() + ) { + Column( + Modifier + .background(WeChatTheme.colors.listItem) + .fillMaxWidth() + ) { + MeListTopBar() + Spacer( + Modifier + .background(WeChatTheme.colors.background) + .fillMaxWidth() + .height(8.dp) + ) + MeListItem(R.drawable.ic_pay, "支付") + Spacer( + Modifier + .background(WeChatTheme.colors.background) + .fillMaxWidth() + .height(8.dp) + ) + MeListItem(R.drawable.ic_collections, "收藏") + Divider(startIndent = 56.dp, color = WeChatTheme.colors.chatListDivider, thickness = 0.8f.dp) + MeListItem(R.drawable.ic_photos, "朋友圈") + Divider(startIndent = 56.dp, color = WeChatTheme.colors.chatListDivider, thickness = 0.8f.dp) + MeListItem(R.drawable.ic_cards, "卡包") + Divider(startIndent = 56.dp, color = WeChatTheme.colors.chatListDivider, thickness = 0.8f.dp) + MeListItem(R.drawable.ic_stickers, "表情") + Spacer( + Modifier + .background(WeChatTheme.colors.background) + .fillMaxWidth() + .height(8.dp) + ) + MeListItem(R.drawable.ic_settings, "设置") + } + } +} + +@Preview(showBackground = true) +@Composable +fun MeListPreview() { + WeChatTheme { + MeList() + } +} \ No newline at end of file diff --git a/app/src/main/java/com/example/wechat/ui/WeBottomBar.kt b/app/src/main/java/com/example/wechat/ui/WeBottomBar.kt new file mode 100644 index 0000000..b43b31b --- /dev/null +++ b/app/src/main/java/com/example/wechat/ui/WeBottomBar.kt @@ -0,0 +1,105 @@ +package com.example.wechat.ui + +import androidx.annotation.DrawableRes +import androidx.compose.foundation.background +import androidx.compose.foundation.clickable +import androidx.compose.foundation.layout.Column +import androidx.compose.foundation.layout.Row +import androidx.compose.foundation.layout.padding +import androidx.compose.foundation.layout.size +import androidx.compose.material3.Icon +import androidx.compose.material3.Text +import androidx.compose.runtime.Composable +import androidx.compose.runtime.getValue +import androidx.compose.runtime.mutableStateOf +import androidx.compose.runtime.remember +import androidx.compose.runtime.setValue +import androidx.compose.ui.Alignment +import androidx.compose.ui.Modifier +import androidx.compose.ui.graphics.Color +import androidx.compose.ui.res.painterResource +import androidx.compose.ui.tooling.preview.Preview +import androidx.compose.ui.unit.dp +import androidx.compose.ui.unit.sp +import com.example.wechat.R +import com.example.wechat.ui.theme.WeChatTheme + + +@Composable +fun WeBottomBar(selected: Int,onSelectedChanged: (Int) -> Unit){ + Row(Modifier.background(WeChatTheme.colors.bottomBar)) { + TabItem( + if(selected==0) R.drawable.ic_chat_filled else R.drawable.ic_chat_outlined,"聊天", + if(selected==0) WeChatTheme.colors.iconCurrent else WeChatTheme.colors.icon, + Modifier.weight(1f) + .clickable { + onSelectedChanged(0) + } + ) + TabItem(if(selected==1) R.drawable.ic_contacts_filled else R.drawable.ic_contacts_outlined,"通讯录", + if(selected==1) WeChatTheme.colors.iconCurrent else WeChatTheme.colors.icon, + Modifier.weight(1f) + .clickable { + onSelectedChanged(1) + } + ) + TabItem(if(selected==2) R.drawable.ic_discovery_filled else R.drawable.ic_discovery_outlined,"朋友圈", + if(selected==2) WeChatTheme.colors.iconCurrent else WeChatTheme.colors.icon, + Modifier.weight(1f) + .clickable { + onSelectedChanged(2) + } + ) + TabItem(if(selected==3) R.drawable.ic_me_filled else R.drawable.ic_me_outlined,"我", + if(selected==3) WeChatTheme.colors.iconCurrent else WeChatTheme.colors.icon, + Modifier.weight(1f) + .clickable { + onSelectedChanged(3) + } + ) + } +} + +@Composable +fun TabItem(@DrawableRes iconId :Int, title: String, tint: Color,modifier: Modifier=Modifier){ + Column( + modifier.padding(vertical = 8.dp), + horizontalAlignment = Alignment.CenterHorizontally + ) { + Icon(painterResource(iconId),title, Modifier.size(24.dp), tint = tint) + Text(title, fontSize = 11.sp, color = tint) + } +} + +@Preview(showBackground = true) +@Composable +fun WeBottomBarPreview(){ + WeChatTheme(WeChatTheme.Theme.Light){ + var selectedTab by remember{ mutableStateOf(0)} + WeBottomBar(selectedTab){ selectedTab=it } + } +} + +@Preview(showBackground = true) +@Composable +fun WeBottomBarPreviewDark() { + WeChatTheme(WeChatTheme.Theme.Dark) { + var selectedTab by remember { mutableStateOf(0) } + WeBottomBar(selectedTab) { selectedTab = it } + } +} + +@Preview(showBackground = true) +@Composable +fun WeBottomBarPreviewNewYear() { + WeChatTheme(WeChatTheme.Theme.NewYear) { + var selectedTab by remember { mutableStateOf(0) } + WeBottomBar(selectedTab) { selectedTab = it } + } +} + +@Preview(showBackground = true) +@Composable +fun TabItemPreview(){ + TabItem(iconId= R.drawable.ic_chat_outlined,title= "聊天", tint = WeChatTheme.colors.icon) +} \ No newline at end of file diff --git a/app/src/main/java/com/example/wechat/ui/WeTopBar.kt b/app/src/main/java/com/example/wechat/ui/WeTopBar.kt new file mode 100644 index 0000000..1e977f1 --- /dev/null +++ b/app/src/main/java/com/example/wechat/ui/WeTopBar.kt @@ -0,0 +1,62 @@ +package com.example.wechat.ui + +import androidx.compose.foundation.background +import androidx.compose.foundation.clickable +import androidx.compose.foundation.layout.* +import androidx.compose.material3.Icon +import androidx.compose.material3.Text +import androidx.compose.runtime.Composable +import androidx.compose.ui.Alignment +import androidx.compose.ui.Modifier +import androidx.compose.ui.res.painterResource +import androidx.compose.ui.unit.dp +import androidx.lifecycle.viewmodel.compose.viewModel +import com.example.wechat.R +import com.example.wechat.WeViewModel +import com.example.wechat.ui.theme.WeChatTheme + +@Composable +fun WeTopBar(title: String, onBack: (() -> Unit)? = null) { + Box( + Modifier + .background(WeChatTheme.colors.background) + .fillMaxWidth() + ) { + Row( + Modifier + .height(48.dp) + ) { + if (onBack != null) { + Icon( + painterResource(R.drawable.ic_back), + null, + Modifier + .clickable(onClick = onBack) + .align(Alignment.CenterVertically) + .size(36.dp) + .padding(8.dp), + tint = WeChatTheme.colors.icon + ) + } + Spacer(Modifier.weight(1f)) + val viewModel: WeViewModel = viewModel() + Icon( + painterResource(R.drawable.ic_palette), + "切换主题", + Modifier + .clickable { + viewModel.theme = when (viewModel.theme) { + WeChatTheme.Theme.Light -> WeChatTheme.Theme.Dark + WeChatTheme.Theme.Dark -> WeChatTheme.Theme.NewYear + WeChatTheme.Theme.NewYear -> WeChatTheme.Theme.Light + } + } + .align(Alignment.CenterVertically) + .size(36.dp) + .padding(8.dp), + tint = WeChatTheme.colors.icon + ) + } + Text(title, Modifier.align(Alignment.Center), color = WeChatTheme.colors.textPrimary) + } +} \ No newline at end of file diff --git a/app/src/main/java/com/example/wechat/ui/theme/Color.kt b/app/src/main/java/com/example/wechat/ui/theme/Color.kt new file mode 100644 index 0000000..958031b --- /dev/null +++ b/app/src/main/java/com/example/wechat/ui/theme/Color.kt @@ -0,0 +1,41 @@ +package com.example.wechat.ui.theme + +import androidx.compose.ui.graphics.Color +import androidx.compose.ui.graphics.colorspace.ColorSpaces + +val Purple200 = Color(0xFFBB86FC) +val Purple500 = Color(0xFF6200EE) +val Purple700 = Color(0xFF3700B3) +val Teal200 = Color(0xFF03DAC5) + +val white = Color(0xFFFFFFFF).convert(ColorSpaces.CieXyz) +val white1 = Color(0xFFF7F7F7).convert(ColorSpaces.CieXyz) +val white2 = Color(0xFFEDEDED).convert(ColorSpaces.CieXyz) +val white3 = Color(0xFFE5E5E5).convert(ColorSpaces.CieXyz) +val white4 = Color(0xFFD5D5D5).convert(ColorSpaces.CieXyz) +val white5 = Color(0xFFCCCCCC).convert(ColorSpaces.CieXyz) +val black = Color(0xFF000000).convert(ColorSpaces.CieXyz) +val black1 = Color(0xFF1E1E1E).convert(ColorSpaces.CieXyz) +val black2 = Color(0xFF111111).convert(ColorSpaces.CieXyz) +val black3 = Color(0xFF191919).convert(ColorSpaces.CieXyz) +val black4 = Color(0xFF252525).convert(ColorSpaces.CieXyz) +val black5 = Color(0xFF2C2C2C).convert(ColorSpaces.CieXyz) +val black6 = Color(0xFF07130A).convert(ColorSpaces.CieXyz) +val black7 = Color(0xFF292929).convert(ColorSpaces.CieXyz) +val grey1 = Color(0xFF888888).convert(ColorSpaces.CieXyz) +val grey2 = Color(0xFFCCC7BF).convert(ColorSpaces.CieXyz) +val grey3 = Color(0xFF767676).convert(ColorSpaces.CieXyz) +val grey4 = Color(0xFFB2B2B2).convert(ColorSpaces.CieXyz) +val grey5 = Color(0xFF5E5E5E).convert(ColorSpaces.CieXyz) +val green1 = Color(0xFFB0EB6E).convert(ColorSpaces.CieXyz) +val green2 = Color(0xFF6DB476).convert(ColorSpaces.CieXyz) +val green3 = Color(0xFF67BF63).convert(ColorSpaces.CieXyz) +val red1 = Color(0xFFDF5554).convert(ColorSpaces.CieXyz) +val red2 = Color(0xFFDD302E).convert(ColorSpaces.CieXyz) +val red3 = Color(0xFFF77B7A).convert(ColorSpaces.CieXyz) +val red4 = Color(0xFFD42220).convert(ColorSpaces.CieXyz) +val red5 = Color(0xFFC51614).convert(ColorSpaces.CieXyz) +val red6 = Color(0xFFF74D4B).convert(ColorSpaces.CieXyz) +val red7 = Color(0xFFDC514E).convert(ColorSpaces.CieXyz) +val red8 = Color(0xFFCBC7BF).convert(ColorSpaces.CieXyz) +val yellow1 = Color(0xFFF6CA23).convert(ColorSpaces.CieXyz) \ No newline at end of file diff --git a/app/src/main/java/com/example/wechat/ui/theme/Theme.kt b/app/src/main/java/com/example/wechat/ui/theme/Theme.kt new file mode 100644 index 0000000..4d07670 --- /dev/null +++ b/app/src/main/java/com/example/wechat/ui/theme/Theme.kt @@ -0,0 +1,199 @@ +package com.example.wechat.ui.theme + +import androidx.compose.animation.animateColorAsState +import androidx.compose.animation.core.TweenSpec +import androidx.compose.animation.core.animateFloatAsState +import androidx.compose.foundation.isSystemInDarkTheme +import androidx.compose.material3.MaterialTheme +import androidx.compose.material3.MaterialTheme.shapes +import androidx.compose.runtime.* +import androidx.compose.ui.graphics.Color + +private val LightColorPalette = WeChatColors( + bottomBar = white1, + background = white2, + listItem = white, + divider = white3, + chatPage = white2, + textPrimary = black3, + textPrimaryMe = black3, + textSecondary = grey1, + onBackground = grey3, + icon = black, + iconCurrent = green3, + badge = red1, + onBadge = white, + bubbleMe = green1, + bubbleOthers = white, + textFieldBackground = white, + more = grey4, + chatPageBgAlpha = 0f, +) +private val DarkColorPalette = WeChatColors( + bottomBar = black1, + background = black2, + listItem = black3, + divider = black4, + chatPage = black2, + textPrimary = white4, + textPrimaryMe = black6, + textSecondary = grey1, + onBackground = grey1, + icon = white5, + iconCurrent = green3, + badge = red1, + onBadge = white, + bubbleMe = green2, + bubbleOthers = black5, + textFieldBackground = black7, + more = grey5, + chatPageBgAlpha = 0f, +) +private val NewYearColorPalette = WeChatColors( + bottomBar = red4, + background = red5, + listItem = red2, + divider = red3, + chatPage = red5, + textPrimary = white, + textPrimaryMe = black6, + textSecondary = grey2, + onBackground = grey2, + icon = white5, + iconCurrent = green3, + badge = yellow1, + onBadge = black3, + bubbleMe = green2, + bubbleOthers = red6, + textFieldBackground = red7, + more = red8, + chatPageBgAlpha = 1f, +) + +private val LocalWeComposeColors = compositionLocalOf { + LightColorPalette +} + +object WeChatTheme { + val colors: WeChatColors + @Composable + get() = LocalWeComposeColors.current + enum class Theme { + Light, Dark, NewYear + } +} + +@Stable +class WeChatColors( + bottomBar: Color, + background: Color, + listItem: Color, + divider: Color, + chatPage: Color, + textPrimary: Color, + textPrimaryMe: Color, + textSecondary: Color, + onBackground: Color, + icon: Color, + iconCurrent: Color, + badge: Color, + onBadge: Color, + bubbleMe: Color, + bubbleOthers: Color, + textFieldBackground: Color, + more: Color, + chatPageBgAlpha: Float, +) { + var bottomBar: Color by mutableStateOf(bottomBar) + private set + var background: Color by mutableStateOf(background) + private set + var listItem: Color by mutableStateOf(listItem) + private set + var chatListDivider: Color by mutableStateOf(divider) + private set + var chatPage: Color by mutableStateOf(chatPage) + private set + var textPrimary: Color by mutableStateOf(textPrimary) + private set + var textPrimaryMe: Color by mutableStateOf(textPrimaryMe) + private set + var textSecondary: Color by mutableStateOf(textSecondary) + private set + var onBackground: Color by mutableStateOf(onBackground) + private set + var icon: Color by mutableStateOf(icon) + private set + var iconCurrent: Color by mutableStateOf(iconCurrent) + private set + var badge: Color by mutableStateOf(badge) + private set + var onBadge: Color by mutableStateOf(onBadge) + private set + var bubbleMe: Color by mutableStateOf(bubbleMe) + private set + var bubbleOthers: Color by mutableStateOf(bubbleOthers) + private set + var textFieldBackground: Color by mutableStateOf(textFieldBackground) + private set + var more: Color by mutableStateOf(more) + private set + var chatPageBgAlpha: Float by mutableStateOf(chatPageBgAlpha) + private set +} + +@Composable +fun WeChatTheme(theme: WeChatTheme.Theme = WeChatTheme.Theme.Light, content: @Composable() () -> Unit) { + val targetColors = when (theme) { + WeChatTheme.Theme.Light -> LightColorPalette + WeChatTheme.Theme.Dark -> DarkColorPalette + WeChatTheme.Theme.NewYear -> NewYearColorPalette + } + + val bottomBar = animateColorAsState(targetColors.bottomBar, TweenSpec(600)) + val background = animateColorAsState(targetColors.background, TweenSpec(600)) + val listItem = animateColorAsState(targetColors.listItem, TweenSpec(600)) + val chatListDivider = animateColorAsState(targetColors.chatListDivider, TweenSpec(600)) + val chatPage = animateColorAsState(targetColors.chatPage, TweenSpec(600)) + val textPrimary = animateColorAsState(targetColors.textPrimary, TweenSpec(600)) + val textPrimaryMe = animateColorAsState(targetColors.textPrimaryMe, TweenSpec(600)) + val textSecondary = animateColorAsState(targetColors.textSecondary, TweenSpec(600)) + val onBackground = animateColorAsState(targetColors.onBackground, TweenSpec(600)) + val icon = animateColorAsState(targetColors.icon, TweenSpec(600), label = "") + val iconCurrent = animateColorAsState(targetColors.iconCurrent, TweenSpec(600)) + val badge = animateColorAsState(targetColors.badge, TweenSpec(600)) + val onBadge = animateColorAsState(targetColors.onBadge, TweenSpec(600)) + val bubbleMe = animateColorAsState(targetColors.bubbleMe, TweenSpec(600)) + val bubbleOthers = animateColorAsState(targetColors.bubbleOthers, TweenSpec(600)) + val textFieldBackground = animateColorAsState(targetColors.textFieldBackground, TweenSpec(600)) + val more = animateColorAsState(targetColors.more, TweenSpec(600)) + val chatPageBgAlpha = animateFloatAsState(targetColors.chatPageBgAlpha, TweenSpec(600)) + + val colors = WeChatColors( + bottomBar = bottomBar.value, + background = background.value, + listItem = listItem.value, + divider = chatListDivider.value, + chatPage = chatPage.value, + textPrimary = textPrimary.value, + textPrimaryMe = textPrimaryMe.value, + textSecondary = textSecondary.value, + onBackground = onBackground.value, + icon = icon.value, + iconCurrent = iconCurrent.value, + badge = badge.value, + onBadge = onBadge.value, + bubbleMe = bubbleMe.value, + bubbleOthers = bubbleOthers.value, + textFieldBackground = textFieldBackground.value, + more = more.value, + chatPageBgAlpha = chatPageBgAlpha.value, + ) + + CompositionLocalProvider(LocalWeComposeColors provides colors) { + MaterialTheme( + shapes = shapes, + content = content + ) + } +} \ No newline at end of file diff --git a/app/src/main/java/com/example/wechat/ui/theme/Type.kt b/app/src/main/java/com/example/wechat/ui/theme/Type.kt new file mode 100644 index 0000000..80e1e26 --- /dev/null +++ b/app/src/main/java/com/example/wechat/ui/theme/Type.kt @@ -0,0 +1,34 @@ +package com.example.wechat.ui.theme + +import androidx.compose.material3.Typography +import androidx.compose.ui.text.TextStyle +import androidx.compose.ui.text.font.FontFamily +import androidx.compose.ui.text.font.FontWeight +import androidx.compose.ui.unit.sp + +// Set of Material typography styles to start with +val Typography = Typography( + bodyLarge = TextStyle( + fontFamily = FontFamily.Default, + fontWeight = FontWeight.Normal, + fontSize = 16.sp, + lineHeight = 24.sp, + letterSpacing = 0.5.sp + ) + /* Other default text styles to override + titleLarge = TextStyle( + fontFamily = FontFamily.Default, + fontWeight = FontWeight.Normal, + fontSize = 22.sp, + lineHeight = 28.sp, + letterSpacing = 0.sp + ), + labelSmall = TextStyle( + fontFamily = FontFamily.Default, + fontWeight = FontWeight.Medium, + fontSize = 11.sp, + lineHeight = 16.sp, + letterSpacing = 0.5.sp + ) + */ +) \ No newline at end of file diff --git a/app/src/main/res/drawable/avatar_jskj.jpg b/app/src/main/res/drawable/avatar_jskj.jpg new file mode 100644 index 0000000..e4cf67c Binary files /dev/null and b/app/src/main/res/drawable/avatar_jskj.jpg differ diff --git a/app/src/main/res/drawable/avatar_me.jpg b/app/src/main/res/drawable/avatar_me.jpg new file mode 100644 index 0000000..9e6bad9 Binary files /dev/null and b/app/src/main/res/drawable/avatar_me.jpg differ diff --git a/app/src/main/res/drawable/avatar_yinzhicheng.jpg b/app/src/main/res/drawable/avatar_yinzhicheng.jpg new file mode 100644 index 0000000..d1f08bd Binary files /dev/null and b/app/src/main/res/drawable/avatar_yinzhicheng.jpg differ diff --git a/app/src/main/res/drawable/ic_add.xml b/app/src/main/res/drawable/ic_add.xml new file mode 100644 index 0000000..0c1cc2b --- /dev/null +++ b/app/src/main/res/drawable/ic_add.xml @@ -0,0 +1,12 @@ + + + + + diff --git a/app/src/main/res/drawable/ic_arrow_more.xml b/app/src/main/res/drawable/ic_arrow_more.xml new file mode 100644 index 0000000..83ce609 --- /dev/null +++ b/app/src/main/res/drawable/ic_arrow_more.xml @@ -0,0 +1,4 @@ + + + diff --git a/app/src/main/res/drawable/ic_back.xml b/app/src/main/res/drawable/ic_back.xml new file mode 100644 index 0000000..1d50402 --- /dev/null +++ b/app/src/main/res/drawable/ic_back.xml @@ -0,0 +1,16 @@ + + + + diff --git a/app/src/main/res/drawable/ic_bg_newyear_left.xml b/app/src/main/res/drawable/ic_bg_newyear_left.xml new file mode 100644 index 0000000..8e8a79f --- /dev/null +++ b/app/src/main/res/drawable/ic_bg_newyear_left.xml @@ -0,0 +1,21 @@ + + + + + + + + diff --git a/app/src/main/res/drawable/ic_bg_newyear_right.xml b/app/src/main/res/drawable/ic_bg_newyear_right.xml new file mode 100644 index 0000000..7e2148f --- /dev/null +++ b/app/src/main/res/drawable/ic_bg_newyear_right.xml @@ -0,0 +1,69 @@ + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/app/src/main/res/drawable/ic_bg_newyear_top.xml b/app/src/main/res/drawable/ic_bg_newyear_top.xml new file mode 100644 index 0000000..0917d74 --- /dev/null +++ b/app/src/main/res/drawable/ic_bg_newyear_top.xml @@ -0,0 +1,120 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/app/src/main/res/drawable/ic_cards.xml b/app/src/main/res/drawable/ic_cards.xml new file mode 100644 index 0000000..6d046fe --- /dev/null +++ b/app/src/main/res/drawable/ic_cards.xml @@ -0,0 +1,5 @@ + + + + diff --git a/app/src/main/res/drawable/ic_channels.xml b/app/src/main/res/drawable/ic_channels.xml new file mode 100644 index 0000000..b3a203f --- /dev/null +++ b/app/src/main/res/drawable/ic_channels.xml @@ -0,0 +1,4 @@ + + + diff --git a/app/src/main/res/drawable/ic_chat_filled.xml b/app/src/main/res/drawable/ic_chat_filled.xml new file mode 100644 index 0000000..7a8786a --- /dev/null +++ b/app/src/main/res/drawable/ic_chat_filled.xml @@ -0,0 +1,6 @@ + + + diff --git a/app/src/main/res/drawable/ic_chat_outlined.xml b/app/src/main/res/drawable/ic_chat_outlined.xml new file mode 100644 index 0000000..e568fa7 --- /dev/null +++ b/app/src/main/res/drawable/ic_chat_outlined.xml @@ -0,0 +1,6 @@ + + + diff --git a/app/src/main/res/drawable/ic_collections.xml b/app/src/main/res/drawable/ic_collections.xml new file mode 100644 index 0000000..ce7f13f --- /dev/null +++ b/app/src/main/res/drawable/ic_collections.xml @@ -0,0 +1,12 @@ + + + + + diff --git a/app/src/main/res/drawable/ic_contact_add.xml b/app/src/main/res/drawable/ic_contact_add.xml new file mode 100644 index 0000000..c719734 --- /dev/null +++ b/app/src/main/res/drawable/ic_contact_add.xml @@ -0,0 +1,7 @@ + + + + + + diff --git a/app/src/main/res/drawable/ic_contact_chat.xml b/app/src/main/res/drawable/ic_contact_chat.xml new file mode 100644 index 0000000..5721edd --- /dev/null +++ b/app/src/main/res/drawable/ic_contact_chat.xml @@ -0,0 +1,7 @@ + + + + + + diff --git a/app/src/main/res/drawable/ic_contact_group.xml b/app/src/main/res/drawable/ic_contact_group.xml new file mode 100644 index 0000000..d0fc629 --- /dev/null +++ b/app/src/main/res/drawable/ic_contact_group.xml @@ -0,0 +1,6 @@ + + + + + diff --git a/app/src/main/res/drawable/ic_contact_official.xml b/app/src/main/res/drawable/ic_contact_official.xml new file mode 100644 index 0000000..d115abb --- /dev/null +++ b/app/src/main/res/drawable/ic_contact_official.xml @@ -0,0 +1,12 @@ + + + + + diff --git a/app/src/main/res/drawable/ic_contact_tag.xml b/app/src/main/res/drawable/ic_contact_tag.xml new file mode 100644 index 0000000..8c0bc7d --- /dev/null +++ b/app/src/main/res/drawable/ic_contact_tag.xml @@ -0,0 +1,12 @@ + + + + + diff --git a/app/src/main/res/drawable/ic_contacts_filled.xml b/app/src/main/res/drawable/ic_contacts_filled.xml new file mode 100644 index 0000000..77d95bb --- /dev/null +++ b/app/src/main/res/drawable/ic_contacts_filled.xml @@ -0,0 +1,6 @@ + + + diff --git a/app/src/main/res/drawable/ic_contacts_outlined.xml b/app/src/main/res/drawable/ic_contacts_outlined.xml new file mode 100644 index 0000000..f50e232 --- /dev/null +++ b/app/src/main/res/drawable/ic_contacts_outlined.xml @@ -0,0 +1,6 @@ + + + diff --git a/app/src/main/res/drawable/ic_discovery_filled.xml b/app/src/main/res/drawable/ic_discovery_filled.xml new file mode 100644 index 0000000..53d8980 --- /dev/null +++ b/app/src/main/res/drawable/ic_discovery_filled.xml @@ -0,0 +1,6 @@ + + + diff --git a/app/src/main/res/drawable/ic_discovery_outlined.xml b/app/src/main/res/drawable/ic_discovery_outlined.xml new file mode 100644 index 0000000..11eb137 --- /dev/null +++ b/app/src/main/res/drawable/ic_discovery_outlined.xml @@ -0,0 +1,9 @@ + + + + diff --git a/app/src/main/res/drawable/ic_ilook.xml b/app/src/main/res/drawable/ic_ilook.xml new file mode 100644 index 0000000..ff64723 --- /dev/null +++ b/app/src/main/res/drawable/ic_ilook.xml @@ -0,0 +1,6 @@ + + + diff --git a/app/src/main/res/drawable/ic_isearch.xml b/app/src/main/res/drawable/ic_isearch.xml new file mode 100644 index 0000000..4b1f0ac --- /dev/null +++ b/app/src/main/res/drawable/ic_isearch.xml @@ -0,0 +1,18 @@ + + + + + + + diff --git a/app/src/main/res/drawable/ic_launcher_background.xml b/app/src/main/res/drawable/ic_launcher_background.xml new file mode 100644 index 0000000..140f829 --- /dev/null +++ b/app/src/main/res/drawable/ic_launcher_background.xml @@ -0,0 +1,170 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/app/src/main/res/drawable/ic_launcher_foreground.xml b/app/src/main/res/drawable/ic_launcher_foreground.xml new file mode 100644 index 0000000..2b068d1 --- /dev/null +++ b/app/src/main/res/drawable/ic_launcher_foreground.xml @@ -0,0 +1,30 @@ + + + + + + + + + + + \ No newline at end of file diff --git a/app/src/main/res/drawable/ic_me_filled.xml b/app/src/main/res/drawable/ic_me_filled.xml new file mode 100644 index 0000000..a13c250 --- /dev/null +++ b/app/src/main/res/drawable/ic_me_filled.xml @@ -0,0 +1,4 @@ + + + diff --git a/app/src/main/res/drawable/ic_me_outlined.xml b/app/src/main/res/drawable/ic_me_outlined.xml new file mode 100644 index 0000000..efb6cf2 --- /dev/null +++ b/app/src/main/res/drawable/ic_me_outlined.xml @@ -0,0 +1,4 @@ + + + diff --git a/app/src/main/res/drawable/ic_moments.xml b/app/src/main/res/drawable/ic_moments.xml new file mode 100644 index 0000000..0d225ca --- /dev/null +++ b/app/src/main/res/drawable/ic_moments.xml @@ -0,0 +1,33 @@ + + + + + + + + + + + + diff --git a/app/src/main/res/drawable/ic_nearby.xml b/app/src/main/res/drawable/ic_nearby.xml new file mode 100644 index 0000000..c8a116f --- /dev/null +++ b/app/src/main/res/drawable/ic_nearby.xml @@ -0,0 +1,7 @@ + + + + + + diff --git a/app/src/main/res/drawable/ic_palette.xml b/app/src/main/res/drawable/ic_palette.xml new file mode 100644 index 0000000..d1bc262 --- /dev/null +++ b/app/src/main/res/drawable/ic_palette.xml @@ -0,0 +1,21 @@ + + + + + + + diff --git a/app/src/main/res/drawable/ic_pay.xml b/app/src/main/res/drawable/ic_pay.xml new file mode 100644 index 0000000..e275f20 --- /dev/null +++ b/app/src/main/res/drawable/ic_pay.xml @@ -0,0 +1,9 @@ + + + + diff --git a/app/src/main/res/drawable/ic_photos.xml b/app/src/main/res/drawable/ic_photos.xml new file mode 100644 index 0000000..c4ec648 --- /dev/null +++ b/app/src/main/res/drawable/ic_photos.xml @@ -0,0 +1,9 @@ + + + + diff --git a/app/src/main/res/drawable/ic_qrcode.xml b/app/src/main/res/drawable/ic_qrcode.xml new file mode 100644 index 0000000..2fe5db5 --- /dev/null +++ b/app/src/main/res/drawable/ic_qrcode.xml @@ -0,0 +1,15 @@ + + + + + + diff --git a/app/src/main/res/drawable/ic_settings.xml b/app/src/main/res/drawable/ic_settings.xml new file mode 100644 index 0000000..727d04e --- /dev/null +++ b/app/src/main/res/drawable/ic_settings.xml @@ -0,0 +1,5 @@ + + + + diff --git a/app/src/main/res/drawable/ic_stickers.xml b/app/src/main/res/drawable/ic_stickers.xml new file mode 100644 index 0000000..fd470de --- /dev/null +++ b/app/src/main/res/drawable/ic_stickers.xml @@ -0,0 +1,9 @@ + + + + diff --git a/app/src/main/res/drawable/ic_voice.xml b/app/src/main/res/drawable/ic_voice.xml new file mode 100644 index 0000000..dbb7665 --- /dev/null +++ b/app/src/main/res/drawable/ic_voice.xml @@ -0,0 +1,7 @@ + + + + + + diff --git a/app/src/main/res/mipmap-anydpi/ic_launcher.xml b/app/src/main/res/mipmap-anydpi/ic_launcher.xml new file mode 100644 index 0000000..6f3b755 --- /dev/null +++ b/app/src/main/res/mipmap-anydpi/ic_launcher.xml @@ -0,0 +1,6 @@ + + + + + + \ No newline at end of file diff --git a/app/src/main/res/mipmap-anydpi/ic_launcher_round.xml b/app/src/main/res/mipmap-anydpi/ic_launcher_round.xml new file mode 100644 index 0000000..6f3b755 --- /dev/null +++ b/app/src/main/res/mipmap-anydpi/ic_launcher_round.xml @@ -0,0 +1,6 @@ + + + + + + \ No newline at end of file diff --git a/app/src/main/res/mipmap-hdpi/ic_launcher.webp b/app/src/main/res/mipmap-hdpi/ic_launcher.webp new file mode 100644 index 0000000..c209e78 Binary files /dev/null and b/app/src/main/res/mipmap-hdpi/ic_launcher.webp differ diff --git a/app/src/main/res/mipmap-hdpi/ic_launcher_round.webp b/app/src/main/res/mipmap-hdpi/ic_launcher_round.webp new file mode 100644 index 0000000..b2dfe3d Binary files /dev/null and b/app/src/main/res/mipmap-hdpi/ic_launcher_round.webp differ diff --git a/app/src/main/res/mipmap-mdpi/ic_launcher.webp b/app/src/main/res/mipmap-mdpi/ic_launcher.webp new file mode 100644 index 0000000..4f0f1d6 Binary files /dev/null and b/app/src/main/res/mipmap-mdpi/ic_launcher.webp differ diff --git a/app/src/main/res/mipmap-mdpi/ic_launcher_round.webp b/app/src/main/res/mipmap-mdpi/ic_launcher_round.webp new file mode 100644 index 0000000..62b611d Binary files /dev/null and b/app/src/main/res/mipmap-mdpi/ic_launcher_round.webp differ diff --git a/app/src/main/res/mipmap-xhdpi/ic_launcher.webp b/app/src/main/res/mipmap-xhdpi/ic_launcher.webp new file mode 100644 index 0000000..948a307 Binary files /dev/null and b/app/src/main/res/mipmap-xhdpi/ic_launcher.webp differ diff --git a/app/src/main/res/mipmap-xhdpi/ic_launcher_round.webp b/app/src/main/res/mipmap-xhdpi/ic_launcher_round.webp new file mode 100644 index 0000000..1b9a695 Binary files /dev/null and b/app/src/main/res/mipmap-xhdpi/ic_launcher_round.webp differ diff --git a/app/src/main/res/mipmap-xxhdpi/ic_launcher.webp b/app/src/main/res/mipmap-xxhdpi/ic_launcher.webp new file mode 100644 index 0000000..28d4b77 Binary files /dev/null and b/app/src/main/res/mipmap-xxhdpi/ic_launcher.webp differ diff --git a/app/src/main/res/mipmap-xxhdpi/ic_launcher_round.webp b/app/src/main/res/mipmap-xxhdpi/ic_launcher_round.webp new file mode 100644 index 0000000..9287f50 Binary files /dev/null and b/app/src/main/res/mipmap-xxhdpi/ic_launcher_round.webp differ diff --git a/app/src/main/res/mipmap-xxxhdpi/ic_launcher.webp b/app/src/main/res/mipmap-xxxhdpi/ic_launcher.webp new file mode 100644 index 0000000..aa7d642 Binary files /dev/null and b/app/src/main/res/mipmap-xxxhdpi/ic_launcher.webp differ diff --git a/app/src/main/res/mipmap-xxxhdpi/ic_launcher_round.webp b/app/src/main/res/mipmap-xxxhdpi/ic_launcher_round.webp new file mode 100644 index 0000000..9126ae3 Binary files /dev/null and b/app/src/main/res/mipmap-xxxhdpi/ic_launcher_round.webp differ diff --git a/app/src/main/res/values/colors.xml b/app/src/main/res/values/colors.xml new file mode 100644 index 0000000..f8c6127 --- /dev/null +++ b/app/src/main/res/values/colors.xml @@ -0,0 +1,10 @@ + + + #FFBB86FC + #FF6200EE + #FF3700B3 + #FF03DAC5 + #FF018786 + #FF000000 + #FFFFFFFF + \ No newline at end of file diff --git a/app/src/main/res/values/strings.xml b/app/src/main/res/values/strings.xml new file mode 100644 index 0000000..b08f540 --- /dev/null +++ b/app/src/main/res/values/strings.xml @@ -0,0 +1,3 @@ + + WeChat + \ No newline at end of file diff --git a/app/src/main/res/values/themes.xml b/app/src/main/res/values/themes.xml new file mode 100644 index 0000000..60e932b --- /dev/null +++ b/app/src/main/res/values/themes.xml @@ -0,0 +1,5 @@ + + + +