init version

Signed-off-by: chaoq <chaoq@gxtech.ltd>
This commit is contained in:
2025-09-04 23:45:11 +08:00
parent e9ad937a4c
commit 08e7478cd1
46 changed files with 3881 additions and 1 deletions

3
app/.idea/.gitignore generated vendored Normal file
View File

@@ -0,0 +1,3 @@
# Default ignored files
/shelf/
/workspace.xml

6
app/.idea/AndroidProjectSystem.xml generated Normal file
View File

@@ -0,0 +1,6 @@
<?xml version="1.0" encoding="UTF-8"?>
<project version="4">
<component name="AndroidProjectSystem">
<option name="providerId" value="com.android.tools.idea.GradleProjectSystem" />
</component>
</project>

13
app/.idea/deviceManager.xml generated Normal file
View File

@@ -0,0 +1,13 @@
<?xml version="1.0" encoding="UTF-8"?>
<project version="4">
<component name="DeviceTable">
<option name="columnSorters">
<list>
<ColumnSorterState>
<option name="column" value="Name" />
<option name="order" value="ASCENDING" />
</ColumnSorterState>
</list>
</option>
</component>
</project>

12
app/.idea/gradle.xml generated Normal file
View File

@@ -0,0 +1,12 @@
<?xml version="1.0" encoding="UTF-8"?>
<project version="4">
<component name="GradleSettings">
<option name="linkedExternalProjectsSettings">
<GradleProjectSettings>
<option name="testRunner" value="CHOOSE_PER_TEST" />
<option name="externalProjectPath" value="$PROJECT_DIR$" />
<option name="gradleJvm" value="#GRADLE_LOCAL_JAVA_HOME" />
</GradleProjectSettings>
</option>
</component>
</project>

10
app/.idea/migrations.xml generated Normal file
View File

@@ -0,0 +1,10 @@
<?xml version="1.0" encoding="UTF-8"?>
<project version="4">
<component name="ProjectMigrations">
<option name="MigrateToGradleLocalJavaHome">
<set>
<option value="$PROJECT_DIR$" />
</set>
</option>
</component>
</project>

10
app/.idea/misc.xml generated Normal file
View File

@@ -0,0 +1,10 @@
<?xml version="1.0" encoding="UTF-8"?>
<project version="4">
<component name="ExternalStorageConfigurationManager" enabled="true" />
<component name="ProjectRootManager">
<output url="file://$PROJECT_DIR$/build/classes" />
</component>
<component name="ProjectType">
<option name="id" value="Android" />
</component>
</project>

17
app/.idea/runConfigurations.xml generated Normal file
View File

@@ -0,0 +1,17 @@
<?xml version="1.0" encoding="UTF-8"?>
<project version="4">
<component name="RunConfigurationProducerService">
<option name="ignoredProducers">
<set>
<option value="com.intellij.execution.junit.AbstractAllInDirectoryConfigurationProducer" />
<option value="com.intellij.execution.junit.AllInPackageConfigurationProducer" />
<option value="com.intellij.execution.junit.PatternConfigurationProducer" />
<option value="com.intellij.execution.junit.TestInClassConfigurationProducer" />
<option value="com.intellij.execution.junit.UniqueIdConfigurationProducer" />
<option value="com.intellij.execution.junit.testDiscovery.JUnitTestDiscoveryConfigurationProducer" />
<option value="org.jetbrains.kotlin.idea.junit.KotlinJUnitRunConfigurationProducer" />
<option value="org.jetbrains.kotlin.idea.junit.KotlinPatternConfigurationProducer" />
</set>
</option>
</component>
</project>

68
app/build.gradle Normal file
View File

@@ -0,0 +1,68 @@
plugins {
id 'com.android.application'
id 'org.jetbrains.kotlin.android'
}
android {
namespace 'com.xsynergy.android'
compileSdk 34
defaultConfig {
applicationId "com.xsynergy.android"
minSdk 26
targetSdk 34
versionCode 1
versionName "1.0"
testInstrumentationRunner "androidx.test.runner.AndroidJUnitRunner"
vectorDrawables {
useSupportLibrary true
}
}
buildTypes {
release {
minifyEnabled false
proguardFiles getDefaultProguardFile('proguard-android-optimize.txt'), 'proguard-rules.pro'
}
}
compileOptions {
sourceCompatibility JavaVersion.VERSION_17
targetCompatibility JavaVersion.VERSION_17
}
kotlinOptions {
jvmTarget = '17'
}
buildFeatures {
compose true
}
composeOptions {
kotlinCompilerExtensionVersion '1.5.14'
}
packaging {
resources {
excludes += '/META-INF/{AL2.0,LGPL2.1}'
}
}
}
dependencies {
implementation 'androidx.core:core-ktx:1.13.1'
implementation 'androidx.lifecycle:lifecycle-runtime-ktx:2.8.4'
implementation 'androidx.activity:activity-compose:1.9.1'
implementation platform('androidx.compose:compose-bom:2024.06.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.8.4'
implementation 'androidx.navigation:navigation-compose:2.7.7'
testImplementation 'junit:junit:4.13.2'
androidTestImplementation 'androidx.test.ext:junit:1.2.1'
androidTestImplementation 'androidx.test.espresso:espresso-core:3.6.1'
androidTestImplementation platform('androidx.compose:compose-bom:2024.06.00')
androidTestImplementation 'androidx.compose.ui:ui-test-junit4'
debugImplementation 'androidx.compose.ui:ui-tooling'
debugImplementation 'androidx.compose.ui:ui-test-manifest'
}

8
app/local.properties Normal file
View File

@@ -0,0 +1,8 @@
## This file must *NOT* be checked into Version Control Systems,
# as it contains information specific to your local configuration.
#
# Location of the SDK. This is only used by Gradle.
# For customization when using a Version Control System, please read the
# header note.
#Thu Sep 04 21:24:19 CST 2025
sdk.dir=/Users/qianchao/Library/Android/sdk

21
app/proguard-rules.pro vendored Normal file
View File

@@ -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

View File

@@ -0,0 +1,31 @@
<?xml version="1.0" encoding="utf-8"?>
<manifest xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:tools="http://schemas.android.com/tools">
<uses-permission android:name="android.permission.INTERNET" />
<uses-permission android:name="android.permission.READ_PHONE_STATE" />
<uses-permission android:name="android.permission.RECEIVE_SMS" />
<application
android:allowBackup="true"
android:dataExtractionRules="@xml/data_extraction_rules"
android:fullBackupContent="@xml/backup_rules"
android:icon="@mipmap/ic_launcher"
android:label="@string/app_name"
android:roundIcon="@mipmap/ic_launcher"
android:supportsRtl="true"
android:theme="@style/Theme.XSynergy"
tools:targetApi="31">
<activity
android:name=".MainActivity"
android:exported="true"
android:label="@string/app_name"
android:theme="@style/Theme.XSynergy">
<intent-filter>
<action android:name="android.intent.action.MAIN" />
<category android:name="android.intent.category.LAUNCHER" />
</intent-filter>
</activity>
</application>
</manifest>

View File

@@ -0,0 +1,38 @@
package com.xsynergy.android
import android.os.Bundle
import androidx.activity.ComponentActivity
import androidx.activity.compose.setContent
import androidx.compose.foundation.layout.*
import androidx.compose.material3.*
import androidx.compose.runtime.*
import androidx.compose.ui.Modifier
import com.xsynergy.android.ui.theme.XSynergyTheme
import com.xsynergy.android.ui.screens.LoginScreen
import com.xsynergy.android.ui.screens.MainScreen
class MainActivity : ComponentActivity() {
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
setContent {
XSynergyTheme {
Surface(
modifier = Modifier.fillMaxSize(),
color = MaterialTheme.colorScheme.background
) {
var isLoggedIn by remember { mutableStateOf(false) }
if (!isLoggedIn) {
LoginScreen(
onLoginSuccess = {
isLoggedIn = true
}
)
} else {
MainScreen()
}
}
}
}
}
}

