新增网络工具,修改bug

This commit is contained in:
weber
2025-08-27 16:37:53 +08:00
parent 52e571da01
commit 2a7d310be5
14 changed files with 382 additions and 226 deletions

View File

@@ -4,6 +4,7 @@
xmlns:tools="http://schemas.android.com/tools"> xmlns:tools="http://schemas.android.com/tools">
<uses-permission android:name="android.permission.INTERNET" /> <uses-permission android:name="android.permission.INTERNET" />
<uses-permission android:name="android.permission.ACCESS_NETWORK_STATE" />
<uses-permission android:name="android.permission.POST_NOTIFICATIONS" /> <uses-permission android:name="android.permission.POST_NOTIFICATIONS" />
<uses-permission android:name="android.permission.READ_PHONE_STATE" /> <uses-permission android:name="android.permission.READ_PHONE_STATE" />

View File

@@ -92,7 +92,8 @@ fun AccountSetting() {
), ),
dragHandle = {}, dragHandle = {},
shape = RoundedCornerShape(topStart = 16.dp, topEnd = 16.dp), shape = RoundedCornerShape(topStart = 16.dp, topEnd = 16.dp),
windowInsets = WindowInsets(0) windowInsets = WindowInsets(0),
modifier = Modifier.height(600.dp) // 设置固定高度
) { ) {
RemoveAccountModal( RemoveAccountModal(
onRemove = { onRemove = {

View File

@@ -19,6 +19,7 @@ import androidx.compose.material3.CircularProgressIndicator
import androidx.compose.material3.Icon import androidx.compose.material3.Icon
import androidx.compose.material3.Text import androidx.compose.material3.Text
import androidx.compose.runtime.Composable import androidx.compose.runtime.Composable
import androidx.compose.runtime.DisposableEffect
import androidx.compose.runtime.LaunchedEffect import androidx.compose.runtime.LaunchedEffect
import androidx.compose.runtime.getValue import androidx.compose.runtime.getValue
import androidx.compose.runtime.mutableStateOf import androidx.compose.runtime.mutableStateOf
@@ -86,6 +87,13 @@ fun AddAgentScreen() {
} }
// 页面退出时清空数据
DisposableEffect(Unit) {
onDispose {
model.clearData()
}
}
Column( Column(
modifier = Modifier modifier = Modifier
.fillMaxSize() .fillMaxSize()
@@ -230,10 +238,8 @@ fun AddAgentScreen() {
val result = model.createAgent(context) val result = model.createAgent(context)
if (result != null) { if (result != null) {
println("AddAgent: Agent created successfully, closing page") println("AddAgent: Agent created successfully, closing page")
// 创建成功,关闭页面 // 创建成功,清空数据并关闭页面
model.name = "" model.clearData()
model.desc = ""
model.isFromAddAgent = false // 重置标志
navController.popBackStack() navController.popBackStack()
} }
} catch (e: Exception) { } catch (e: Exception) {

View File

@@ -81,4 +81,15 @@ object AddAgentViewModel : ViewModel() {
else -> null else -> null
} }
} }
/**
* 清空所有页面数据
*/
fun clearData() {
name = ""
desc = ""
croppedBitmap = null
isUpdating = false
isFromAddAgent = false
}
} }

View File

@@ -8,6 +8,7 @@ import androidx.activity.compose.rememberLauncherForActivityResult
import androidx.activity.result.contract.ActivityResultContracts import androidx.activity.result.contract.ActivityResultContracts
import androidx.compose.animation.Crossfade import androidx.compose.animation.Crossfade
import androidx.compose.animation.core.animateDpAsState import androidx.compose.animation.core.animateDpAsState
import androidx.compose.animation.core.animateFloatAsState
import androidx.compose.animation.core.tween import androidx.compose.animation.core.tween
import androidx.compose.foundation.Image import androidx.compose.foundation.Image
import androidx.compose.foundation.background import androidx.compose.foundation.background
@@ -49,6 +50,7 @@ import androidx.compose.runtime.setValue
import androidx.compose.runtime.snapshotFlow import androidx.compose.runtime.snapshotFlow
import androidx.compose.ui.Alignment import androidx.compose.ui.Alignment
import androidx.compose.ui.Modifier import androidx.compose.ui.Modifier
import androidx.compose.ui.draw.alpha
import androidx.compose.ui.draw.clip import androidx.compose.ui.draw.clip
import androidx.compose.ui.draw.shadow import androidx.compose.ui.draw.shadow
import androidx.compose.ui.focus.onFocusChanged import androidx.compose.ui.focus.onFocusChanged
@@ -81,6 +83,7 @@ import com.aiosman.ravenow.ui.composables.DropdownMenu
import com.aiosman.ravenow.ui.composables.MenuItem import com.aiosman.ravenow.ui.composables.MenuItem
import com.aiosman.ravenow.ui.composables.StatusBarSpacer import com.aiosman.ravenow.ui.composables.StatusBarSpacer
import com.aiosman.ravenow.ui.modifiers.noRippleClickable import com.aiosman.ravenow.ui.modifiers.noRippleClickable
import com.aiosman.ravenow.utils.NetworkUtils
import com.tencent.imsdk.v2.V2TIMMessage import com.tencent.imsdk.v2.V2TIMMessage
import kotlinx.coroutines.launch import kotlinx.coroutines.launch
import java.util.UUID import java.util.UUID
@@ -228,12 +231,16 @@ fun ChatAiScreen(userId: String) {
) { ) {
isMenuExpanded = false isMenuExpanded = false
viewModel.viewModelScope.launch { if (NetworkUtils.isNetworkAvailable(context)) {
if (viewModel.notificationStrategy == "mute") { viewModel.viewModelScope.launch {
viewModel.updateNotificationStrategy("active") if (viewModel.notificationStrategy == "mute") {
} else { viewModel.updateNotificationStrategy("active")
viewModel.updateNotificationStrategy("mute") } else {
viewModel.updateNotificationStrategy("mute")
}
} }
} else {
android.widget.Toast.makeText(context, "网络连接异常,请检查网络设置", android.widget.Toast.LENGTH_SHORT).show()
} }
} }
), ),
@@ -260,7 +267,11 @@ fun ChatAiScreen(userId: String) {
ChatAiInput( ChatAiInput(
onSendImage = { onSendImage = {
it?.let { it?.let {
viewModel.sendImageMessage(it, context) if (NetworkUtils.isNetworkAvailable(context)) {
viewModel.sendImageMessage(it, context)
} else {
android.widget.Toast.makeText(context, "网络连接异常,请检查网络设置", android.widget.Toast.LENGTH_SHORT).show()
}
} }
}, },
) { ) {
@@ -355,19 +366,28 @@ fun ChatAiSelfItem(item: ChatItem) {
Column( Column(
horizontalAlignment = androidx.compose.ui.Alignment.End, horizontalAlignment = androidx.compose.ui.Alignment.End,
) { ) {
/* Text(
text = item.nickname,
style = TextStyle(
color = Color.Gray,
fontSize = 12.sp,
),
modifier = Modifier.padding(bottom = 2.dp)
)
*/
Box( Box(
modifier = Modifier modifier = Modifier
.widthIn( .widthIn(
min = 20.dp, min = 20.dp,
max = (if (item.messageType == V2TIMMessage.V2TIM_ELEM_TYPE_TEXT) 250.dp else 150.dp) max = (if (item.messageType == V2TIMMessage.V2TIM_ELEM_TYPE_TEXT) 250.dp else 150.dp)
) )
.clip(RoundedCornerShape(8.dp)) .clip(RoundedCornerShape(20.dp))
.background(Color(0xFF000000)) .background(Color(0xFF6246FF))
.padding( .padding(
vertical = (if (item.messageType == V2TIMMessage.V2TIM_ELEM_TYPE_TEXT) 8.dp else 0.dp), vertical = (if (item.messageType == V2TIMMessage.V2TIM_ELEM_TYPE_TEXT) 8.dp else 0.dp),
horizontal = (if (item.messageType == V2TIMMessage.V2TIM_ELEM_TYPE_TEXT) 16.dp else 0.dp) horizontal = (if (item.messageType == V2TIMMessage.V2TIM_ELEM_TYPE_TEXT) 16.dp else 0.dp)
) )
.padding(bottom = (if (item.messageType == V2TIMMessage.V2TIM_ELEM_TYPE_TEXT) 3.dp else 0.dp))
) { ) {
when (item.messageType) { when (item.messageType) {
V2TIMMessage.V2TIM_ELEM_TYPE_TEXT -> { V2TIMMessage.V2TIM_ELEM_TYPE_TEXT -> {
@@ -375,7 +395,7 @@ fun ChatAiSelfItem(item: ChatItem) {
text = item.message, text = item.message,
style = TextStyle( style = TextStyle(
color = Color.White, color = Color.White,
fontSize = 16.sp, fontSize = 14.sp,
), ),
textAlign = TextAlign.Start textAlign = TextAlign.Start
) )
@@ -391,28 +411,28 @@ fun ChatAiSelfItem(item: ChatItem) {
else -> { else -> {
Text( Text(
text = "Unsupported message type", text = "不支持的消息类型",
style = TextStyle( style = TextStyle(
color = Color.White, color = Color.White,
fontSize = 16.sp, fontSize = 14.sp,
) )
) )
} }
} }
} }
} }
Spacer(modifier = Modifier.width(12.dp)) /*Spacer(modifier = Modifier.width(12.dp))
Box( Box(
modifier = Modifier modifier = Modifier
.size(40.dp) .size(24.dp)
.clip(RoundedCornerShape(40.dp)) .clip(RoundedCornerShape(24.dp))
) { ) {
CustomAsyncImage( CustomAsyncImage(
imageUrl = item.avatar, imageUrl = item.avatar,
modifier = Modifier.fillMaxSize(), modifier = Modifier.fillMaxSize(),
contentDescription = "avatar" contentDescription = "avatar"
) )
} }*/
} }
} }
} }
@@ -432,8 +452,8 @@ fun ChatAiOtherItem(item: ChatItem) {
) { ) {
Box( Box(
modifier = Modifier modifier = Modifier
.size(40.dp) .size(24.dp)
.clip(RoundedCornerShape(40.dp)) .clip(RoundedCornerShape(24.dp))
) { ) {
CustomAsyncImage( CustomAsyncImage(
imageUrl = item.avatar, imageUrl = item.avatar,
@@ -450,7 +470,7 @@ fun ChatAiOtherItem(item: ChatItem) {
max = (if (item.messageType == V2TIMMessage.V2TIM_ELEM_TYPE_TEXT) 250.dp else 150.dp) max = (if (item.messageType == V2TIMMessage.V2TIM_ELEM_TYPE_TEXT) 250.dp else 150.dp)
) )
.clip(RoundedCornerShape(8.dp)) .clip(RoundedCornerShape(8.dp))
.background(AppColors.background) .background(AppColors.bubbleBackground)
.padding( .padding(
vertical = (if (item.messageType == V2TIMMessage.V2TIM_ELEM_TYPE_TEXT) 8.dp else 0.dp), vertical = (if (item.messageType == V2TIMMessage.V2TIM_ELEM_TYPE_TEXT) 8.dp else 0.dp),
horizontal = (if (item.messageType == V2TIMMessage.V2TIM_ELEM_TYPE_TEXT) 16.dp else 0.dp) horizontal = (if (item.messageType == V2TIMMessage.V2TIM_ELEM_TYPE_TEXT) 16.dp else 0.dp)
@@ -463,7 +483,7 @@ fun ChatAiOtherItem(item: ChatItem) {
text = item.message, text = item.message,
style = TextStyle( style = TextStyle(
color = AppColors.text, color = AppColors.text,
fontSize = 16.sp, fontSize = 14.sp,
), ),
textAlign = TextAlign.Start textAlign = TextAlign.Start
) )
@@ -510,6 +530,7 @@ fun ChatAiInput(
onSendImage: (Uri?) -> Unit = {}, onSendImage: (Uri?) -> Unit = {},
onSend: (String) -> Unit = {}, onSend: (String) -> Unit = {},
) { ) {
val context = LocalContext.current
val navigationBarHeight = with(LocalDensity.current) { val navigationBarHeight = with(LocalDensity.current) {
WindowInsets.navigationBars.getBottom(this).toDp() WindowInsets.navigationBars.getBottom(this).toDp()
} }
@@ -525,9 +546,8 @@ fun ChatAiInput(
), label = "" ), label = ""
) )
// 在 isKeyboardOpen 变化时立即更新 inputBarHeight 的动画目标值
LaunchedEffect(isKeyboardOpen) { LaunchedEffect(isKeyboardOpen) {
inputBarHeight // 触发 inputBarHeight 的重组 inputBarHeight
} }
val focusManager = LocalFocusManager.current val focusManager = LocalFocusManager.current
val windowInsets = WindowInsets.ime val windowInsets = WindowInsets.ime
@@ -549,71 +569,83 @@ fun ChatAiInput(
onSendImage(uri) onSendImage(uri)
} }
} }
Box( modifier = Modifier
.fillMaxWidth()
.padding(start = 16.dp, end = 16.dp, bottom = 12.dp),){
Row( Row(
modifier = Modifier
.fillMaxWidth()
.padding(horizontal = 16.dp)
.padding(bottom = inputBarHeight)
) {
Box(
modifier = Modifier modifier = Modifier
.weight(1f) .fillMaxWidth()
.clip(RoundedCornerShape(16.dp)) .clip(RoundedCornerShape(20.dp))
.background(appColors.background) .background(appColors.decentBackground)
.padding(horizontal = 16.dp), .padding(start = 16.dp, end = 8.dp, top = 2.dp, bottom = 2.dp),
contentAlignment = Alignment.CenterStart, verticalAlignment = Alignment.CenterVertically,
) { ) {
BasicTextField( Box(
value = text,
onValueChange = {
text = it
},
textStyle = TextStyle(
color = appColors.text,
fontSize = 16.sp
),
cursorBrush = SolidColor(appColors.text),
modifier = Modifier modifier = Modifier
.fillMaxWidth() .weight(1f)
.padding(vertical = 8.dp) ) {
.onFocusChanged { focusState -> BasicTextField(
isKeyboardOpen = focusState.isFocused value = text,
} onValueChange = {
.pointerInput(Unit) { text = it
awaitPointerEventScope { },
keyboardController = softwareKeyboardController textStyle = TextStyle(
awaitFirstDown().also { color = appColors.text,
keyboardController?.show() fontSize = 16.sp
),
cursorBrush = SolidColor(appColors.text),
singleLine = true,
modifier = Modifier
.fillMaxWidth()
.padding(vertical = 8.dp)
.onFocusChanged { focusState ->
isKeyboardOpen = focusState.isFocused
}
.pointerInput(Unit) {
awaitPointerEventScope {
keyboardController = softwareKeyboardController
awaitFirstDown().also {
keyboardController?.show()
}
} }
},
keyboardOptions = KeyboardOptions(imeAction = ImeAction.Done),
keyboardActions = KeyboardActions(
onDone = {
keyboardController?.hide()
} }
}, )
keyboardOptions = KeyboardOptions(imeAction = ImeAction.Done),
keyboardActions = KeyboardActions(
onDone = {
keyboardController?.hide()
}
) )
) }
}
Spacer(modifier = Modifier.width(8.dp)) Spacer(modifier = Modifier.width(8.dp))
Crossfade( Crossfade(
targetState = text.isNotEmpty(), animationSpec = tween(500), targetState = text.isNotEmpty(), animationSpec = tween(500),
label = "" label = ""
) { isNotEmpty -> ) { isNotEmpty ->
Icon( val alpha by animateFloatAsState(
painter = painterResource(id = R.drawable.rider_pro_video_share), targetValue = if (isNotEmpty) 1f else 0.5f,
contentDescription = "Emoji", animationSpec = tween(300)
modifier = Modifier )
.size(32.dp) Image(
.noRippleClickable { painter = painterResource(R.mipmap.rider_pro_im_send),
if (text.isNotEmpty()) { modifier = Modifier
onSend(text) .size(24.dp)
text = "" .alpha(alpha)
} .noRippleClickable {
}, if (text.isNotEmpty()) {
tint = if (isNotEmpty) appColors.main else appColors.chatActionColor if (NetworkUtils.isNetworkAvailable(context)) {
) onSend(text)
text = ""
} else {
android.widget.Toast.makeText(context, "网络连接异常,请检查网络设置", android.widget.Toast.LENGTH_SHORT).show()
}
}
},
contentDescription = null,
)
}
} }
} }
} }

