Sync from upstream/ai-dev (squashed)

pull/2/head
alimu 2 weeks ago
parent 69bc905300
commit c4029eb684

3
.idea/.gitignore vendored

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

@ -1,9 +0,0 @@
<?xml version="1.0" encoding="UTF-8"?>
<module type="JAVA_MODULE" version="4">
<component name="NewModuleRootManager" inherit-compiler-output="true">
<exclude-output />
<content url="file://$MODULE_DIR$" />
<orderEntry type="inheritedJdk" />
<orderEntry type="sourceFolder" forTests="false" />
</component>
</module>

File diff suppressed because it is too large Load Diff

@ -1,6 +0,0 @@
<?xml version="1.0" encoding="UTF-8"?>
<project version="4">
<component name="ProjectRootManager" version="2">
<output url="file://$PROJECT_DIR$/out" />
</component>
</project>

@ -1,8 +0,0 @@
<?xml version="1.0" encoding="UTF-8"?>
<project version="4">
<component name="ProjectModuleManager">
<modules>
<module fileurl="file://$PROJECT_DIR$/.idea/OnlineMsgServer.iml" filepath="$PROJECT_DIR$/.idea/OnlineMsgServer.iml" />
</modules>
</component>
</project>

@ -1,6 +0,0 @@
<?xml version="1.0" encoding="UTF-8"?>
<project version="4">
<component name="VcsDirectoryMappings">
<mapping directory="" vcs="Git" />
</component>
</project>

Binary file not shown.