View File

@@ -0,0 +1,30 @@
package com.xsynergy.android.ui.navigation
import androidx.compose.material.icons.Icons
import androidx.compose.material.icons.filled.*
import androidx.compose.material.icons.outlined.*
import androidx.compose.material3.*
import androidx.compose.runtime.*
import androidx.compose.ui.graphics.vector.ImageVector
sealed class BottomNavItem(
val route: String,
val title: String,
val icon: ImageVector
) {
object Dashboard : BottomNavItem("dashboard", "首页", Icons.Default.Home)
object History : BottomNavItem("history", "历史", Icons.Default.List)
object AR : BottomNavItem("ar", "AR", Icons.Default.Build)
object ARSession : BottomNavItem("arsession", "AR会话", Icons.Default.Call)
object Contacts : BottomNavItem("contacts", "联系人", Icons.Default.Person)
object Settings : BottomNavItem("settings", "设置", Icons.Default.Settings)
object Profile : BottomNavItem("profile", "我的", Icons.Default.AccountCircle)
}
val bottomNavItems = listOf(
BottomNavItem.Dashboard,
BottomNavItem.History,
BottomNavItem.ARSession,
BottomNavItem.Contacts,
BottomNavItem.Profile
)

View File

@@ -0,0 +1,237 @@
package com.xsynergy.android.ui.screens
import androidx.compose.foundation.layout.*
import androidx.compose.material.icons.Icons
import androidx.compose.material.icons.filled.*
import androidx.compose.material3.*
import androidx.compose.runtime.*
import androidx.compose.ui.Alignment
import androidx.compose.ui.Modifier
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.xsynergy.android.ui.theme.XSynergyTheme
@Composable
fun ARSessionScreen(
sessionId: String = "会话12345",
participants: List<String> = listOf("李明", "王工程师", "张主管"),
onEndSession: () -> Unit = {},
onAddAnnotation: () -> Unit = {},
onToggleAudio: () -> Unit = {},
onToggleVideo: () -> Unit = {},
onShareScreen: () -> Unit = {}
) {
Column(
modifier = Modifier
.fillMaxSize()
.padding(horizontal = 20.dp)
) {
Row(
modifier = Modifier
.fillMaxWidth()
.padding(top = 20.dp, bottom = 16.dp),
horizontalArrangement = Arrangement.SpaceBetween,
verticalAlignment = Alignment.CenterVertically
) {
Text(
text = "AR协作会话",
fontSize = 24.sp,
fontWeight = FontWeight.Bold,
color = MaterialTheme.colorScheme.onBackground
)
Row(horizontalArrangement = Arrangement.spacedBy(8.dp)) {
Text(
text = "会话ID: $sessionId",
fontSize = 14.sp,
color = MaterialTheme.colorScheme.onSurface.copy(alpha = 0.6f)
)
Surface(
color = MaterialTheme.colorScheme.error,
shape = MaterialTheme.shapes.small
) {
Text(
text = "直播中",
fontSize = 12.sp,
color = MaterialTheme.colorScheme.onError,
modifier = Modifier.padding(horizontal = 8.dp, vertical = 2.dp)
)
}
}
}
Card(
modifier = Modifier
.fillMaxWidth()
.weight(1f),
colors = CardDefaults.cardColors(
containerColor = MaterialTheme.colorScheme.surface
),
elevation = CardDefaults.cardElevation(defaultElevation = 4.dp)
) {
Box(
modifier = Modifier.fillMaxSize(),
contentAlignment = Alignment.Center
) {
Column(
horizontalAlignment = Alignment.CenterHorizontally,
verticalArrangement = Arrangement.Center
) {
Text(
text = "AR摄像头视图",
fontSize = 18.sp,
fontWeight = FontWeight.Bold,
color = MaterialTheme.colorScheme.onSurface
)
Spacer(modifier = Modifier.height(16.dp))
Text(
text = "正在显示实时AR内容",
fontSize = 14.sp,
color = MaterialTheme.colorScheme.onSurface.copy(alpha = 0.6f)
)
Spacer(modifier = Modifier.height(24.dp))
Row(
horizontalArrangement = Arrangement.spacedBy(16.dp)
) {
AssistChip(
onClick = { /* TODO: Toggle AR mode */ },
label = { Text("3D模型") },
leadingIcon = { Icon(Icons.Default.Build, contentDescription = null) }
)
AssistChip(
onClick = { /* TODO: Toggle measurement */ },
label = { Text("测量") },
leadingIcon = { Icon(Icons.Default.Edit, contentDescription = null) }
)
AssistChip(
onClick = { /* TODO: Toggle annotation */ },
label = { Text("标注") },
leadingIcon = { Icon(Icons.Default.Edit, contentDescription = null) }
)
}
}
}
}
Spacer(modifier = Modifier.height(16.dp))
Card(
modifier = Modifier.fillMaxWidth(),
colors = CardDefaults.cardColors(
containerColor = MaterialTheme.colorScheme.surface
),
elevation = CardDefaults.cardElevation(defaultElevation = 2.dp)
) {
Column(
modifier = Modifier.padding(16.dp)
) {
Text(
text = "参与者 (${participants.size})",
fontSize = 16.sp,
fontWeight = FontWeight.Bold,
color = MaterialTheme.colorScheme.onSurface
)
Spacer(modifier = Modifier.height(8.dp))
Row {
participants.forEach { participant ->
AssistChip(
onClick = { /* TODO: Focus on participant */ },
label = { Text(participant) },
modifier = Modifier.padding(end = 8.dp)
)
}
}
}
}
Spacer(modifier = Modifier.height(16.dp))
Row(
modifier = Modifier.fillMaxWidth(),
horizontalArrangement = Arrangement.SpaceEvenly
) {
IconButton(
onClick = onToggleAudio,
modifier = Modifier.size(48.dp)
) {
Icon(
imageVector = Icons.Default.Call,
contentDescription = "麦克风",
tint = MaterialTheme.colorScheme.primary
)
}
IconButton(
onClick = onToggleVideo,
modifier = Modifier.size(48.dp)
) {
Icon(
imageVector = Icons.Default.Call,
contentDescription = "摄像头",
tint = MaterialTheme.colorScheme.primary
)
}
IconButton(
onClick = onShareScreen,
modifier = Modifier.size(48.dp)
) {
Icon(
imageVector = Icons.Default.Call,
contentDescription = "屏幕共享",
tint = MaterialTheme.colorScheme.primary
)
}
IconButton(
onClick = onAddAnnotation,
modifier = Modifier.size(48.dp)
) {
Icon(
imageVector = Icons.Default.Edit,
contentDescription = "添加标注",
tint = MaterialTheme.colorScheme.primary
)
}
Button(
onClick = onEndSession,
colors = ButtonDefaults.buttonColors(
containerColor = MaterialTheme.colorScheme.error
),
modifier = Modifier.height(48.dp)
) {
Icon(
imageVector = Icons.Default.Close,
contentDescription = "结束会话",
tint = MaterialTheme.colorScheme.onError
)
Spacer(modifier = Modifier.width(8.dp))
Text("结束", color = MaterialTheme.colorScheme.onError)
}
}
Spacer(modifier = Modifier.height(20.dp))
}
}
@Preview(showBackground = true)
@Composable
fun ARSessionScreenPreview() {
XSynergyTheme {
Surface {
ARSessionScreen()
}
}
}