View File

@@ -83,6 +83,7 @@ import com.aiosman.ravenow.ui.composables.DropdownMenu
import com.aiosman.ravenow.ui.composables.MenuItem import com.aiosman.ravenow.ui.composables.MenuItem
import com.aiosman.ravenow.ui.composables.StatusBarSpacer import com.aiosman.ravenow.ui.composables.StatusBarSpacer
import com.aiosman.ravenow.ui.modifiers.noRippleClickable import com.aiosman.ravenow.ui.modifiers.noRippleClickable
import com.aiosman.ravenow.utils.NetworkUtils
import com.tencent.imsdk.v2.V2TIMMessage import com.tencent.imsdk.v2.V2TIMMessage
import kotlinx.coroutines.launch import kotlinx.coroutines.launch
import java.util.UUID import java.util.UUID
@@ -231,12 +232,16 @@ fun ChatScreen(userId: String) {
) { ) {
isMenuExpanded = false isMenuExpanded = false
viewModel.viewModelScope.launch { if (NetworkUtils.isNetworkAvailable(context)) {
if (viewModel.notificationStrategy == "mute") { viewModel.viewModelScope.launch {
viewModel.updateNotificationStrategy("active") if (viewModel.notificationStrategy == "mute") {
} else { viewModel.updateNotificationStrategy("active")
viewModel.updateNotificationStrategy("mute") } else {
viewModel.updateNotificationStrategy("mute")
}
} }
} else {
android.widget.Toast.makeText(context, "网络连接异常,请检查网络设置", android.widget.Toast.LENGTH_SHORT).show()
} }
} }
), ),
@@ -262,7 +267,11 @@ fun ChatScreen(userId: String) {
ChatInput( ChatInput(
onSendImage = { onSendImage = {
it?.let { it?.let {
viewModel.sendImageMessage(it, context) if (NetworkUtils.isNetworkAvailable(context)) {
viewModel.sendImageMessage(it, context)
} else {
android.widget.Toast.makeText(context, "网络连接异常,请检查网络设置", android.widget.Toast.LENGTH_SHORT).show()
}
} }
}, },
) { ) {
@@ -443,11 +452,11 @@ fun ChatOtherItem(item: ChatItem) {
) { ) {
Box( Box(
modifier = Modifier modifier = Modifier
.size(24.dp) .size(40.dp)
.clip(RoundedCornerShape(24.dp)) .clip(RoundedCornerShape(40.dp))
) { ) {
CustomAsyncImage( CustomAsyncImage(
imageUrl = item.avatar.replace("storage/avatars/", "/avatar/"), imageUrl = item.avatar,
modifier = Modifier.fillMaxSize(), modifier = Modifier.fillMaxSize(),
contentDescription = "avatar" contentDescription = "avatar"
) )
@@ -460,12 +469,13 @@ fun ChatOtherItem(item: ChatItem) {
min = 20.dp, min = 20.dp,
max = (if (item.messageType == V2TIMMessage.V2TIM_ELEM_TYPE_TEXT) 250.dp else 150.dp) max = (if (item.messageType == V2TIMMessage.V2TIM_ELEM_TYPE_TEXT) 250.dp else 150.dp)
) )
.clip(RoundedCornerShape(20.dp)) .clip(RoundedCornerShape(8.dp))
.background(AppColors.bubbleBackground) .background(AppColors.background)
.padding( .padding(
vertical = (if (item.messageType == V2TIMMessage.V2TIM_ELEM_TYPE_TEXT) 8.dp else 0.dp), vertical = (if (item.messageType == V2TIMMessage.V2TIM_ELEM_TYPE_TEXT) 8.dp else 0.dp),
horizontal = (if (item.messageType == V2TIMMessage.V2TIM_ELEM_TYPE_TEXT) 16.dp else 0.dp) horizontal = (if (item.messageType == V2TIMMessage.V2TIM_ELEM_TYPE_TEXT) 16.dp else 0.dp)
) )
.padding(bottom = (if (item.messageType == V2TIMMessage.V2TIM_ELEM_TYPE_TEXT) 3.dp else 0.dp))
) { ) {
when (item.messageType) { when (item.messageType) {
V2TIMMessage.V2TIM_ELEM_TYPE_TEXT -> { V2TIMMessage.V2TIM_ELEM_TYPE_TEXT -> {
@@ -473,7 +483,7 @@ fun ChatOtherItem(item: ChatItem) {
text = item.message, text = item.message,
style = TextStyle( style = TextStyle(
color = AppColors.text, color = AppColors.text,
fontSize = 14.sp, fontSize = 16.sp,
), ),
textAlign = TextAlign.Start textAlign = TextAlign.Start
) )
@@ -489,7 +499,7 @@ fun ChatOtherItem(item: ChatItem) {
else -> { else -> {
Text( Text(
text = "不支持的消息类型", text = "Unsupported message type",
style = TextStyle( style = TextStyle(
color = AppColors.text, color = AppColors.text,
fontSize = 16.sp, fontSize = 16.sp,
@@ -498,6 +508,7 @@ fun ChatOtherItem(item: ChatItem) {
} }
} }
} }
} }
} }
@@ -519,6 +530,7 @@ fun ChatInput(
onSendImage: (Uri?) -> Unit = {}, onSendImage: (Uri?) -> Unit = {},
onSend: (String) -> Unit = {}, onSend: (String) -> Unit = {},
) { ) {
val context = LocalContext.current
val navigationBarHeight = with(LocalDensity.current) { val navigationBarHeight = with(LocalDensity.current) {
WindowInsets.navigationBars.getBottom(this).toDp() WindowInsets.navigationBars.getBottom(this).toDp()
} }
@@ -614,14 +626,18 @@ fun ChatInput(
modifier = Modifier modifier = Modifier
.size(30.dp) .size(30.dp)
.noRippleClickable { .noRippleClickable {
imagePickUpLauncher.launch( if (NetworkUtils.isNetworkAvailable(context)) {
Intent.createChooser( imagePickUpLauncher.launch(
Intent(Intent.ACTION_GET_CONTENT).apply { Intent.createChooser(
type = "image/*" Intent(Intent.ACTION_GET_CONTENT).apply {
}, type = "image/*"
"Select Image" },
"Select Image"
)
) )
) } else {
android.widget.Toast.makeText(context, "网络连接异常,请检查网络设置", android.widget.Toast.LENGTH_SHORT).show()
}
}, },
) )
@@ -634,19 +650,23 @@ fun ChatInput(
targetValue = if (isNotEmpty) 1f else 0.5f, targetValue = if (isNotEmpty) 1f else 0.5f,
animationSpec = tween(300) animationSpec = tween(300)
) )
Image( Image(
painter = painterResource(R.mipmap.rider_pro_im_send), painter = painterResource(R.mipmap.rider_pro_im_send),
modifier = Modifier modifier = Modifier
.size(24.dp) .size(24.dp)
.alpha(alpha) .alpha(alpha)
.noRippleClickable { .noRippleClickable {
if (text.isNotEmpty()) { if (text.isNotEmpty()) {
if (NetworkUtils.isNetworkAvailable(context)) {
onSend(text) onSend(text)
text = "" text = ""
} else {
android.widget.Toast.makeText(context, "网络连接异常,请检查网络设置", android.widget.Toast.LENGTH_SHORT).show()
} }
}, }
contentDescription = null, },
) contentDescription = null,
)
} }
} }
} }

