(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
+ }
+}
\ No newline at end of file
diff --git a/app/src/main/res/mipmap-hdpi/ic_launcher.png b/app/src/main/res/mipmap-hdpi/ic_launcher.png
new file mode 100644
index 0000000..9055871
--- /dev/null
+++ b/app/src/main/res/mipmap-hdpi/ic_launcher.png
@@ -0,0 +1 @@
+iVBORw0KGgoAAAANSUhEUgAAAAEAAAABCAYAAAAfFcSJAAAADUlEQVR42mNkYPhfDwAChwGA60e6kgAAAABJRU5ErkJggg==
\ No newline at end of file
diff --git a/app/src/main/res/mipmap-mdpi/ic_launcher.png b/app/src/main/res/mipmap-mdpi/ic_launcher.png
new file mode 100644
index 0000000..9055871
--- /dev/null
+++ b/app/src/main/res/mipmap-mdpi/ic_launcher.png
@@ -0,0 +1 @@
+iVBORw0KGgoAAAANSUhEUgAAAAEAAAABCAYAAAAfFcSJAAAADUlEQVR42mNkYPhfDwAChwGA60e6kgAAAABJRU5ErkJggg==
\ No newline at end of file
diff --git a/app/src/main/res/mipmap-xhdpi/ic_launcher.png b/app/src/main/res/mipmap-xhdpi/ic_launcher.png
new file mode 100644
index 0000000..9055871
--- /dev/null
+++ b/app/src/main/res/mipmap-xhdpi/ic_launcher.png
@@ -0,0 +1 @@
+iVBORw0KGgoAAAANSUhEUgAAAAEAAAABCAYAAAAfFcSJAAAADUlEQVR42mNkYPhfDwAChwGA60e6kgAAAABJRU5ErkJggg==
\ No newline at end of file
diff --git a/app/src/main/res/mipmap-xxhdpi/ic_launcher.png b/app/src/main/res/mipmap-xxhdpi/ic_launcher.png
new file mode 100644
index 0000000..9055871
--- /dev/null
+++ b/app/src/main/res/mipmap-xxhdpi/ic_launcher.png
@@ -0,0 +1 @@
+iVBORw0KGgoAAAANSUhEUgAAAAEAAAABCAYAAAAfFcSJAAAADUlEQVR42mNkYPhfDwAChwGA60e6kgAAAABJRU5ErkJggg==
\ No newline at end of file
diff --git a/app/src/main/res/mipmap-xxxhdpi/ic_launcher.png b/app/src/main/res/mipmap-xxxhdpi/ic_launcher.png
new file mode 100644
index 0000000..9055871
--- /dev/null
+++ b/app/src/main/res/mipmap-xxxhdpi/ic_launcher.png
@@ -0,0 +1 @@
+iVBORw0KGgoAAAANSUhEUgAAAAEAAAABCAYAAAAfFcSJAAAADUlEQVR42mNkYPhfDwAChwGA60e6kgAAAABJRU5ErkJggg==
\ No newline at end of file
diff --git a/app/src/main/res/values/colors.xml b/app/src/main/res/values/colors.xml
new file mode 100644
index 0000000..cebf086
--- /dev/null
+++ b/app/src/main/res/values/colors.xml
@@ -0,0 +1,18 @@
+
+
+ #0A7CFF
+ #0066CC
+ #E6F2FF
+ #F0F8FF
+ #B3D9FF
+ #FFD700
+ #FF4D4F
+ #52C41A
+ #333333
+ #888888
+ #FFFFFF
+ #F5F5F5
+ #FFFFFF
+ #FFFFFF
+ #000000
+
\ 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..240c039
--- /dev/null
+++ b/app/src/main/res/values/strings.xml
@@ -0,0 +1,16 @@
+
+
+ XSynergy
+ 登录
+ 请输入手机号
+ 请输入验证码
+ 获取验证码
+ 重新获取
+ 登录
+ 记住我
+ 忘记密码
+ SSO登录
+ 请输入有效的手机号
+ 请输入有效的验证码
+ 验证码已发送
+
\ 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..0d152ce
--- /dev/null
+++ b/app/src/main/res/values/themes.xml
@@ -0,0 +1,12 @@
+
+
+
+
\ No newline at end of file
diff --git a/app/src/main/res/xml/backup_rules.xml b/app/src/main/res/xml/backup_rules.xml
new file mode 100644
index 0000000..818c93d
--- /dev/null
+++ b/app/src/main/res/xml/backup_rules.xml
@@ -0,0 +1,4 @@
+
+
+
+
\ No newline at end of file
diff --git a/app/src/main/res/xml/data_extraction_rules.xml b/app/src/main/res/xml/data_extraction_rules.xml
new file mode 100644
index 0000000..f2e32b3
--- /dev/null
+++ b/app/src/main/res/xml/data_extraction_rules.xml
@@ -0,0 +1,10 @@
+
+
+
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/build.gradle b/build.gradle
new file mode 100644
index 0000000..03a1bed
--- /dev/null
+++ b/build.gradle
@@ -0,0 +1,23 @@
+buildscript {
+ ext.kotlin_version = '1.9.24'
+ ext.compose_version = '1.6.8'
+ repositories {
+ google()
+ mavenCentral()
+ }
+ dependencies {
+ classpath 'com.android.tools.build:gradle:8.5.0'
+ classpath "org.jetbrains.kotlin:kotlin-gradle-plugin:$kotlin_version"
+ }
+}
+
+allprojects {
+ repositories {
+ google()
+ mavenCentral()
+ }
+}
+
+task clean(type: Delete) {
+ delete rootProject.buildDir
+}
\ No newline at end of file
diff --git a/gradle.properties b/gradle.properties
new file mode 100644
index 0000000..940fc12
--- /dev/null
+++ b/gradle.properties
@@ -0,0 +1,13 @@
+# Project-wide Gradle settings.
+org.gradle.jvmargs=-Xmx2048m -Dfile.encoding=UTF-8
+org.gradle.daemon=true
+org.gradle.parallel=true
+org.gradle.configureondemand=false
+
+# AndroidX package structure to make it clearer which packages are bundled with the
+# Android operating system, and which are packaged with your app's APK
+android.useAndroidX=true
+android.enableJetifier=true
+
+# Kotlin code style for this project: "official" or "obsolete":
+kotlin.code.style=official
\ No newline at end of file
diff --git a/gradle/wrapper/gradle-wrapper.jar b/gradle/wrapper/gradle-wrapper.jar
new file mode 100644
index 0000000..8bdaf60
Binary files /dev/null and b/gradle/wrapper/gradle-wrapper.jar differ
diff --git a/gradle/wrapper/gradle-wrapper.properties b/gradle/wrapper/gradle-wrapper.properties
new file mode 100644
index 0000000..b82aa23
--- /dev/null
+++ b/gradle/wrapper/gradle-wrapper.properties
@@ -0,0 +1,7 @@
+distributionBase=GRADLE_USER_HOME
+distributionPath=wrapper/dists
+distributionUrl=https\://services.gradle.org/distributions/gradle-8.7-bin.zip
+networkTimeout=10000
+validateDistributionUrl=true
+zipStoreBase=GRADLE_USER_HOME
+zipStorePath=wrapper/dists
diff --git a/gradlew b/gradlew
new file mode 100755
index 0000000..ef07e01
--- /dev/null
+++ b/gradlew
@@ -0,0 +1,251 @@
+#!/bin/sh
+
+#
+# Copyright © 2015 the original authors.
+#
+# Licensed under the Apache License, Version 2.0 (the "License");
+# you may not use this file except in compliance with the License.
+# You may obtain a copy of the License at
+#
+# https://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing, software
+# distributed under the License is distributed on an "AS IS" BASIS,
+# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+# See the License for the specific language governing permissions and
+# limitations under the License.
+#
+# SPDX-License-Identifier: Apache-2.0
+#
+
+##############################################################################
+#
+# Gradle start up script for POSIX generated by Gradle.
+#
+# Important for running:
+#
+# (1) You need a POSIX-compliant shell to run this script. If your /bin/sh is
+# noncompliant, but you have some other compliant shell such as ksh or
+# bash, then to run this script, type that shell name before the whole
+# command line, like:
+#
+# ksh Gradle
+#
+# Busybox and similar reduced shells will NOT work, because this script
+# requires all of these POSIX shell features:
+# * functions;
+# * expansions «$var», «${var}», «${var:-default}», «${var+SET}»,
+# «${var#prefix}», «${var%suffix}», and «$( cmd )»;
+# * compound commands having a testable exit status, especially «case»;
+# * various built-in commands including «command», «set», and «ulimit».
+#
+# Important for patching:
+#
+# (2) This script targets any POSIX shell, so it avoids extensions provided
+# by Bash, Ksh, etc; in particular arrays are avoided.
+#
+# The "traditional" practice of packing multiple parameters into a
+# space-separated string is a well documented source of bugs and security
+# problems, so this is (mostly) avoided, by progressively accumulating
+# options in "$@", and eventually passing that to Java.
+#
+# Where the inherited environment variables (DEFAULT_JVM_OPTS, JAVA_OPTS,
+# and GRADLE_OPTS) rely on word-splitting, this is performed explicitly;
+# see the in-line comments for details.
+#
+# There are tweaks for specific operating systems such as AIX, CygWin,
+# Darwin, MinGW, and NonStop.
+#
+# (3) This script is generated from the Groovy template
+# https://github.com/gradle/gradle/blob/HEAD/platforms/jvm/plugins-application/src/main/resources/org/gradle/api/internal/plugins/unixStartScript.txt
+# within the Gradle project.
+#
+# You can find Gradle at https://github.com/gradle/gradle/.
+#
+##############################################################################
+
+# Attempt to set APP_HOME
+
+# Resolve links: $0 may be a link
+app_path=$0
+
+# Need this for daisy-chained symlinks.
+while
+ APP_HOME=${app_path%"${app_path##*/}"} # leaves a trailing /; empty if no leading path
+ [ -h "$app_path" ]
+do
+ ls=$( ls -ld "$app_path" )
+ link=${ls#*' -> '}
+ case $link in #(
+ /*) app_path=$link ;; #(
+ *) app_path=$APP_HOME$link ;;
+ esac
+done
+
+# This is normally unused
+# shellcheck disable=SC2034
+APP_BASE_NAME=${0##*/}
+# Discard cd standard output in case $CDPATH is set (https://github.com/gradle/gradle/issues/25036)
+APP_HOME=$( cd -P "${APP_HOME:-./}" > /dev/null && printf '%s\n' "$PWD" ) || exit
+
+# Use the maximum available, or set MAX_FD != -1 to use that value.
+MAX_FD=maximum
+
+warn () {
+ echo "$*"
+} >&2
+
+die () {
+ echo
+ echo "$*"
+ echo
+ exit 1
+} >&2
+
+# OS specific support (must be 'true' or 'false').
+cygwin=false
+msys=false
+darwin=false
+nonstop=false
+case "$( uname )" in #(
+ CYGWIN* ) cygwin=true ;; #(
+ Darwin* ) darwin=true ;; #(
+ MSYS* | MINGW* ) msys=true ;; #(
+ NONSTOP* ) nonstop=true ;;
+esac
+
+CLASSPATH="\\\"\\\""
+
+
+# Determine the Java command to use to start the JVM.
+if [ -n "$JAVA_HOME" ] ; then
+ if [ -x "$JAVA_HOME/jre/sh/java" ] ; then
+ # IBM's JDK on AIX uses strange locations for the executables
+ JAVACMD=$JAVA_HOME/jre/sh/java
+ else
+ JAVACMD=$JAVA_HOME/bin/java
+ fi
+ if [ ! -x "$JAVACMD" ] ; then
+ die "ERROR: JAVA_HOME is set to an invalid directory: $JAVA_HOME
+
+Please set the JAVA_HOME variable in your environment to match the
+location of your Java installation."
+ fi
+else
+ JAVACMD=java
+ if ! command -v java >/dev/null 2>&1
+ then
+ die "ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH.
+
+Please set the JAVA_HOME variable in your environment to match the
+location of your Java installation."
+ fi
+fi
+
+# Increase the maximum file descriptors if we can.
+if ! "$cygwin" && ! "$darwin" && ! "$nonstop" ; then
+ case $MAX_FD in #(
+ max*)
+ # In POSIX sh, ulimit -H is undefined. That's why the result is checked to see if it worked.
+ # shellcheck disable=SC2039,SC3045
+ MAX_FD=$( ulimit -H -n ) ||
+ warn "Could not query maximum file descriptor limit"
+ esac
+ case $MAX_FD in #(
+ '' | soft) :;; #(
+ *)
+ # In POSIX sh, ulimit -n is undefined. That's why the result is checked to see if it worked.
+ # shellcheck disable=SC2039,SC3045
+ ulimit -n "$MAX_FD" ||
+ warn "Could not set maximum file descriptor limit to $MAX_FD"
+ esac
+fi
+
+# Collect all arguments for the java command, stacking in reverse order:
+# * args from the command line
+# * the main class name
+# * -classpath
+# * -D...appname settings
+# * --module-path (only if needed)
+# * DEFAULT_JVM_OPTS, JAVA_OPTS, and GRADLE_OPTS environment variables.
+
+# For Cygwin or MSYS, switch paths to Windows format before running java
+if "$cygwin" || "$msys" ; then
+ APP_HOME=$( cygpath --path --mixed "$APP_HOME" )
+ CLASSPATH=$( cygpath --path --mixed "$CLASSPATH" )
+
+ JAVACMD=$( cygpath --unix "$JAVACMD" )
+
+ # Now convert the arguments - kludge to limit ourselves to /bin/sh
+ for arg do
+ if
+ case $arg in #(
+ -*) false ;; # don't mess with options #(
+ /?*) t=${arg#/} t=/${t%%/*} # looks like a POSIX filepath
+ [ -e "$t" ] ;; #(
+ *) false ;;
+ esac
+ then
+ arg=$( cygpath --path --ignore --mixed "$arg" )
+ fi
+ # Roll the args list around exactly as many times as the number of
+ # args, so each arg winds up back in the position where it started, but
+ # possibly modified.
+ #
+ # NB: a `for` loop captures its iteration list before it begins, so
+ # changing the positional parameters here affects neither the number of
+ # iterations, nor the values presented in `arg`.
+ shift # remove old arg
+ set -- "$@" "$arg" # push replacement arg
+ done
+fi
+
+
+# Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script.
+DEFAULT_JVM_OPTS='"-Xmx64m" "-Xms64m"'
+
+# Collect all arguments for the java command:
+# * DEFAULT_JVM_OPTS, JAVA_OPTS, and optsEnvironmentVar are not allowed to contain shell fragments,
+# and any embedded shellness will be escaped.
+# * For example: A user cannot expect ${Hostname} to be expanded, as it is an environment variable and will be
+# treated as '${Hostname}' itself on the command line.
+
+set -- \
+ "-Dorg.gradle.appname=$APP_BASE_NAME" \
+ -classpath "$CLASSPATH" \
+ -jar "$APP_HOME/gradle/wrapper/gradle-wrapper.jar" \
+ "$@"
+
+# Stop when "xargs" is not available.
+if ! command -v xargs >/dev/null 2>&1
+then
+ die "xargs is not available"
+fi
+
+# Use "xargs" to parse quoted args.
+#
+# With -n1 it outputs one arg per line, with the quotes and backslashes removed.
+#
+# In Bash we could simply go:
+#
+# readarray ARGS < <( xargs -n1 <<<"$var" ) &&
+# set -- "${ARGS[@]}" "$@"
+#
+# but POSIX shell has neither arrays nor command substitution, so instead we
+# post-process each arg (as a line of input to sed) to backslash-escape any
+# character that might be a shell metacharacter, then use eval to reverse
+# that process (while maintaining the separation between arguments), and wrap
+# the whole thing up as a single "set" statement.
+#
+# This will of course break if any of these variables contains a newline or
+# an unmatched quote.
+#
+
+eval "set -- $(
+ printf '%s\n' "$DEFAULT_JVM_OPTS $JAVA_OPTS $GRADLE_OPTS" |
+ xargs -n1 |
+ sed ' s~[^-[:alnum:]+,./:=@_]~\\&~g; ' |
+ tr '\n' ' '
+ )" '"$@"'
+
+exec "$JAVACMD" "$@"
diff --git a/gradlew.bat b/gradlew.bat
new file mode 100644
index 0000000..5eed7ee
--- /dev/null
+++ b/gradlew.bat
@@ -0,0 +1,94 @@
+@rem
+@rem Copyright 2015 the original author or authors.
+@rem
+@rem Licensed under the Apache License, Version 2.0 (the "License");
+@rem you may not use this file except in compliance with the License.
+@rem You may obtain a copy of the License at
+@rem
+@rem https://www.apache.org/licenses/LICENSE-2.0
+@rem
+@rem Unless required by applicable law or agreed to in writing, software
+@rem distributed under the License is distributed on an "AS IS" BASIS,
+@rem WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+@rem See the License for the specific language governing permissions and
+@rem limitations under the License.
+@rem
+@rem SPDX-License-Identifier: Apache-2.0
+@rem
+
+@if "%DEBUG%"=="" @echo off
+@rem ##########################################################################
+@rem
+@rem Gradle startup script for Windows
+@rem
+@rem ##########################################################################
+
+@rem Set local scope for the variables with windows NT shell
+if "%OS%"=="Windows_NT" setlocal
+
+set DIRNAME=%~dp0
+if "%DIRNAME%"=="" set DIRNAME=.
+@rem This is normally unused
+set APP_BASE_NAME=%~n0
+set APP_HOME=%DIRNAME%
+
+@rem Resolve any "." and ".." in APP_HOME to make it shorter.
+for %%i in ("%APP_HOME%") do set APP_HOME=%%~fi
+
+@rem Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script.
+set DEFAULT_JVM_OPTS="-Xmx64m" "-Xms64m"
+
+@rem Find java.exe
+if defined JAVA_HOME goto findJavaFromJavaHome
+
+set JAVA_EXE=java.exe
+%JAVA_EXE% -version >NUL 2>&1
+if %ERRORLEVEL% equ 0 goto execute
+
+echo. 1>&2
+echo ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH. 1>&2
+echo. 1>&2
+echo Please set the JAVA_HOME variable in your environment to match the 1>&2
+echo location of your Java installation. 1>&2
+
+goto fail
+
+:findJavaFromJavaHome
+set JAVA_HOME=%JAVA_HOME:"=%
+set JAVA_EXE=%JAVA_HOME%/bin/java.exe
+
+if exist "%JAVA_EXE%" goto execute
+
+echo. 1>&2
+echo ERROR: JAVA_HOME is set to an invalid directory: %JAVA_HOME% 1>&2
+echo. 1>&2
+echo Please set the JAVA_HOME variable in your environment to match the 1>&2
+echo location of your Java installation. 1>&2
+
+goto fail
+
+:execute
+@rem Setup the command line
+
+set CLASSPATH=
+
+
+@rem Execute Gradle
+"%JAVA_EXE%" %DEFAULT_JVM_OPTS% %JAVA_OPTS% %GRADLE_OPTS% "-Dorg.gradle.appname=%APP_BASE_NAME%" -classpath "%CLASSPATH%" -jar "%APP_HOME%\gradle\wrapper\gradle-wrapper.jar" %*
+
+:end
+@rem End local scope for the variables with windows NT shell
+if %ERRORLEVEL% equ 0 goto mainEnd
+
+:fail
+rem Set variable GRADLE_EXIT_CONSOLE if you need the _script_ return code instead of
+rem the _cmd.exe /c_ return code!
+set EXIT_CODE=%ERRORLEVEL%
+if %EXIT_CODE% equ 0 set EXIT_CODE=1
+if not ""=="%GRADLE_EXIT_CONSOLE%" exit %EXIT_CODE%
+exit /b %EXIT_CODE%
+
+:mainEnd
+if "%OS%"=="Windows_NT" endlocal
+
+:omega
diff --git a/local.properties b/local.properties
new file mode 100644
index 0000000..f79ab69
--- /dev/null
+++ b/local.properties
@@ -0,0 +1 @@
+sdk.dir=/Users/qianchao/Library/Android/sdk
\ No newline at end of file
diff --git a/prd.md b/prd.md
new file mode 100644
index 0000000..02cd060
--- /dev/null
+++ b/prd.md
@@ -0,0 +1,167 @@
+# **AR远程协作APP (代号:Synergy Lens) \- 产品需求文档 (PRD)**
+
+### **1\. 产品概述 (Product Overview)**
+
+### **1.1 产品简介**
+
+XSynergy是一款面向企业用户的AR远程协作应用。它通过结合实时音视频、增强现实(AR)标注和人工智能(AI)技术,使一线现场人员能够与远程专家进行“身临其境”的互动,高效解决复杂问题,旨在打破地理限制,降低差旅成本,提升协作效率和知识传承。
+
+### **1.2 目标用户 (Target Audience)**
+
+* **一线现场人员 (Field Technician)**:如设备维修工程师、产线操作员、医疗巡检人员。他们需要实时、精准的远程指导。
+* **远程专家 (Remote Expert)**:如资深技术专家、产品设计师、医疗顾问。他们需要清晰地了解现场情况并提供精确指令。
+* **项目/团队管理者 (Manager)**:需要监督协作过程、回顾协作内容、管理团队知识。
+
+### **1.3 核心目标 (Goals)**
+
+* **提升问题解决效率**:通过AR标注和实时音视频,将专家指导的平均解决时间缩短30%。
+* **降低运营成本**:减少专家差旅需求,预计每年可为企业节省20%的相关费用。
+* **赋能一线员工**:通过AI知识库和会议回放,帮助一线人员快速成长,沉淀组织知识。
+
+### **2\. 功能需求详述 (Functional Requirements)**
+
+### **2.1 用户与账户模块 (User & Account)**
+
+* **FR-2.1.1 用户登录**
+ * **用户故事**:作为一名企业员工,我希望可以通过我的企业账号(如SSO、手机号/验证码)安全地登录APP,以便访问我的协作空间和组织信息。
+ * **功能描述**:
+ 1. 支持手机号+验证码登录方式。
+ 2. 支持企业单点登录(SSO)。
+ 3. 提供“记住我”功能,保持登录状态。
+ 4. 包含“忘记密码”流程(针对非SSO用户)。
+ * **验收标准**:用户可以成功登录并进入APP主界面;登录失败时有明确的错误提示。
+* **FR-2.1.2 用户登出**
+ * **用户故事**:作为一名用户,我希望能从APP中安全退出登录,以保护我的账户信息。
+ * **功能描述**:在“我的”或“设置”页面提供“退出登录”按钮,点击后清除本地登录凭证并返回登录页面。
+ * **验收标准**:用户点击退出后,无法在未重新登录的情况下访问需要授权的页面。
+
+### **2.2 协作会话模块 (Collaboration Session)**
+
+* **FR-2.2.1 发起即时协作**
+ * **用户故事**:作为一名现场技术员,当我遇到紧急问题时,我希望能立即向组织内的专家发起视频协作请求。
+ * **功能描述**:
+ 1. 主界面提供“发起协作”入口。
+ 2. 用户可以从组织架构中选择一位或多位成员发起呼叫。
+ 3. 被呼叫方会收到系统推送通知,可选择接听或拒绝。
+ * **验收标准**:呼叫成功发起,被叫方能收到通知并进行响应。
+* **FR-2.2.2 预约协作**
+ * **用户故事**:作为一名项目经理,我希望能提前预约一个多方协作会议,并设定主题、时间和参与人,系统能自动提醒相关人员。
+ * **功能描述**:
+ 1. 提供“预约协作”功能入口。
+ 2. 用户可设置会议主题、选择开始/结束时间、添加参会人员、填写会议描述。
+ 3. 预约成功后,系统向所有参会人发送日历邀请和APP内通知。
+ 4. 在会议开始前15分钟,系统再次发送提醒通知。(以日历提醒和APP内消息推送的方式)
+ * **验收标准**:预约信息准确无误,所有参会人能按时收到通知和提醒。(考虑到国内安卓手机限制较多且接口不统一,APP内部推送消息无法在手机端弹出提醒的话,不视为功能实现失败。)
+* **FR-2.2.3 邀请与加入**
+ * **用户故事**:作为会议发起人,我希望能通过链接或一个简单的会议码,快速邀请组织外或未在联系人列表中的人加入协作。
+ * **功能描述**:
+ 1. 每个协作会话(即时或预约)都生成一个唯一的分享链接和一个6位数字的会议码。
+ 2. 用户可以通过社交应用(微信、钉钉等)分享链接(分享为链接复制,形式同腾讯会议)。
+ 3. APP主界面提供“加入协作”入口,用户可输入6位会议码加入。
+ 4. 通过链接加入时,自动拉起APP并进入会议。
+ * **验收标准**:用户可以通过链接和会议码两种方式成功加入指定的协作会议。
+
+### **2.3 协作中功能模块 (In-Session Features)**
+
+* **FR-2.3.1 基础音视频控制**
+ * **用户故事**:作为参会者,我希望能自由控制自己的麦克风和摄像头,以便在需要时静音或关闭画面。
+ * **功能描述**:会议界面提供清晰的图标按钮,用于一键禁用/启用麦克风和摄像头。图标状态需明确反映当前设备状态(开/关)。
+ * **验收标准**:用户可以随时开启或关闭自己的音视频输入,其他参会者能实时看到状态变化。
+* **FR-2.3.2 AR标注协作**
+ * **用户故事**:作为远程专家,我希望能在我方屏幕上,对现场人员传输回来的实时视频画面进行3D空间标注(如画箭头、框选),且标注能稳定地附着在现实物体上,以便清晰地指示操作位置。
+ * **功能描述**:
+ 1. 提供多种标注工具:箭头、自由曲线、矩形框。
+ 2. 支持选择不同颜色进行标注。
+ 3. 标注内容通过AR技术“冻结”在三维空间中,即使用户移动摄像头,标注也会停留在原来的物理位置上。
+ 4. 提供“清除”按钮,可清除自己或全部的标注。
+ * **验收标准**:AR标注延迟低,跟踪稳定,在现场人员视角中清晰可见。
+* **FR-2.3.3 白板涂鸦协作**
+ * **用户故事**:作为参会者,当我们需要讨论流程图或进行头脑风暴时,我希望能打开一个共享的虚拟白板,所有人都可以在上面实时涂鸦和书写。
+ * **功能描述**:
+ 1. 会议中可开启“白板模式”,视频画面切换为共享白板。
+ 2. 提供画笔、文本输入、形状工具和橡皮擦。
+ 3. 所有参会者的操作实时同步。
+ 4. 支持白板内容截图保存。
+ * **验收标准**:白板操作流畅,同步延迟低,所有用户看到的内容完全一致。
+* **FR-2.3.4 激光笔标注**
+ * **用户故事**:作为远程专家,我希望能有一个激光笔工具,在我讲解时,可以在对方的视频画面上实时指示出我正在关注的点,而不会留下永久标记。
+ * **功能描述**:提供“激光笔”工具。专家在自己屏幕上长按并移动手指(或在web端长按鼠标进行拖动),现场人员的屏幕上会实时显示一个跟随移动的光点或小图标。手指松开后光点消失。
+ * **验收标准**:激光笔指示实时同步,无明显延迟,能准确传达专家意图。
+* **FR-2.3.5 屏幕共享**
+ * **用户故事**:作为参会者,我希望能将我的手机屏幕内容共享给会议中的其他人,以便展示APP操作或数据图表。
+ * **功能描述**:
+ 1. 提供“屏幕共享”功能按钮。
+ 2. 用户授权后,将其整个手机屏幕或指定应用画面作为视频流发送给其他参会者。
+ 3. 共享期间,屏幕边缘有明显提示(如红色边框),告知用户正在共享。
+ * **验收标准**:屏幕共享画面清晰流畅,其他用户可以正常观看。
+* **FR-2.3.6 文件共享与播放**
+ * **用户故事**:作为专家,我希望能向现场人员发送技术手册(PDF/Word)或操作演示视频(MP4),并能在会议中共同观看和讨论。
+ * **功能描述**:
+ 1. 提供“发送文件”功能,支持从本地上传PDF, Word, MP4等格式文件。
+ 2. 文件发送给指定的一位或全部参会者。
+ 3. 接收方收到文件后可下载到本地。
+ 4. 发起方可以开启“共同播放”模式,所有人的界面上会同步播放该视频或展示该文档,并支持翻页、暂停等同步操作。(此功能需要讨论,非优先项)
+ * **验收标准**:文件能成功发送和接收;共同播放/阅览时,所有人的视图保持同步。
+
+### **2.4 AI能力模块 (AI Capabilities)**
+
+* **FR-2.4.1 实时语音转文字与会议纪要**
+ * **用户故事**:作为一名参会者,我希望能看到实时的语音转文字字幕,并在会后自动生成一份包含关键决策和待办事项的会议纪要,以减少我的记录工作。
+ * **功能描述**:
+ 1. 会议中可开启“实时字幕”功能,将所有人的发言实时转化为文字显示在屏幕上。
+ 2. 会议结束后,AI自动处理录音,生成结构化的会议纪要,包括:会议摘要、议题列表、关键决策、待办事项(Action Items)及负责人。
+ 3. 会议纪要与会议回放关联,存储在历史记录中。
+ * **验收标准**:语音转文字准确率不低于90%;会议纪要能准确提炼核心信息。
+* **FR-2.4.2 AI知识库查询**
+ * **用户故事**:作为现场人员,在协作过程中,我希望能通过一个对话框,快速查询公司内部知识库(如设备故障手册、标准操作流程),以便快速找到参考信息。
+ * **功能描述**:
+ 1. 会议界面提供一个“AI助手”入口,点击或语音激活后弹出对话框。
+ 2. 用户输入自然语言问题(如“E-101泵的常见故障代码及解决方案”)。
+ 3. AI助手基于后台接入的企业知识库进行检索,并以对话形式返回最相关的答案和文档链接。
+ * **验收标准**:AI助手能理解用户意图,并从知识库中返回准确、相关的答案。
+
+### **2.5 其他模块 (Miscellaneous)**
+
+* **FR-2.5.1 会议回放**
+ * **用户故事**:作为一名项目经理或未能参会的人员,我希望能随时查看历史会议的完整录像(包含AR标注),以便复盘问题或了解会议内容。
+ * **功能描述**:
+ 1. 所有协作会话默认开启云端录制。
+ 2. 用户可以在“历史记录”中找到过去的会议列表。
+ 3. 点击即可播放会议录像,录像需完整重现当时的视频、音频、AR标注、白板、文件共享等所有协作信息。
+ * **验收标准**:回放内容与实际协作过程完全一致,播放流畅。
+* **FR-2.5.2 组织架构查看**
+ * **用户故事**:作为一名员工,我希望能方便地查看公司的组织架构树,快速找到并联系到我需要的同事。
+ * **功能描述**:
+ 1. 提供“通讯录”或“组织”入口。
+ 2. 以树状结构展示公司部门和人员。
+ 3. 支持按姓名、部门、职位进行搜索。
+ 4. 点击人员可查看其联系方式并直接发起协作。
+ * **验收标准**:组织架构数据准确,搜索功能可用,能快速定位到目标同事。
+
+### **3\. 非功能性需求 (Non-Functional Requirements)**
+
+* **NF-3.1 性能 (Performance)**
+ * 音视频通话延迟低于200ms。
+ * AR标注跟踪刷新率不低于30fps。
+ * APP冷启动时间小于3秒。
+* **NF-3.2 兼容性 (Compatibility)**
+ * 支持Android 8.0及以上版本。
+ * 要求设备支持ARCore。
+* **NF-3.3 安全性 (Security)**
+ * 所有通信数据(音视频、信令、文件)均采用端到端加密。
+ * 用户数据存储符合GDPR或相关数据保护法规。
+* **NF-3.4 用户体验 (Usability)**
+ * 界面设计简洁直观,关键操作按钮尺寸足够大,易于在移动或工业环境中单手操作。
+ * 网络不稳定时,应优先保证音频清晰度,并有明确的网络状态提示。
+
+### **4\. 设计与AI集成指南 (Design & AI Integration)**
+
+* **UI/UE设计指南**:
+ * **核心原则**:信息降噪,聚焦于协作视图。避免不必要的UI元素干扰现场人员的视线。
+ * **AR交互**:AR标注工具栏应设计为可收缩式,默认最小化。标注的视觉效果应有足够的对比度,以适应各种复杂的现场环境光线。
+ * **手势操作**:考虑引入简单的手势操作,如双击屏幕清除最后一次标注,以提升操作效率。
+* **AI模型集成指南**:
+ * **语音转文字 (STT)**:需选用或训练针对特定行业术语(如机械、医疗)进行优化的模型,以提高识别准确率。模型需在端侧或低延迟的云端运行,保证实时性。
+ * **会议纪要生成**:采用大语言模型(LLM)进行文本摘要和信息提取。模型需被调整以准确识别“决策”、“任务分配”等关键意图。
+ * **知识库查询**:建议采用RAG(检索增强生成)架构。后端需建立高效的文档索引,前端AI助手通过语义搜索匹配最相关的知识片段,并由LLM整合后生成自然语言答案。
+
diff --git a/settings.gradle b/settings.gradle
new file mode 100644
index 0000000..9df2f02
--- /dev/null
+++ b/settings.gradle
@@ -0,0 +1,2 @@
+include ':app'
+rootProject.name = "XSynergy"
\ No newline at end of file
diff --git a/ui.html b/ui.html
new file mode 100644
index 0000000..00264b7
--- /dev/null
+++ b/ui.html
@@ -0,0 +1,1220 @@
+
+
+
+
+
+ XSynergy - AR远程协作APP设计原型
+
+
+
+
+
+
+
+ 9:41
+ 100%
+
+
+
XSynergy
+
+
+
+
+
登录
+
+
+ 记住我
+
+
+
+
+
+
+
+
+ 9:41
+ 100%
+
+
+
你好,李明
+
+ 发起协作
+ 加入协作
+ 预约协作
+
+
今日预约
+
+
即将开始
+
设备检修会议
+
14:30 - 15:30 | 发起人:王工
+
立即加入
+
+
+
项目评审会议
+
16:00 - 17:00 | 发起人:张总
+
+
+
+
+
+
+
+
+ 9:41
+ 100%
+
+
+
+
+
+
+
+ 🎤
+ 📹
+ ✏️
+ 📤
+ ⋯
+
+
+
+
+
+
+
+
+
+
+
+
+
+ 9:41
+ 100%
+
+
+
+
+ 退出登录
+
+
+
+
+
+
+
+ 9:41
+ 100%
+
+
+
📅
+
暂无预约会议,发起一个吧!
+
发起协作
+
+
+
+
+
+
+ 登录
+ 主页
+ AR协作
+ 历史
+ 通讯录
+ 我的
+
+
+
+
+
\ No newline at end of file