View File

@@ -0,0 +1,199 @@
package com.xsynergy.android.ui.screens
import androidx.compose.foundation.layout.*
import androidx.compose.foundation.lazy.LazyColumn
import androidx.compose.foundation.lazy.items
import androidx.compose.foundation.shape.CircleShape
import androidx.compose.material3.*
import androidx.compose.runtime.*
import androidx.compose.ui.Alignment
import androidx.compose.ui.Modifier
import androidx.compose.ui.draw.clip
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.xsynergy.android.ui.theme.XSynergyTheme
data class Contact(
val id: String,
val name: String,
val role: String,
val department: String,
val isOnline: Boolean
)
@Composable
fun ContactsScreen() {
val contacts = remember {
listOf(
Contact(
id = "1",
name = "王工程师",
role = "高级工程师",
department = "设备部",
isOnline = true
),
Contact(
id = "2",
name = "李设计师",
role = "UI设计师",
department = "设计部",
isOnline = true
),
Contact(
id = "3",
name = "张主管",
role = "技术主管",
department = "技术部",
isOnline = false
),
Contact(
id = "4",
name = "刘经理",
role = "项目经理",
department = "项目部",
isOnline = true
),
Contact(
id = "5",
name = "陈技术员",
role = "技术员",
department = "运维部",
isOnline = false
)
)
}
Column(
modifier = Modifier
.fillMaxSize()
.padding(horizontal = 20.dp)
) {
Text(
text = "联系人",
fontSize = 24.sp,
fontWeight = FontWeight.Bold,
color = MaterialTheme.colorScheme.onBackground,
modifier = Modifier.padding(top = 20.dp, bottom = 24.dp)
)
LazyColumn(
modifier = Modifier.fillMaxSize(),
verticalArrangement = Arrangement.spacedBy(12.dp)
) {
items(contacts) { contact ->
ContactCard(contact)
}
}
}
}
@Composable
fun ContactCard(contact: Contact) {
Card(
modifier = Modifier.fillMaxWidth(),
colors = CardDefaults.cardColors(
containerColor = MaterialTheme.colorScheme.surface
),
elevation = CardDefaults.cardElevation(defaultElevation = 2.dp)
) {
Row(
modifier = Modifier
.fillMaxWidth()
.padding(16.dp),
horizontalArrangement = Arrangement.SpaceBetween,
verticalAlignment = Alignment.CenterVertically
) {
Row(
horizontalArrangement = Arrangement.spacedBy(12.dp),
verticalAlignment = Alignment.CenterVertically
) {
Surface(
modifier = Modifier.size(48.dp),
shape = CircleShape,
color = MaterialTheme.colorScheme.primaryContainer
) {
Box(contentAlignment = Alignment.Center) {
Text(
text = contact.name.first().toString(),
fontSize = 20.sp,
fontWeight = FontWeight.Bold,
color = MaterialTheme.colorScheme.onPrimaryContainer
)
}
}
Column {
Text(
text = contact.name,
fontSize = 16.sp,
fontWeight = FontWeight.Bold,
color = MaterialTheme.colorScheme.onSurface
)
Text(
text = contact.role,
fontSize = 14.sp,
color = MaterialTheme.colorScheme.onSurface.copy(alpha = 0.6f)
)
Text(
text = contact.department,
fontSize = 12.sp,
color = MaterialTheme.colorScheme.onSurface.copy(alpha = 0.4f)
)
}
}
Row {
if (contact.isOnline) {
Surface(
color = MaterialTheme.colorScheme.secondaryContainer,
shape = MaterialTheme.shapes.small
) {
Text(
text = "在线",
fontSize = 12.sp,
color = MaterialTheme.colorScheme.onSecondaryContainer,
modifier = Modifier.padding(horizontal = 8.dp, vertical = 4.dp)
)
}
} else {
Surface(
color = MaterialTheme.colorScheme.surfaceVariant,
shape = MaterialTheme.shapes.small
) {
Text(
text = "离线",
fontSize = 12.sp,
color = MaterialTheme.colorScheme.onSurfaceVariant,
modifier = Modifier.padding(horizontal = 8.dp, vertical = 4.dp)
)
}
}
Spacer(modifier = Modifier.width(8.dp))
Button(
onClick = { /* TODO: Handle call action */ },
modifier = Modifier.height(32.dp),
colors = ButtonDefaults.buttonColors(
containerColor = MaterialTheme.colorScheme.primary,
contentColor = MaterialTheme.colorScheme.onPrimary
)
) {
Text(text = "联系", fontSize = 12.sp)
}
}
}
}
}
@Preview(showBackground = true)
@Composable
fun ContactsScreenPreview() {
XSynergyTheme {
Surface {
ContactsScreen()
}
}
}

View File