View File

@@ -85,6 +85,7 @@ import com.aiosman.ravenow.ui.composables.MenuItem
import com.aiosman.ravenow.ui.composables.StatusBarSpacer import com.aiosman.ravenow.ui.composables.StatusBarSpacer
import com.aiosman.ravenow.ui.modifiers.noRippleClickable import com.aiosman.ravenow.ui.modifiers.noRippleClickable
import com.aiosman.ravenow.ui.navigateToGroupInfo import com.aiosman.ravenow.ui.navigateToGroupInfo
import com.aiosman.ravenow.utils.NetworkUtils
import com.tencent.imsdk.v2.V2TIMMessage import com.tencent.imsdk.v2.V2TIMMessage
import kotlinx.coroutines.launch import kotlinx.coroutines.launch
import java.util.UUID import java.util.UUID
@@ -257,7 +258,11 @@ fun GroupChatScreen(groupId: String,name: String,avatar: String,) {
GroupChatInput( GroupChatInput(
onSendImage = { uri -> onSendImage = { uri ->
uri?.let { uri?.let {
viewModel.sendImageMessage(it, context) if (NetworkUtils.isNetworkAvailable(context)) {
viewModel.sendImageMessage(it, context)
} else {
android.widget.Toast.makeText(context, "网络连接异常,请检查网络设置", android.widget.Toast.LENGTH_SHORT).show()
}
} }
}, },
) { message -> ) { message ->
@@ -566,6 +571,7 @@ fun GroupChatInput(
onSendImage: (Uri?) -> Unit = {}, onSendImage: (Uri?) -> Unit = {},
onSend: (String) -> Unit = {}, onSend: (String) -> Unit = {},
) { ) {
val context = LocalContext.current
val navigationBarHeight = with(LocalDensity.current) { val navigationBarHeight = with(LocalDensity.current) {
WindowInsets.navigationBars.getBottom(this).toDp() WindowInsets.navigationBars.getBottom(this).toDp()
} }
@@ -670,8 +676,12 @@ fun GroupChatInput(
.alpha(alpha) .alpha(alpha)
.noRippleClickable { .noRippleClickable {
if (text.isNotEmpty()) { if (text.isNotEmpty()) {
onSend(text) if (NetworkUtils.isNetworkAvailable(context)) {
text = "" onSend(text)
text = ""
} else {
android.widget.Toast.makeText(context, "网络连接异常,请检查网络设置", android.widget.Toast.LENGTH_SHORT).show()
}
} }
}, },
contentDescription = null, contentDescription = null,

View File

@@ -14,6 +14,7 @@ import androidx.compose.material3.Text
import androidx.compose.runtime.Composable import androidx.compose.runtime.Composable
import androidx.compose.ui.Alignment import androidx.compose.ui.Alignment
import androidx.compose.ui.Modifier import androidx.compose.ui.Modifier
import androidx.compose.ui.draw.clip
import androidx.compose.ui.res.painterResource import androidx.compose.ui.res.painterResource
import androidx.compose.ui.text.font.FontWeight import androidx.compose.ui.text.font.FontWeight
import androidx.compose.ui.unit.dp import androidx.compose.ui.unit.dp
@@ -24,7 +25,6 @@ data class MenuItem(
val title: String, val title: String,
val icon: Int, val icon: Int,
val action: () -> Unit val action: () -> Unit
) )
@Composable @Composable
@@ -36,50 +36,42 @@ fun DropdownMenu(
) { ) {
val AppColors = LocalAppTheme.current val AppColors = LocalAppTheme.current
MaterialTheme( androidx.compose.material3.DropdownMenu(
shapes = MaterialTheme.shapes.copy( expanded = expanded,
extraSmall = RoundedCornerShape( onDismissRequest = onDismissRequest,
16.dp modifier = Modifier
) .let {
) if (width != null) it.width(width.dp) else it
}
.background(color = AppColors.background),
) { ) {
androidx.compose.material3.DropdownMenu( for (item in menuItems) {
expanded = expanded, Box(
onDismissRequest = onDismissRequest, modifier = Modifier
modifier = Modifier .padding(vertical = 14.dp, horizontal = 24.dp)
.let { .noRippleClickable {
if (width != null) it.width(width.dp) else it item.action()
}
.background(AppColors.background)
) {
for (item in menuItems) {
Box(
modifier = Modifier
.padding(vertical = 14.dp, horizontal = 24.dp)
.noRippleClickable {
item.action()
}) {
Row(
verticalAlignment = Alignment.CenterVertically
) {
Text(
item.title,
fontWeight = FontWeight.W500,
color = AppColors.text,
)
if (width != null) {
Spacer(modifier = Modifier.weight(1f))
} else {
Spacer(modifier = Modifier.width(16.dp))
}
Icon(
painter = painterResource(id = item.icon),
contentDescription = "",
modifier = Modifier.size(24.dp),
tint = AppColors.text
)
} }
) {
Row(
verticalAlignment = Alignment.CenterVertically
) {
Text(
item.title,
fontWeight = FontWeight.W500,
color = AppColors.text,
)
if (width != null) {
Spacer(modifier = Modifier.weight(1f))
} else {
Spacer(modifier = Modifier.width(16.dp))
}
Icon(
painter = painterResource(id = item.icon),
contentDescription = "",
modifier = Modifier.size(24.dp),
tint = AppColors.text
)
} }
} }
} }

View File

@@ -363,7 +363,7 @@ fun IndexScreen() {
@Composable @Composable
fun Home() { fun Home() {
val systemUiController = rememberSystemUiController() val systemUiController = rememberSystemUiController()
LaunchedEffect(Unit) { LaunchedEffect(AppState.darkMode) {
systemUiController.setStatusBarColor(Color.Transparent, darkIcons = !AppState.darkMode) systemUiController.setStatusBarColor(Color.Transparent, darkIcons = !AppState.darkMode)
} }
Column( Column(
@@ -380,7 +380,7 @@ fun Home() {
@Composable @Composable
fun Street() { fun Street() {
val systemUiController = rememberSystemUiController() val systemUiController = rememberSystemUiController()
LaunchedEffect(Unit) { LaunchedEffect(AppState.darkMode) {
systemUiController.setStatusBarColor(Color.Transparent, darkIcons = !AppState.darkMode) systemUiController.setStatusBarColor(Color.Transparent, darkIcons = !AppState.darkMode)
} }
Column( Column(
@@ -396,7 +396,7 @@ fun Street() {
@Composable @Composable
fun Add() { fun Add() {
val systemUiController = rememberSystemUiController() val systemUiController = rememberSystemUiController()
LaunchedEffect(Unit) { LaunchedEffect(AppState.darkMode) {
systemUiController.setStatusBarColor(Color.Black, darkIcons = !AppState.darkMode) systemUiController.setStatusBarColor(Color.Black, darkIcons = !AppState.darkMode)
} }
Column( Column(
@@ -413,7 +413,7 @@ fun Add() {
@Composable @Composable
fun Video() { fun Video() {
val systemUiController = rememberSystemUiController() val systemUiController = rememberSystemUiController()
LaunchedEffect(Unit) { LaunchedEffect(AppState.darkMode) {
systemUiController.setStatusBarColor(Color.Black, darkIcons = false) systemUiController.setStatusBarColor(Color.Black, darkIcons = false)
} }
Column( Column(
@@ -430,7 +430,7 @@ fun Video() {
@Composable @Composable
fun Profile() { fun Profile() {
val systemUiController = rememberSystemUiController() val systemUiController = rememberSystemUiController()
LaunchedEffect(Unit) { LaunchedEffect(AppState.darkMode) {
systemUiController.setStatusBarColor(Color.Transparent, !AppState.darkMode) systemUiController.setStatusBarColor(Color.Transparent, !AppState.darkMode)
} }
Column( Column(
@@ -446,7 +446,7 @@ fun Profile() {
@Composable @Composable
fun Notifications() { fun Notifications() {
val systemUiController = rememberSystemUiController() val systemUiController = rememberSystemUiController()
LaunchedEffect(Unit) { LaunchedEffect(AppState.darkMode) {
systemUiController.setStatusBarColor(Color.Transparent, !AppState.darkMode) systemUiController.setStatusBarColor(Color.Transparent, !AppState.darkMode)
} }
Column( Column(

View File

@@ -44,6 +44,7 @@ import com.aiosman.ravenow.R
import com.aiosman.ravenow.ui.NavigationRoute import com.aiosman.ravenow.ui.NavigationRoute
import com.aiosman.ravenow.ui.composables.CustomAsyncImage import com.aiosman.ravenow.ui.composables.CustomAsyncImage
import com.aiosman.ravenow.ui.modifiers.noRippleClickable import com.aiosman.ravenow.ui.modifiers.noRippleClickable
import com.aiosman.ravenow.utils.NetworkUtils
/** /**
* 智能体聊天列表页面 * 智能体聊天列表页面
@@ -123,8 +124,12 @@ fun AgentChatListScreen() {
AgentChatListViewModel.goToUserDetail(conv, navController) AgentChatListViewModel.goToUserDetail(conv, navController)
}, },
onChatClick = { conv -> onChatClick = { conv ->
model.createSingleChat(conv.trtcUserId) if (NetworkUtils.isNetworkAvailable(context)) {
model.goToChatAi(conv.trtcUserId,navController) model.createSingleChat(conv.trtcUserId)
model.goToChatAi(conv.trtcUserId,navController)
} else {
android.widget.Toast.makeText(context, "网络连接异常,请检查网络设置", android.widget.Toast.LENGTH_SHORT).show()
}
} }
) )

View File

@@ -32,6 +32,7 @@ import com.aiosman.ravenow.LocalNavController
import com.aiosman.ravenow.R import com.aiosman.ravenow.R
import com.aiosman.ravenow.ui.composables.CustomAsyncImage import com.aiosman.ravenow.ui.composables.CustomAsyncImage
import com.aiosman.ravenow.ui.modifiers.noRippleClickable import com.aiosman.ravenow.ui.modifiers.noRippleClickable
import com.aiosman.ravenow.utils.NetworkUtils
@OptIn(ExperimentalMaterialApi::class) @OptIn(ExperimentalMaterialApi::class)
@Composable @Composable
@@ -106,7 +107,11 @@ fun FriendChatListScreen() {
FriendChatListViewModel.goToUserDetail(conv, navController) FriendChatListViewModel.goToUserDetail(conv, navController)
}, },
onChatClick = { conv -> onChatClick = { conv ->
FriendChatListViewModel.goToChat(conv, navController) if (NetworkUtils.isNetworkAvailable(context)) {
FriendChatListViewModel.goToChat(conv, navController)
} else {
android.widget.Toast.makeText(context, "网络连接异常,请检查网络设置", android.widget.Toast.LENGTH_SHORT).show()
}
} }
) )

View File

@@ -1,8 +1,6 @@
package com.aiosman.ravenow.ui.index.tabs.message.tab package com.aiosman.ravenow.ui.index.tabs.message.tab
import androidx.compose.foundation.Image
import androidx.compose.foundation.background import androidx.compose.foundation.background
import androidx.compose.foundation.isSystemInDarkTheme
import androidx.compose.foundation.layout.* import androidx.compose.foundation.layout.*
import androidx.compose.foundation.lazy.LazyColumn import androidx.compose.foundation.lazy.LazyColumn
import androidx.compose.foundation.lazy.itemsIndexed import androidx.compose.foundation.lazy.itemsIndexed
@@ -16,14 +14,11 @@ import androidx.compose.material3.CircularProgressIndicator
import androidx.compose.material3.HorizontalDivider import androidx.compose.material3.HorizontalDivider
import androidx.compose.material3.Text import androidx.compose.material3.Text
import androidx.compose.runtime.Composable import androidx.compose.runtime.Composable
import androidx.compose.runtime.DisposableEffect
import androidx.compose.runtime.LaunchedEffect import androidx.compose.runtime.LaunchedEffect
import androidx.compose.ui.Alignment import androidx.compose.ui.Alignment
import androidx.compose.ui.Modifier import androidx.compose.ui.Modifier
import androidx.compose.ui.draw.clip import androidx.compose.ui.draw.clip
import androidx.compose.ui.graphics.Color
import androidx.compose.ui.platform.LocalContext import androidx.compose.ui.platform.LocalContext
import androidx.compose.ui.res.painterResource
import androidx.compose.ui.res.stringResource import androidx.compose.ui.res.stringResource
import androidx.compose.ui.text.font.FontWeight import androidx.compose.ui.text.font.FontWeight
import androidx.compose.ui.text.style.TextOverflow import androidx.compose.ui.text.style.TextOverflow
@@ -34,6 +29,7 @@ import com.aiosman.ravenow.LocalNavController
import com.aiosman.ravenow.R import com.aiosman.ravenow.R
import com.aiosman.ravenow.ui.composables.CustomAsyncImage import com.aiosman.ravenow.ui.composables.CustomAsyncImage
import com.aiosman.ravenow.ui.modifiers.noRippleClickable import com.aiosman.ravenow.ui.modifiers.noRippleClickable
import com.aiosman.ravenow.utils.NetworkUtils
@OptIn(ExperimentalMaterialApi::class) @OptIn(ExperimentalMaterialApi::class)
@Composable @Composable
@@ -51,15 +47,6 @@ fun GroupChatListScreen() {
LaunchedEffect(Unit) { LaunchedEffect(Unit) {
GroupChatListViewModel.refreshPager(context = context) GroupChatListViewModel.refreshPager(context = context)
// 初始化消息监听器
GroupChatListViewModel.initMessageListener(context)
}
// 在组件销毁时清理监听器
DisposableEffect(Unit) {
onDispose {
GroupChatListViewModel.removeMessageListener()
}
} }
@@ -79,31 +66,18 @@ fun GroupChatListScreen() {
.fillMaxSize() .fillMaxSize()
.padding(16.dp), .padding(16.dp),
horizontalAlignment = Alignment.CenterHorizontally, horizontalAlignment = Alignment.CenterHorizontally,
verticalArrangement = Arrangement.Center
) { ) {
Spacer(modifier = Modifier.height(80.dp))
Image(
painter = painterResource(id = if (isSystemInDarkTheme()) {
R.mipmap.icon_group_chat_empty // 深色模式图片
} else {
R.mipmap.icon_group_chat_empty // 浅色模式图片
}),
contentDescription = "null data",
modifier = Modifier
.size(140.dp)
)
Spacer(modifier = Modifier.height(24.dp))
Text( Text(
text = stringResource(R.string.group_chat_empty_title), text = "暂无群聊",
color = AppColors.text, color = AppColors.text,
fontSize = 16.sp, fontSize = 16.sp,
fontWeight = FontWeight.W600 fontWeight = FontWeight.W600
) )
Spacer(modifier = Modifier.height(8.dp)) Spacer(modifier = Modifier.height(8.dp))
Text( Text(
text = stringResource(R.string.group_chat_empty_subtitle), text = "您还没有加入任何群聊",
color = AppColors.text, color = AppColors.secondaryText,
fontSize = 14.sp fontSize = 14.sp
) )
} }
@@ -121,10 +95,20 @@ fun GroupChatListScreen() {
GroupChatListViewModel.goToGroupDetail(conv, navController) GroupChatListViewModel.goToGroupDetail(conv, navController)
}, },
onChatClick = { conv -> onChatClick = { conv ->
GroupChatListViewModel.goToChat(conv, navController) if (NetworkUtils.isNetworkAvailable(context)) {
GroupChatListViewModel.goToChat(conv, navController)
} else {
android.widget.Toast.makeText(context, "网络连接异常,请检查网络设置", android.widget.Toast.LENGTH_SHORT).show()
}
} }
) )
if (index < GroupChatListViewModel.groupChatList.size - 1) {
HorizontalDivider(
modifier = Modifier.padding(horizontal = 24.dp),
color = AppColors.divider
)
}
} }
if (GroupChatListViewModel.isLoading && GroupChatListViewModel.groupChatList.isNotEmpty()) { if (GroupChatListViewModel.isLoading && GroupChatListViewModel.groupChatList.isNotEmpty()) {
@@ -180,11 +164,10 @@ fun GroupChatItem(
Row( Row(
modifier = Modifier modifier = Modifier
.fillMaxWidth() .fillMaxWidth()
.padding(horizontal = 16.dp, vertical = 12.dp) .padding(horizontal = 24.dp, vertical = 12.dp)
.noRippleClickable { .noRippleClickable {
onChatClick(conversation) onChatClick(conversation)
}, }
verticalAlignment = Alignment.CenterVertically
) { ) {
Box { Box {
CustomAsyncImage( CustomAsyncImage(
@@ -202,9 +185,9 @@ fun GroupChatItem(
Column( Column(
modifier = Modifier modifier = Modifier
.weight(1f) .weight(1f)
.padding(start = 12.dp, top = 2.dp), .padding(start = 12.dp)
verticalArrangement = Arrangement.Center
) { ) {
Row( Row(
modifier = Modifier.fillMaxWidth(), modifier = Modifier.fillMaxWidth(),
@@ -212,22 +195,22 @@ fun GroupChatItem(
) { ) {
Text( Text(
text = conversation.groupName, text = conversation.groupName,
fontSize = 14.sp, fontSize = 16.sp,
fontWeight = FontWeight.Bold, fontWeight = FontWeight.Bold,
color = AppColors.text, color = AppColors.text,
modifier = Modifier.weight(1f) modifier = Modifier.weight(1f)
) )
Spacer(modifier = Modifier.width(6.dp)) Spacer(modifier = Modifier.width(8.dp))
Text( Text(
text = conversation.lastMessageTime, text = conversation.lastMessageTime,
fontSize = 11.sp, fontSize = 12.sp,
color = AppColors.secondaryText color = AppColors.secondaryText
) )
} }
Spacer(modifier = Modifier.height(6.dp)) Spacer(modifier = Modifier.height(4.dp))
Row( Row(
modifier = Modifier.fillMaxWidth(), modifier = Modifier.fillMaxWidth(),
@@ -235,29 +218,30 @@ fun GroupChatItem(
) { ) {
Text( Text(
text = "${if (conversation.isSelf) stringResource(R.string.friend_chat_me_prefix) else ""}${conversation.displayText}", text = "${if (conversation.isSelf) stringResource(R.string.friend_chat_me_prefix) else ""}${conversation.displayText}",
fontSize = 12.sp, fontSize = 14.sp,
color = AppColors.secondaryText, color = AppColors.secondaryText,
maxLines = 1, maxLines = 1,
overflow = TextOverflow.Ellipsis, overflow = TextOverflow.Ellipsis,
modifier = Modifier.weight(1f) modifier = Modifier.weight(1f)
) )
Spacer(modifier = Modifier.width(10.dp)) Spacer(modifier = Modifier.width(8.dp))
if (conversation.unreadCount > 0) { if (conversation.unreadCount > 0) {
Box( Box(
modifier = Modifier modifier = Modifier
.size(if (conversation.unreadCount > 99) 24.dp else 20.dp) .size(if (conversation.unreadCount > 99) 24.dp else 20.dp)
.background( .background(
color = Color(0xFFFF3B30), color = AppColors.main,
shape = CircleShape shape = CircleShape
), ),
contentAlignment = Alignment.Center contentAlignment = Alignment.Center
) { ) {
Text( Text(
text = if (conversation.unreadCount > 99) "99+" else conversation.unreadCount.toString(), text = if (conversation.unreadCount > 99) "99+" else conversation.unreadCount.toString(),
color = Color.White, color = AppColors.mainText,
fontSize = if (conversation.unreadCount > 99) 11.sp else 12.sp, fontSize = if (conversation.unreadCount > 99) 9.sp else 10.sp,
fontWeight = FontWeight.Bold
) )
} }
} }

View File

@@ -26,6 +26,7 @@ import androidx.compose.foundation.lazy.LazyColumn
import androidx.compose.foundation.lazy.rememberLazyListState import androidx.compose.foundation.lazy.rememberLazyListState
import androidx.compose.foundation.lazy.grid.GridCells import androidx.compose.foundation.lazy.grid.GridCells
import androidx.compose.foundation.lazy.grid.LazyVerticalGrid import androidx.compose.foundation.lazy.grid.LazyVerticalGrid
import androidx.compose.foundation.lazy.grid.rememberLazyGridState
import androidx.compose.foundation.pager.HorizontalPager import androidx.compose.foundation.pager.HorizontalPager
import androidx.compose.foundation.pager.rememberPagerState import androidx.compose.foundation.pager.rememberPagerState
import androidx.compose.foundation.shape.CircleShape import androidx.compose.foundation.shape.CircleShape
@@ -140,6 +141,7 @@ fun ProfileV3(
val AppColors = appTheme val AppColors = appTheme
val systemUiController = rememberSystemUiController() val systemUiController = rememberSystemUiController()
val listState = rememberLazyListState() val listState = rememberLazyListState()
val gridState = rememberLazyGridState()
// observe list scrolling // observe list scrolling
val reachedListBottom by remember { val reachedListBottom by remember {
@@ -149,13 +151,28 @@ fun ProfileV3(
} }
} }
// load more if scrolled to bottom // observe grid scrolling for load more
val reachedGridBottom by remember {
derivedStateOf {
val lastVisibleItem = gridState.layoutInfo.visibleItemsInfo.lastOrNull()
lastVisibleItem?.index != 0 && lastVisibleItem?.index == gridState.layoutInfo.totalItemsCount - 2
}
}
// load more if scrolled to bottom of list
LaunchedEffect(reachedListBottom) { LaunchedEffect(reachedListBottom) {
if (reachedListBottom) { if (reachedListBottom) {
onLoadMore() onLoadMore()
} }
} }
// load more if scrolled to bottom of grid
LaunchedEffect(reachedGridBottom) {
if (reachedGridBottom) {
onLoadMore()
}
}
fun switchTheme() { fun switchTheme() {
@@ -451,15 +468,16 @@ fun ProfileV3(
pagerState = pagerState, pagerState = pagerState,
) )
Spacer(modifier = Modifier.height(8.dp)) Spacer(modifier = Modifier.height(8.dp))
HorizontalPager( HorizontalPager(
state = pagerState, state = pagerState,
) { idx -> ) { idx ->
when (idx) { when (idx) {
0 -> 0 ->
LazyVerticalGrid( LazyVerticalGrid(
columns = GridCells.Fixed(3), columns = GridCells.Fixed(3),
modifier = Modifier.fillMaxSize().padding(bottom = 8.dp), state = gridState,
) { modifier = Modifier.fillMaxSize().padding(bottom = 8.dp),
) {
items(moments.size) { idx -> items(moments.size) { idx ->
val moment = moments[idx] ?: return@items val moment = moments[idx] ?: return@items

View File

@@ -0,0 +1,71 @@
package com.aiosman.ravenow.utils
import android.content.Context
import android.net.ConnectivityManager
import android.net.Network
import android.net.NetworkCapabilities
import android.net.NetworkRequest
import android.os.Build
import kotlinx.coroutines.channels.awaitClose
import kotlinx.coroutines.flow.Flow
import kotlinx.coroutines.flow.callbackFlow
import kotlinx.coroutines.flow.distinctUntilChanged
object NetworkUtils {
/**
* 检查当前网络是否可用
*/
fun isNetworkAvailable(context: Context): Boolean {
val connectivityManager = context.getSystemService(Context.CONNECTIVITY_SERVICE) as ConnectivityManager
return if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.M) {
val network = connectivityManager.activeNetwork ?: return false
val activeNetwork = connectivityManager.getNetworkCapabilities(network) ?: return false
activeNetwork.hasCapability(NetworkCapabilities.NET_CAPABILITY_INTERNET) &&
activeNetwork.hasCapability(NetworkCapabilities.NET_CAPABILITY_VALIDATED)
} else {
@Suppress("DEPRECATION")
val networkInfo = connectivityManager.activeNetworkInfo
@Suppress("DEPRECATION")
networkInfo?.isConnected == true
}
}
/**
* 获取网络状态变化的Flow
*/
fun getNetworkStateFlow(context: Context): Flow<Boolean> = callbackFlow {
val connectivityManager = context.getSystemService(Context.CONNECTIVITY_SERVICE) as ConnectivityManager
val networkCallback = object : ConnectivityManager.NetworkCallback() {
override fun onAvailable(network: Network) {
trySend(true)
}
override fun onLost(network: Network) {
trySend(false)
}
override fun onCapabilitiesChanged(network: Network, networkCapabilities: NetworkCapabilities) {
val hasInternet = networkCapabilities.hasCapability(NetworkCapabilities.NET_CAPABILITY_INTERNET) &&
networkCapabilities.hasCapability(NetworkCapabilities.NET_CAPABILITY_VALIDATED)
trySend(hasInternet)
}
}
val networkRequest = NetworkRequest.Builder()
.addCapability(NetworkCapabilities.NET_CAPABILITY_INTERNET)
.build()
connectivityManager.registerNetworkCallback(networkRequest, networkCallback)
// 发送初始状态
trySend(isNetworkAvailable(context))
awaitClose {
connectivityManager.unregisterNetworkCallback(networkCallback)
}
}.distinctUntilChanged()
}