diff --git a/.idea/.gitignore b/.idea/.gitignore
deleted file mode 100644
index 26d3352..0000000
--- a/.idea/.gitignore
+++ /dev/null
@@ -1,3 +0,0 @@
-# Default ignored files
-/shelf/
-/workspace.xml
diff --git a/.idea/OnlineMsgServer.iml b/.idea/OnlineMsgServer.iml
deleted file mode 100644
index d6ebd48..0000000
--- a/.idea/OnlineMsgServer.iml
+++ /dev/null
@@ -1,9 +0,0 @@
-
-
-
-
-
-
-
-
-
\ No newline at end of file
diff --git a/.idea/caches/deviceStreaming.xml b/.idea/caches/deviceStreaming.xml
deleted file mode 100644
index 23267a6..0000000
--- a/.idea/caches/deviceStreaming.xml
+++ /dev/null
@@ -1,1490 +0,0 @@
-
-
-
-
-
-
\ No newline at end of file
diff --git a/.idea/misc.xml b/.idea/misc.xml
deleted file mode 100644
index 4b151ab..0000000
--- a/.idea/misc.xml
+++ /dev/null
@@ -1,6 +0,0 @@
-
-
-
-
-
-
\ No newline at end of file
diff --git a/.idea/modules.xml b/.idea/modules.xml
deleted file mode 100644
index 7801fe1..0000000
--- a/.idea/modules.xml
+++ /dev/null
@@ -1,8 +0,0 @@
-
-
-
-
-
-
-
-
\ No newline at end of file
diff --git a/.idea/vcs.xml b/.idea/vcs.xml
deleted file mode 100644
index 35eb1dd..0000000
--- a/.idea/vcs.xml
+++ /dev/null
@@ -1,6 +0,0 @@
-
-
-
-
-
-
\ No newline at end of file
diff --git a/android-client/app/.DS_Store b/android-client/app/.DS_Store
deleted file mode 100644
index ca77567..0000000
Binary files a/android-client/app/.DS_Store and /dev/null differ
diff --git a/android-client/app/src/main/java/com/onlinemsg/client/MainActivity.kt b/android-client/app/src/main/java/com/onlinemsg/client/MainActivity.kt
index a07bba6..defba49 100644
--- a/android-client/app/src/main/java/com/onlinemsg/client/MainActivity.kt
+++ b/android-client/app/src/main/java/com/onlinemsg/client/MainActivity.kt
@@ -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,53 +21,21 @@ 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,
- 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 {
const val REQUEST_NOTIFICATION_PERMISSION = 1002
}
-}
\ No newline at end of file
+}
diff --git a/android-client/app/src/main/java/com/onlinemsg/client/service/ChatForegroundService.kt b/android-client/app/src/main/java/com/onlinemsg/client/service/ChatForegroundService.kt
index 8566d22..0ebaa12 100644
--- a/android-client/app/src/main/java/com/onlinemsg/client/service/ChatForegroundService.kt
+++ b/android-client/app/src/main/java/com/onlinemsg/client/service/ChatForegroundService.kt
@@ -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()
diff --git a/android-client/app/src/main/java/com/onlinemsg/client/ui/ChatScreen.kt b/android-client/app/src/main/java/com/onlinemsg/client/ui/ChatScreen.kt
index 714d1e2..a8e31a4 100644
--- a/android-client/app/src/main/java/com/onlinemsg/client/ui/ChatScreen.kt
+++ b/android-client/app/src/main/java/com/onlinemsg/client/ui/ChatScreen.kt
@@ -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}")