@@ -0,0 +1,234 @@
package com.xsynergy.android.ui.screens
import androidx.compose.foundation.layout.*
import androidx.compose.foundation.lazy.LazyColumn
import androidx.compose.foundation.lazy.items
import androidx.compose.material3.*
import androidx.compose.runtime.*
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.text.font.FontWeight
import androidx.compose.ui.tooling.preview.Preview
import androidx.compose.ui.unit.dp
import androidx.compose.ui.unit.sp
import com.xsynergy.android.ui.theme.XSynergyTheme
@Composable
fun DashboardScreen(
userName: String = "李明",
onStartCollaboration: () -> Unit = {},
onJoinCollaboration: () -> Unit = {},
onScheduleCollaboration: () -> Unit = {}
) {
val meetings = remember {
listOf(
Meeting(
title = "设备检修会议",
time = "14:30 - 15:30",
organizer = "王工",
status = "即将开始"
),
Meeting(
title = "项目评审会议",
time = "16:00 - 17:00",
organizer = "张总",
status = "已预约"
)
)
}
Column(
modifier = Modifier
.fillMaxSize()
.padding(horizontal = 20.dp)
) {
// Greeting
Text(
text = "你好,$userName",
fontSize = 24.sp,
fontWeight = FontWeight.Bold,
color = MaterialTheme.colorScheme.onBackground,
modifier = Modifier.padding(top = 20.dp, bottom = 32.dp)
)
// Action Section
Column(
modifier = Modifier.fillMaxWidth(),
verticalArrangement = Arrangement.spacedBy(16.dp)
) {
// Start Collaboration Button
Button(
onClick = onStartCollaboration,
modifier = Modifier
.fillMaxWidth()
.height(60.dp),
colors = ButtonDefaults.buttonColors(
containerColor = MaterialTheme.colorScheme.primary
)
) {
Text(
text = "发起协作",
fontSize = 18.sp,
fontWeight = FontWeight.Bold
)
}
// Join Collaboration Button
OutlinedButton(
onClick = onJoinCollaboration,
modifier = Modifier
.fillMaxWidth()
.height(48.dp),
colors = ButtonDefaults.outlinedButtonColors(
contentColor = MaterialTheme.colorScheme.primary
)
) {
Text(
text = "加入协作",
fontSize = 16.sp
)
}
// Schedule Collaboration Text Button
TextButton(
onClick = onScheduleCollaboration,
modifier = Modifier.align(Alignment.CenterHorizontally)
) {
Text(
text = "预约协作",
fontSize = 16.sp,
color = MaterialTheme.colorScheme.primary
)
}
}
Spacer(modifier = Modifier.height(40.dp))
// Today's Schedule Section
Text(
text = "今日预约",
fontSize = 18.sp,
fontWeight = FontWeight.Bold,
color = MaterialTheme.colorScheme.onBackground,
modifier = Modifier.padding(bottom = 16.dp)
)
// Meetings List
LazyColumn(
modifier = Modifier.fillMaxWidth(),
verticalArrangement = Arrangement.spacedBy(12.dp)
) {
items(meetings) { meeting ->
MeetingCard(meeting)
}
}
}
}
@Composable
fun MeetingCard(meeting: Meeting) {
Card(
modifier = Modifier.fillMaxWidth(),
colors = CardDefaults.cardColors(
containerColor = MaterialTheme.colorScheme.surface
),
elevation = CardDefaults.cardElevation(defaultElevation = 2.dp)
) {
Box(
modifier = Modifier.padding(16.dp)
) {
// Status Badge
if (meeting.status == "即将开始") {
Surface(
modifier = Modifier
.align(Alignment.TopEnd),
color = MaterialTheme.colorScheme.secondaryContainer,
shape = MaterialTheme.shapes.small
) {
Text(
text = meeting.status,
fontSize = 12.sp,
fontWeight = FontWeight.Medium,
color = MaterialTheme.colorScheme.onSecondaryContainer,
modifier = Modifier.padding(horizontal = 8.dp, vertical = 4.dp)
)
}
}
Column(
modifier = Modifier.fillMaxWidth(),
verticalArrangement = Arrangement.spacedBy(8.dp)
) {
// Meeting Title
Text(
text = meeting.title,
fontSize = 16.sp,
fontWeight = FontWeight.Bold,
color = MaterialTheme.colorScheme.onSurface
)
// Meeting Meta
Text(
text = "${meeting.time} | 发起人:${meeting.organizer}",
fontSize = 14.sp,
color = MaterialTheme.colorScheme.onSurface.copy(alpha = 0.6f)
)
// Join Button (only for active meetings)
if (meeting.status == "即将开始") {
Button(
onClick = { /* TODO: Handle join meeting */ },
modifier = Modifier
.fillMaxWidth()
.height(36.dp),
colors = ButtonDefaults.buttonColors(
containerColor = MaterialTheme.colorScheme.secondaryContainer,
contentColor = MaterialTheme.colorScheme.onSecondaryContainer
)
) {
Text(
text = "立即加入",
fontSize = 14.sp
)
}
}
}
}
}
}
@Preview(showBackground = true)
@Composable
fun DashboardScreenPreview() {
XSynergyTheme {
Surface {
DashboardScreen()
}
}
}
@Preview(showBackground = true)
@Composable
fun MeetingCardPreview() {
XSynergyTheme {
Surface {
MeetingCard(
Meeting(
title = "设备检修会议",
time = "14:30 - 15:30",
organizer = "王工",
status = "即将开始"
)
)
}
}
}
data class Meeting(
val title: String,
val time: String,
val organizer: String,
val status: String
)

View File