@ -9,11 +9,9 @@ import androidx.activity.compose.setContent
import androidx.activity.enableEdgeToEdge
import androidx.core.app.ActivityCompat
import androidx.core.content.ContextCompat
import com.onlinemsg.client.service.ChatForegroundService
import com.onlinemsg.client.ui.OnlineMsgApp
class MainActivity : ComponentActivity() {
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
requestNotificationPermissionIfNeeded()
@ -23,50 +21,18 @@ class MainActivity : ComponentActivity() {
}
}
private val PERMISSION_REQUEST_NOTIFICATIONS = 1001
private fun requestNotificationPermissionIfNeeded() {
if (Build.VERSION.SDK_INT < Build.VERSION_CODES.TIRAMISU) return
val granted = ContextCompat.checkSelfPermission(
this,
Manifest.permission.POST_NOTIFICATIONS
) == PackageManager.PERMISSION_GRANTED
if (granted) {
// 已有权限
startForegroundServiceIfNeeded()
} else {
// 请求权限
ActivityCompat.requestPermissions(
this,
arrayOf(Manifest.permission.POST_NOTIFICATIONS),
REQUEST_NOTIFICATION_PERMISSION
)
}
}
override fun onRequestPermissionsResult(
requestCode: Int,
permissions: Array<String>,
grantResults: IntArray
) {
super.onRequestPermissionsResult(requestCode, permissions, grantResults)
when (requestCode) {
REQUEST_NOTIFICATION_PERMISSION -> {
if (grantResults.isNotEmpty() && grantResults[0] == PackageManager.PERMISSION_GRANTED) {
/* 授予权限的处理 */
startForegroundServiceIfNeeded()
} else {
/* 拒绝权限的处理 */
}
}
}
}
/**
* 根据业务逻辑决定何时启动前台服务(@emilia-t)
*/
private fun startForegroundServiceIfNeeded() {
ChatForegroundService.start(this)
if (granted) return
ActivityCompat.requestPermissions(
this,
arrayOf(Manifest.permission.POST_NOTIFICATIONS),
REQUEST_NOTIFICATION_PERMISSION
)
}
private companion object {

@ -21,10 +21,6 @@ import kotlinx.coroutines.SupervisorJob
import kotlinx.coroutines.flow.distinctUntilChanged
import kotlinx.coroutines.flow.map
import kotlinx.coroutines.launch
import android.Manifest
import android.content.pm.PackageManager
import android.util.Log
import android.annotation.SuppressLint
class ChatForegroundService : Service() {
@ -66,18 +62,6 @@ class ChatForegroundService : Service() {
override fun onBind(intent: Intent?): IBinder? = null
/**
* 权限检查函数(@emilia-t)
*/
private fun hasNotificationPermission(): Boolean {
return if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.TIRAMISU) {
checkSelfPermission(Manifest.permission.POST_NOTIFICATIONS) == PackageManager.PERMISSION_GRANTED
} else {
true // 低于 Android 13 不需要权限
}
}
@SuppressLint("MissingPermission")
private fun observeStatusAndRefreshNotification() {
if (statusJob != null) return
statusJob = serviceScope.launch {
@ -85,15 +69,10 @@ class ChatForegroundService : Service() {
.map { it.status to it.statusHint }
.distinctUntilChanged()
.collect { (status, hint) ->
/* 检查是否有通知权限(@emilia-t) */
if (hasNotificationPermission()) {
NotificationManagerCompat.from(this@ChatForegroundService).notify(
FOREGROUND_NOTIFICATION_ID,
buildForegroundNotification(status, hint)
)
} else {
Log.d("ChatForegroundService", "通知权限缺失,跳过前台通知更新")
}
NotificationManagerCompat.from(this@ChatForegroundService).notify(
FOREGROUND_NOTIFICATION_ID,
buildForegroundNotification(status, hint)
)
if (status == ConnectionStatus.IDLE && !ChatSessionManager.shouldForegroundServiceRun()) {
stopForeground(STOP_FOREGROUND_REMOVE)
stopSelf()

@ -14,6 +14,7 @@ import androidx.compose.foundation.layout.Spacer
import androidx.compose.foundation.layout.fillMaxSize
import androidx.compose.foundation.layout.fillMaxWidth
import androidx.compose.foundation.layout.height
import androidx.compose.foundation.layout.heightIn
import androidx.compose.foundation.layout.padding
import androidx.compose.foundation.layout.size
import androidx.compose.foundation.layout.width
@ -101,7 +102,11 @@ fun OnlineMsgApp(
ConnectionStatus.READY -> MaterialTheme.colorScheme.primary
ConnectionStatus.ERROR -> MaterialTheme.colorScheme.error
else -> MaterialTheme.colorScheme.secondary
}
},
canConnect = state.canConnect,
canDisconnect = state.canDisconnect,
onConnect = viewModel::connect,
onDisconnect = viewModel::disconnect
)
},
bottomBar = {
@ -133,9 +138,6 @@ fun OnlineMsgApp(
onTargetKeyChange = viewModel::updateTargetKey,
onDraftChange = viewModel::updateDraft,
onSend = viewModel::sendMessage,
onConnect = viewModel::connect,
onDisconnect = viewModel::disconnect,
onClearMessages = viewModel::clearMessages,
onCopyMessage = { content ->
clipboard.setText(AnnotatedString(content))
viewModel.onMessageCopied()
@ -162,8 +164,6 @@ fun OnlineMsgApp(
viewModel.onMessageCopied()
}
},
onConnect = viewModel::connect,
onDisconnect = viewModel::disconnect,
onClearMessages = viewModel::clearMessages
)
}
@ -176,8 +176,13 @@ fun OnlineMsgApp(
@Composable
private fun AppTopBar(
statusText: String,
statusColor: Color
statusColor: Color,
canConnect: Boolean,
canDisconnect: Boolean,
onConnect: () -> Unit,
onDisconnect: () -> Unit
) {
val enabled = canConnect || canDisconnect
TopAppBar(
title = {
Text(
@ -187,7 +192,13 @@ private fun AppTopBar(
},
actions = {
AssistChip(
onClick = {},
onClick = {
when {
canDisconnect -> onDisconnect()
canConnect -> onConnect()
}
},
enabled = enabled,
label = { Text(statusText) },
leadingIcon = {
Box(
@ -211,9 +222,6 @@ private fun ChatTab(
onTargetKeyChange: (String) -> Unit,
onDraftChange: (String) -> Unit,
onSend: () -> Unit,
onConnect: () -> Unit,
onDisconnect: () -> Unit,
onClearMessages: () -> Unit,
onCopyMessage: (String) -> Unit
) {
val listState = rememberLazyListState()
@ -230,17 +238,6 @@ private fun ChatTab(
.imePadding()
.padding(horizontal = 16.dp, vertical = 8.dp)
) {
ConnectionRow(
statusHint = state.statusHint,
canConnect = state.canConnect,
canDisconnect = state.canDisconnect,
onConnect = onConnect,
onDisconnect = onDisconnect,
onClearMessages = onClearMessages
)
Spacer(modifier = Modifier.height(8.dp))
Row(horizontalArrangement = Arrangement.spacedBy(12.dp)) {
FilterChip(
selected = !state.directMode,
@ -254,6 +251,13 @@ private fun ChatTab(
)
}
Spacer(modifier = Modifier.height(8.dp))
Text(
text = state.statusHint,
style = MaterialTheme.typography.bodySmall,
color = MaterialTheme.colorScheme.onSurfaceVariant
)
if (state.directMode) {
Spacer(modifier = Modifier.height(8.dp))
OutlinedTextField(
@ -331,44 +335,6 @@ private fun ChatTab(
}
}
@Composable
private fun ConnectionRow(
statusHint: String,
canConnect: Boolean,
canDisconnect: Boolean,
onConnect: () -> Unit,
onDisconnect: () -> Unit,
onClearMessages: () -> Unit
) {
Card(
modifier = Modifier.fillMaxWidth(),
colors = CardDefaults.cardColors(containerColor = MaterialTheme.colorScheme.surfaceVariant)
) {
Column(modifier = Modifier.padding(12.dp), verticalArrangement = Arrangement.spacedBy(8.dp)) {
Text(
text = "在线会话",
style = MaterialTheme.typography.titleMedium
)
Text(
text = statusHint,
style = MaterialTheme.typography.bodyMedium,
color = MaterialTheme.colorScheme.onSurfaceVariant
)
Row(horizontalArrangement = Arrangement.spacedBy(8.dp)) {
Button(onClick = onConnect, enabled = canConnect) {
Text("连接")
}
OutlinedButton(onClick = onDisconnect, enabled = canDisconnect) {
Text("断开")
}
OutlinedButton(onClick = onClearMessages) {
Text("清空")
}
}
}
}
}
@Composable
private fun MessageItem(
message: UiMessage,
@ -517,10 +483,15 @@ private fun SettingsTab(
onToggleShowSystem: (Boolean) -> Unit,
onRevealPublicKey: () -> Unit,
onCopyPublicKey: () -> Unit,
onConnect: () -> Unit,
onDisconnect: () -> Unit,
onClearMessages: () -> Unit
) {
val settingsCardModifier = Modifier.fillMaxWidth()
val settingsCardContentModifier = Modifier
.fillMaxWidth()
.heightIn(min = 112.dp)
.padding(horizontal = 14.dp, vertical = 12.dp)
val settingsCardContentSpacing = Arrangement.spacedBy(10.dp)
LazyColumn(
modifier = modifier
.fillMaxSize()
@ -528,10 +499,10 @@ private fun SettingsTab(
verticalArrangement = Arrangement.spacedBy(12.dp)
) {
item {
Card {
Card(modifier = settingsCardModifier) {
Column(
modifier = Modifier.padding(12.dp),
verticalArrangement = Arrangement.spacedBy(8.dp)
modifier = settingsCardContentModifier,
verticalArrangement = settingsCardContentSpacing
) {
Text("个人设置", style = MaterialTheme.typography.titleMedium)
OutlinedTextField(
@ -542,26 +513,29 @@ private fun SettingsTab(
supportingText = { Text("最长 64 字符") },
maxLines = 1
)
Row(horizontalArrangement = Arrangement.spacedBy(8.dp)) {
Button(onClick = onConnect, enabled = state.canConnect) {
Text("连接")
}
OutlinedButton(onClick = onDisconnect, enabled = state.canDisconnect) {
Text("断开")
}
OutlinedButton(onClick = onClearMessages) {
Text("清空消息")
}
}
}
}
item {
Card(modifier = settingsCardModifier) {
Column(
modifier = settingsCardContentModifier,
verticalArrangement = settingsCardContentSpacing
) {
Text("聊天数据", style = MaterialTheme.typography.titleMedium)
OutlinedButton(onClick = onClearMessages) {
Text("清空聊天记录")
}
}
}
}
item {
Card {
Card(modifier = settingsCardModifier) {
Column(
modifier = Modifier.padding(12.dp),
verticalArrangement = Arrangement.spacedBy(8.dp)
modifier = settingsCardContentModifier,
verticalArrangement = settingsCardContentSpacing
) {
Text("服务器", style = MaterialTheme.typography.titleMedium)
OutlinedTextField(
@ -603,10 +577,10 @@ private fun SettingsTab(
}
item {
Card {
Card(modifier = settingsCardModifier) {
Column(
modifier = Modifier.padding(12.dp),
verticalArrangement = Arrangement.spacedBy(8.dp)
modifier = settingsCardContentModifier,
verticalArrangement = settingsCardContentSpacing
) {
Text("身份与安全", style = MaterialTheme.typography.titleMedium)
Row(horizontalArrangement = Arrangement.spacedBy(8.dp)) {
@ -637,10 +611,10 @@ private fun SettingsTab(
}
item {
Card {
Card(modifier = settingsCardModifier) {
Column(
modifier = Modifier.padding(12.dp),
verticalArrangement = Arrangement.spacedBy(8.dp)
modifier = settingsCardContentModifier,
verticalArrangement = settingsCardContentSpacing
) {
Text("诊断", style = MaterialTheme.typography.titleMedium)
Text("连接提示:${state.statusHint}")

Loading…
Cancel
Save