@@ -0,0 +1,310 @@
package com.xsynergy.android.ui.screens
import androidx.compose.foundation.layout.*
import androidx.compose.foundation.shape.CircleShape
import androidx.compose.material.icons.Icons
import androidx.compose.material.icons.filled.*
import androidx.compose.material3.*
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.text.font.FontWeight
import androidx.compose.ui.text.style.TextAlign
import androidx.compose.ui.tooling.preview.Preview
import androidx.compose.ui.unit.dp
import androidx.compose.ui.unit.sp
import com.xsynergy.android.ui.theme.XSynergyTheme
@Composable
fun ARScreen() {
Column(
modifier = Modifier
.fillMaxSize()
.padding(horizontal = 20.dp)
) {
Text(
text = "AR协作",
fontSize = 24.sp,
fontWeight = FontWeight.Bold,
color = MaterialTheme.colorScheme.onBackground,
modifier = Modifier.padding(top = 20.dp, bottom = 24.dp)
)
Card(
modifier = Modifier
.fillMaxWidth()
.weight(1f),
colors = CardDefaults.cardColors(
containerColor = MaterialTheme.colorScheme.surface
),
elevation = CardDefaults.cardElevation(defaultElevation = 4.dp)
) {
Column(
modifier = Modifier
.fillMaxSize()
.padding(24.dp),
horizontalAlignment = Alignment.CenterHorizontally,
verticalArrangement = Arrangement.Center
) {
Icon(
imageVector = Icons.Default.Build,
contentDescription = "AR",
modifier = Modifier.size(64.dp),
tint = MaterialTheme.colorScheme.primary
)
Spacer(modifier = Modifier.height(16.dp))
Text(
text = "AR协作功能",
fontSize = 20.sp,
fontWeight = FontWeight.Bold,
color = MaterialTheme.colorScheme.onSurface
)
Spacer(modifier = Modifier.height(8.dp))
Text(
text = "开始使用增强现实技术进行\n远程协作",
fontSize = 16.sp,
color = MaterialTheme.colorScheme.onSurface.copy(alpha = 0.6f),
textAlign = TextAlign.Center
)
Spacer(modifier = Modifier.height(24.dp))
Button(
onClick = { /* TODO: Start AR session */ },
modifier = Modifier
.fillMaxWidth()
.height(48.dp)
) {
Text("开始AR会话")
}
}
}
}
}
@Composable
fun SettingsScreen() {
Column(
modifier = Modifier
.fillMaxSize()
.padding(horizontal = 20.dp)
) {
Text(
text = "设置",
fontSize = 24.sp,
fontWeight = FontWeight.Bold,
color = MaterialTheme.colorScheme.onBackground,
modifier = Modifier.padding(top = 20.dp, bottom = 24.dp)
)
Card(
modifier = Modifier.fillMaxWidth(),
colors = CardDefaults.cardColors(
containerColor = MaterialTheme.colorScheme.surface
),
elevation = CardDefaults.cardElevation(defaultElevation = 2.dp)
) {
Column(modifier = Modifier.padding(16.dp)) {
SettingsItem("通知设置", "管理应用通知")
SettingsItem("隐私设置", "管理隐私权限")
SettingsItem("账户设置", "管理账户信息")
SettingsItem("关于应用", "版本信息和帮助")
}
}
}
}
@Composable
fun SettingsItem(title: String, description: String) {
Row(
modifier = Modifier
.fillMaxWidth()
.padding(vertical = 12.dp),
horizontalArrangement = Arrangement.SpaceBetween,
verticalAlignment = Alignment.CenterVertically
) {
Column {
Text(
text = title,
fontSize = 16.sp,
fontWeight = FontWeight.Bold,
color = MaterialTheme.colorScheme.onSurface
)
Text(
text = description,
fontSize = 14.sp,
color = MaterialTheme.colorScheme.onSurface.copy(alpha = 0.6f)
)
}
Icon(
imageVector = Icons.Default.ArrowForward,
contentDescription = null,
tint = MaterialTheme.colorScheme.onSurface.copy(alpha = 0.4f)
)
}
}
@Composable
fun ProfileScreen() {
Column(
modifier = Modifier
.fillMaxSize()
.padding(horizontal = 20.dp)
) {
Text(
text = "个人中心",
fontSize = 24.sp,
fontWeight = FontWeight.Bold,
color = MaterialTheme.colorScheme.onBackground,
modifier = Modifier.padding(top = 20.dp, bottom = 24.dp)
)
Card(
modifier = Modifier
.fillMaxWidth()
.padding(bottom = 16.dp),
colors = CardDefaults.cardColors(
containerColor = MaterialTheme.colorScheme.surface
),
elevation = CardDefaults.cardElevation(defaultElevation = 2.dp)
) {
Column(
modifier = Modifier.padding(16.dp),
horizontalAlignment = Alignment.CenterHorizontally
) {
Surface(
modifier = Modifier.size(80.dp),
color = MaterialTheme.colorScheme.primaryContainer,
shape = CircleShape
) {
Box(contentAlignment = Alignment.Center) {
Text(
text = "李明",
fontSize = 32.sp,
fontWeight = FontWeight.Bold,
color = MaterialTheme.colorScheme.onPrimaryContainer
)
}
}
Spacer(modifier = Modifier.height(12.dp))
Text(
text = "李明",
fontSize = 20.sp,
fontWeight = FontWeight.Bold,
color = MaterialTheme.colorScheme.onSurface
)
Text(
text = "高级工程师",
fontSize = 14.sp,
color = MaterialTheme.colorScheme.onSurface.copy(alpha = 0.6f)
)
Text(
text = "设备部",
fontSize = 14.sp,
color = MaterialTheme.colorScheme.onSurface.copy(alpha = 0.6f)
)
}
}
Card(
modifier = Modifier.fillMaxWidth(),
colors = CardDefaults.cardColors(
containerColor = MaterialTheme.colorScheme.surface
),
elevation = CardDefaults.cardElevation(defaultElevation = 2.dp)
) {
Column(
modifier = Modifier.padding(16.dp)
) {
Text(
text = "个人信息",
fontSize = 16.sp,
fontWeight = FontWeight.Bold,
color = MaterialTheme.colorScheme.onSurface,
modifier = Modifier.padding(bottom = 8.dp)
)
ProfileItem("手机号", "138****1234")
ProfileItem("邮箱", "liming@example.com")
ProfileItem("工号", "A001234")
ProfileItem("部门", "设备部")
ProfileItem("职位", "高级工程师")
}
}
Spacer(modifier = Modifier.height(16.dp))
Button(
onClick = { /* TODO: Handle logout */ },
modifier = Modifier
.fillMaxWidth()
.height(48.dp),
colors = ButtonDefaults.buttonColors(
containerColor = MaterialTheme.colorScheme.error
)
) {
Text(text = "退出登录", fontSize = 16.sp)
}
}
}
@Composable
fun ProfileItem(label: String, value: String) {
Row(
modifier = Modifier
.fillMaxWidth()
.padding(vertical = 8.dp),
horizontalArrangement = Arrangement.SpaceBetween
) {
Text(
text = label,
fontSize = 14.sp,
color = MaterialTheme.colorScheme.onSurface.copy(alpha = 0.6f)
)
Text(
text = value,
fontSize = 14.sp,
color = MaterialTheme.colorScheme.onSurface
)
}
}
@Preview(showBackground = true)
@Composable
fun ARScreenPreview() {
XSynergyTheme {
Surface {
ARScreen()
}
}
}
@Preview(showBackground = true)
@Composable
fun SettingsScreenPreview() {
XSynergyTheme {
Surface {
SettingsScreen()
}
}
}
@Preview(showBackground = true)
@Composable
fun ProfileScreenPreview() {
XSynergyTheme {
Surface {
ProfileScreen()
}
}
}

View File

@@ -0,0 +1,147 @@
package com.xsynergy.android.ui.screens
import androidx.compose.foundation.layout.*
import androidx.compose.foundation.lazy.LazyColumn
import androidx.compose.material3.*
import androidx.compose.runtime.*
import androidx.compose.ui.Alignment
import androidx.compose.ui.Modifier
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.xsynergy.android.ui.theme.XSynergyTheme
import java.text.SimpleDateFormat
import java.util.*
data class HistoryItem(
val id: String,
val title: String,
val date: Date,
val duration: String,
val participants: List<String>
)
@Composable
fun HistoryScreen() {
val historyItems = remember {
listOf(
HistoryItem(
id = "1",
title = "设备检修会议",
date = Calendar.getInstance().apply {
set(Calendar.DAY_OF_MONTH, 15)
set(Calendar.MONTH, Calendar.SEPTEMBER)
set(Calendar.YEAR, 2024)
}.time,
duration = "45分钟",
participants = listOf("王工", "李工程师", "张主管")
),
HistoryItem(
id = "2",
title = "项目评审会议",
date = Calendar.getInstance().apply {
set(Calendar.DAY_OF_MONTH, 14)
set(Calendar.MONTH, Calendar.SEPTEMBER)
set(Calendar.YEAR, 2024)
}.time,
duration = "60分钟",
participants = listOf("张总", "王工程师", "李设计师")
),
HistoryItem(
id = "3",
title = "技术培训",
date = Calendar.getInstance().apply {
set(Calendar.DAY_OF_MONTH, 13)
set(Calendar.MONTH, Calendar.SEPTEMBER)
set(Calendar.YEAR, 2024)
}.time,
duration = "90分钟",
participants = listOf("全体成员")
)
)
}
Column(
modifier = Modifier
.fillMaxSize()
.padding(horizontal = 20.dp)
) {
Text(
text = "历史记录",
fontSize = 24.sp,
fontWeight = FontWeight.Bold,
color = MaterialTheme.colorScheme.onBackground,
modifier = Modifier.padding(top = 20.dp, bottom = 24.dp)
)
LazyColumn(
modifier = Modifier.fillMaxSize(),
verticalArrangement = Arrangement.spacedBy(12.dp)
) {
items(historyItems.size) { index ->
HistoryCard(historyItems[index])
}
}
}
}
@Composable
fun HistoryCard(item: HistoryItem) {
val dateFormat = SimpleDateFormat("yyyy-MM-dd HH:mm", Locale.getDefault())
Card(
modifier = Modifier.fillMaxWidth(),
colors = CardDefaults.cardColors(
containerColor = MaterialTheme.colorScheme.surface
),
elevation = CardDefaults.cardElevation(defaultElevation = 2.dp)
) {
Column(
modifier = Modifier
.fillMaxWidth()
.padding(16.dp),
verticalArrangement = Arrangement.spacedBy(8.dp)
) {
Text(
text = item.title,
fontSize = 16.sp,
fontWeight = FontWeight.Bold,
color = MaterialTheme.colorScheme.onSurface
)
Text(
text = dateFormat.format(item.date),
fontSize = 14.sp,
color = MaterialTheme.colorScheme.onSurface.copy(alpha = 0.6f)
)
Row(
modifier = Modifier.fillMaxWidth(),
horizontalArrangement = Arrangement.SpaceBetween
) {
Text(
text = "时长: ${item.duration}",
fontSize = 14.sp,
color = MaterialTheme.colorScheme.onSurface.copy(alpha = 0.6f)
)
Text(
text = "参与: ${item.participants.joinToString(", ")}",
fontSize = 14.sp,
color = MaterialTheme.colorScheme.onSurface.copy(alpha = 0.6f)
)
}
}
}
}
@Preview(showBackground = true)
@Composable
fun HistoryScreenPreview() {
XSynergyTheme {
Surface {
HistoryScreen()
}
}
}

View File

@@ -0,0 +1,193 @@
package com.xsynergy.android.ui.screens
import androidx.compose.foundation.layout.*
import androidx.compose.foundation.text.KeyboardOptions
import androidx.compose.material3.*
import androidx.compose.runtime.*
import androidx.compose.ui.Alignment
import androidx.compose.ui.Modifier
import androidx.compose.ui.platform.LocalContext
import androidx.compose.ui.res.stringResource
import androidx.compose.ui.text.input.KeyboardType
import androidx.compose.ui.text.style.TextAlign
import androidx.compose.ui.unit.dp
import androidx.compose.ui.tooling.preview.Preview
import androidx.lifecycle.viewmodel.compose.viewModel
import com.xsynergy.android.R
import com.xsynergy.android.ui.theme.XSynergyTheme
import com.xsynergy.android.viewmodel.LoginViewModel
@Composable
fun LoginScreen(
viewModel: LoginViewModel = viewModel(),
onLoginSuccess: () -> Unit = {}
) {
var phoneNumber by remember { mutableStateOf(viewModel.phoneNumber) }
var verificationCode by remember { mutableStateOf(viewModel.verificationCode) }
var rememberMe by remember { mutableStateOf(viewModel.rememberMe) }
val isCodeSent by remember { derivedStateOf { viewModel.isCodeSent } }
val isLoading by remember { derivedStateOf { viewModel.isLoading } }
val errorMessage by remember { derivedStateOf { viewModel.errorMessage } }
val countdown by remember { derivedStateOf { viewModel.countdown } }
// Sync state with ViewModel
LaunchedEffect(phoneNumber) {
viewModel.phoneNumber = phoneNumber
}
LaunchedEffect(verificationCode) {
viewModel.verificationCode = verificationCode
}
LaunchedEffect(rememberMe) {
viewModel.rememberMe = rememberMe
}
// Show error message
errorMessage?.let { message ->
LaunchedEffect(message) {
// You could show a Snackbar here
viewModel.clearError()
}
}
Column(
modifier = Modifier
.fillMaxSize()
.padding(horizontal = 32.dp),
horizontalAlignment = Alignment.CenterHorizontally,
verticalArrangement = Arrangement.Center
) {
// Logo
Text(
text = "XSynergy",
style = MaterialTheme.typography.titleLarge,
color = MaterialTheme.colorScheme.primary,
modifier = Modifier.padding(bottom = 60.dp)
)
// Error message
errorMessage?.let { message ->
Text(
text = message,
color = MaterialTheme.colorScheme.error,
modifier = Modifier.padding(bottom = 16.dp),
textAlign = TextAlign.Center
)
}
// Phone Number Input
OutlinedTextField(
value = phoneNumber,
onValueChange = {
if (it.length <= 11) phoneNumber = it
},
label = { Text("手机号") },
placeholder = { Text("请输入手机号") },
keyboardOptions = KeyboardOptions(keyboardType = KeyboardType.Phone),
modifier = Modifier
.fillMaxWidth()
.padding(bottom = 16.dp),
singleLine = true,
enabled = !isLoading
)
// Verification Code Input
Row(
modifier = Modifier.fillMaxWidth(),
horizontalArrangement = Arrangement.spacedBy(12.dp)
) {
OutlinedTextField(
value = verificationCode,
onValueChange = {
if (it.length <= 6) verificationCode = it
},
label = { Text("验证码") },
placeholder = { Text("请输入验证码") },
keyboardOptions = KeyboardOptions(keyboardType = KeyboardType.Number),
modifier = Modifier.weight(1f),
singleLine = true,
enabled = !isLoading
)
Button(
onClick = {
viewModel.sendVerificationCode()
},
modifier = Modifier.height(56.dp),
enabled = phoneNumber.isNotEmpty() && !isLoading && countdown == 0
) {
Text(
if (countdown > 0) "${countdown}s"
else if (isCodeSent) "重新获取"
else "获取验证码"
)
}
}
Spacer(modifier = Modifier.height(32.dp))
// Login Button
Button(
onClick = {
viewModel.login(onLoginSuccess)
},
modifier = Modifier
.fillMaxWidth()
.height(48.dp),
enabled = phoneNumber.isNotEmpty() && verificationCode.isNotEmpty() && !isLoading
) {
if (isLoading) {
CircularProgressIndicator(
modifier = Modifier.size(24.dp),
color = MaterialTheme.colorScheme.onPrimary
)
} else {
Text("登录")
}
}
Spacer(modifier = Modifier.height(16.dp))
// Remember Me Checkbox
Row(
modifier = Modifier.fillMaxWidth(),
verticalAlignment = Alignment.CenterVertically
) {
Checkbox(
checked = rememberMe,
onCheckedChange = { rememberMe = it },
enabled = !isLoading
)
Text("记住我")
}
Spacer(modifier = Modifier.height(16.dp))
// Links
Row(
modifier = Modifier.fillMaxWidth(),
horizontalArrangement = Arrangement.SpaceBetween
) {
TextButton(
onClick = { /* TODO: Forgot password */ },
enabled = !isLoading
) {
Text("忘记密码")
}
TextButton(
onClick = { /* TODO: SSO login */ },
enabled = !isLoading
) {
Text("SSO登录")
}
}
}
}
@Preview(showBackground = true)
@Composable
fun LoginScreenPreview() {
XSynergyTheme {
LoginScreen()
}
}

View File

@@ -0,0 +1,100 @@
package com.xsynergy.android.ui.screens
import androidx.compose.foundation.layout.*
import androidx.compose.foundation.clickable
import androidx.compose.material3.*
import androidx.compose.runtime.*
import androidx.compose.ui.Alignment
import androidx.compose.ui.Modifier
import androidx.compose.ui.unit.dp
import androidx.compose.ui.unit.sp
import androidx.navigation.compose.NavHost
import androidx.navigation.compose.composable
import androidx.navigation.compose.currentBackStackEntryAsState
import androidx.navigation.compose.rememberNavController
import com.xsynergy.android.ui.navigation.BottomNavItem
import com.xsynergy.android.ui.navigation.bottomNavItems
@Composable
fun MainScreen() {
val navController = rememberNavController()
Scaffold(
bottomBar = {
BottomAppBar(
containerColor = MaterialTheme.colorScheme.surface,
tonalElevation = 8.dp
) {
val navBackStackEntry by navController.currentBackStackEntryAsState()
val currentRoute = navBackStackEntry?.destination?.route
Row(
modifier = Modifier
.fillMaxWidth()
.padding(horizontal = 8.dp),
horizontalArrangement = Arrangement.SpaceEvenly,
verticalAlignment = Alignment.CenterVertically
) {
bottomNavItems.forEach { item ->
Column(
modifier = Modifier
.clickable {
navController.navigate(item.route) {
popUpTo(navController.graph.startDestinationId) {
saveState = true
}
launchSingleTop = true
restoreState = true
}
}
.padding(vertical = 8.dp),
horizontalAlignment = Alignment.CenterHorizontally
) {
Icon(
imageVector = item.icon,
contentDescription = item.title,
tint = if (currentRoute == item.route)
MaterialTheme.colorScheme.primary
else
MaterialTheme.colorScheme.onSurface.copy(alpha = 0.6f)
)
Text(
text = item.title,
fontSize = 12.sp,
color = if (currentRoute == item.route)
MaterialTheme.colorScheme.primary
else
MaterialTheme.colorScheme.onSurface.copy(alpha = 0.6f)
)
}
}
}
}
}
) { paddingValues ->
NavHost(
navController = navController,
startDestination = BottomNavItem.Dashboard.route,
modifier = Modifier.padding(paddingValues)
) {
composable(BottomNavItem.Dashboard.route) {
DashboardScreen()
}
composable(BottomNavItem.History.route) {
HistoryScreen()
}
composable(BottomNavItem.ARSession.route) {
ARSessionScreen()
}
composable(BottomNavItem.Contacts.route) {
ContactsScreen()
}
composable(BottomNavItem.Settings.route) {
SettingsScreen()
}
composable(BottomNavItem.Profile.route) {
ProfileScreen()
}
}
}
}

View File

@@ -0,0 +1,20 @@
package com.xsynergy.android.ui.theme
import androidx.compose.ui.graphics.Color
// Primary colors from UI design
val Primary = Color(0xFF0A7CFF)
val Secondary = Color(0xFFF0F8FF)
val Accent = Color(0xFFFFD700)
val Danger = Color(0xFFFF4D4F)
val Success = Color(0xFF52C41A)
// Text colors
val TextPrimary = Color(0xFF333333)
val TextSecondary = Color(0xFF888888)
val TextWhite = Color(0xFFFFFFFF)
// Background colors
val Background = Color(0xFFF5F5F5)
val Surface = Color(0xFFFFFFFF)
val SurfaceVariant = Color(0xFFF8F9FA)

View File

@@ -0,0 +1,53 @@
package com.xsynergy.android.ui.theme
import androidx.compose.foundation.isSystemInDarkTheme
import androidx.compose.material3.MaterialTheme
import androidx.compose.material3.darkColorScheme
import androidx.compose.material3.lightColorScheme
import androidx.compose.runtime.Composable
import androidx.compose.ui.graphics.Color
private val DarkColorScheme = darkColorScheme(
primary = Primary,
secondary = Secondary,
tertiary = Accent,
onPrimary = TextWhite,
onSecondary = TextPrimary,
background = Color(0xFF1C1C1C),
onBackground = TextWhite,
surface = Color(0xFF2C2C2C),
onSurface = TextWhite,
error = Danger,
onError = TextWhite
)
private val LightColorScheme = lightColorScheme(
primary = Primary,
secondary = Secondary,
tertiary = Accent,
onPrimary = TextWhite,
onSecondary = TextPrimary,
background = Background,
onBackground = TextPrimary,
surface = Surface,
onSurface = TextPrimary,
error = Danger,
onError = TextWhite
)
@Composable
fun XSynergyTheme(
darkTheme: Boolean = isSystemInDarkTheme(),
content: @Composable () -> Unit
) {
val colorScheme = when {
darkTheme -> DarkColorScheme
else -> LightColorScheme
}
MaterialTheme(
colorScheme = colorScheme,
typography = Typography,
content = content
)
}

View File

@@ -0,0 +1,32 @@
package com.xsynergy.android.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
),
titleLarge = TextStyle(
fontFamily = FontFamily.Default,
fontWeight = FontWeight.Bold,
fontSize = 32.sp,
lineHeight = 40.sp,
letterSpacing = 0.sp
),
labelSmall = TextStyle(
fontFamily = FontFamily.Default,
fontWeight = FontWeight.Medium,
fontSize = 11.sp,
lineHeight = 16.sp,
letterSpacing = 0.5.sp
)
)

View File

@@ -0,0 +1,93 @@
package com.xsynergy.android.viewmodel
import androidx.compose.runtime.getValue
import androidx.compose.runtime.mutableStateOf
import androidx.compose.runtime.setValue
import androidx.lifecycle.ViewModel
import androidx.lifecycle.viewModelScope
import kotlinx.coroutines.delay
import kotlinx.coroutines.launch
class LoginViewModel : ViewModel() {
var phoneNumber by mutableStateOf("")
var verificationCode by mutableStateOf("")
var isCodeSent by mutableStateOf(false)
var rememberMe by mutableStateOf(false)
var isLoading by mutableStateOf(false)
var errorMessage by mutableStateOf<String?>(null)
var countdown by mutableStateOf(0)
fun sendVerificationCode() {
if (phoneNumber.isEmpty() || !isValidPhoneNumber(phoneNumber)) {
errorMessage = "请输入有效的手机号"
return
}
isLoading = true
viewModelScope.launch {
try {
// Simulate network delay
delay(1000)
// TODO: Implement actual Firebase SMS verification
isCodeSent = true
errorMessage = null
startCountdown()
} catch (e: Exception) {
errorMessage = "发送验证码失败,请重试"
} finally {
isLoading = false
}
}
}
fun login(onLoginSuccess: () -> Unit) {
if (phoneNumber.isEmpty() || !isValidPhoneNumber(phoneNumber)) {
errorMessage = "请输入有效的手机号"
return
}
if (verificationCode.isEmpty() || verificationCode.length != 6) {
errorMessage = "请输入6位验证码"
return
}
isLoading = true
viewModelScope.launch {
try {
// Simulate network delay
delay(1500)
// TODO: Implement actual Firebase verification
if (verificationCode == "123456") { // Mock verification
errorMessage = null
onLoginSuccess()
} else {
errorMessage = "验证码错误"
}
} catch (e: Exception) {
errorMessage = "登录失败,请重试"
} finally {
isLoading = false
}
}
}
private fun startCountdown() {
countdown = 60
viewModelScope.launch {
while (countdown > 0) {
delay(1000)
countdown--
}
}
}
private fun isValidPhoneNumber(phone: String): Boolean {
return phone.matches(Regex("^1[3-9]\\d{9}$"))
}
fun clearError() {
errorMessage = null
}
}

View File

@@ -0,0 +1 @@
iVBORw0KGgoAAAANSUhEUgAAAAEAAAABCAYAAAAfFcSJAAAADUlEQVR42mNkYPhfDwAChwGA60e6kgAAAABJRU5ErkJggg==

View File

@@ -0,0 +1 @@
iVBORw0KGgoAAAANSUhEUgAAAAEAAAABCAYAAAAfFcSJAAAADUlEQVR42mNkYPhfDwAChwGA60e6kgAAAABJRU5ErkJggg==

View File

@@ -0,0 +1 @@
iVBORw0KGgoAAAANSUhEUgAAAAEAAAABCAYAAAAfFcSJAAAADUlEQVR42mNkYPhfDwAChwGA60e6kgAAAABJRU5ErkJggg==

View File

@@ -0,0 +1 @@
iVBORw0KGgoAAAANSUhEUgAAAAEAAAABCAYAAAAfFcSJAAAADUlEQVR42mNkYPhfDwAChwGA60e6kgAAAABJRU5ErkJggg==

View File

@@ -0,0 +1 @@
iVBORw0KGgoAAAANSUhEUgAAAAEAAAABCAYAAAAfFcSJAAAADUlEQVR42mNkYPhfDwAChwGA60e6kgAAAABJRU5ErkJggg==

View File

@@ -0,0 +1,18 @@
<?xml version="1.0" encoding="utf-8"?>
<resources>
<color name="primary">#0A7CFF</color>
<color name="primary_dark">#0066CC</color>
<color name="primary_light">#E6F2FF</color>
<color name="secondary">#F0F8FF</color>
<color name="secondary_dark">#B3D9FF</color>
<color name="accent">#FFD700</color>
<color name="danger">#FF4D4F</color>
<color name="success">#52C41A</color>
<color name="text_primary">#333333</color>
<color name="text_secondary">#888888</color>
<color name="text_white">#FFFFFF</color>
<color name="background">#F5F5F5</color>
<color name="surface">#FFFFFF</color>
<color name="white">#FFFFFF</color>
<color name="black">#000000</color>
</resources>

View File

@@ -0,0 +1,16 @@
<?xml version="1.0" encoding="utf-8"?>
<resources>
<string name="app_name">XSynergy</string>
<string name="login_title">登录</string>
<string name="phone_number_hint">请输入手机号</string>
<string name="verification_code_hint">请输入验证码</string>
<string name="get_verification_code">获取验证码</string>
<string name="resend_verification_code">重新获取</string>
<string name="login">登录</string>
<string name="remember_me">记住我</string>
<string name="forgot_password">忘记密码</string>
<string name="sso_login">SSO登录</string>
<string name="invalid_phone_number">请输入有效的手机号</string>
<string name="invalid_verification_code">请输入有效的验证码</string>
<string name="verification_code_sent">验证码已发送</string>
</resources>

View File

@@ -0,0 +1,12 @@
<?xml version="1.0" encoding="utf-8"?>
<resources xmlns:tools="http://schemas.android.com/tools">
<style name="Theme.XSynergy" parent="android:Theme.Material.Light.NoActionBar">
<!-- Primary brand color. -->
<item name="android:colorPrimary">@color/primary</item>
<item name="android:colorPrimaryDark">@color/primary_dark</item>
<item name="android:colorAccent">@color/primary</item>
<item name="android:windowBackground">@color/background</item>
<item name="android:statusBarColor">@color/primary_dark</item>
<item name="android:windowLightStatusBar">true</item>
</style>
</resources>

View File

@@ -0,0 +1,4 @@
<?xml version="1.0" encoding="utf-8"?>
<full-backup-content>
<exclude domain="sharedpref" path="login_preferences.xml"/>
</full-backup-content>

View File

@@ -0,0 +1,10 @@
<?xml version="1.0" encoding="utf-8"?>
<data-extraction-rules>
<locale-config allowBackup="true"/>
<cloud-backup>
<exclude domain="sharedpref" path="login_preferences.xml"/>
</cloud-backup>
<device-transfer>
<exclude domain="sharedpref" path="login_preferences.xml"/>
</device-transfer>
</data-extraction-rules>