This commit is contained in:
callebtc
2025-07-14 17:05:49 +02:00
parent a1b44ee4dd
commit f44fc8b0aa
7 changed files with 414 additions and 37 deletions
@@ -27,7 +27,8 @@ import android.util.Log
@Composable
fun ParsedMessageContent(
elements: List<MessageElement>,
modifier: Modifier = Modifier
modifier: Modifier = Modifier,
onCashuPaymentClick: ((ParsedCashuToken) -> Unit)? = null
) {
// Use a Column with proper text and special element rendering
Column(
@@ -52,6 +53,7 @@ fun ParsedMessageContent(
// Show the payment chip on its own row
CashuPaymentChip(
token = element.token,
onPaymentClick = onCashuPaymentClick,
modifier = Modifier.padding(vertical = 2.dp)
)
}
@@ -96,14 +98,17 @@ fun TextRow(elements: List<MessageElement>) {
@Composable
fun CashuPaymentChip(
token: ParsedCashuToken,
modifier: Modifier = Modifier
modifier: Modifier = Modifier,
onPaymentClick: ((ParsedCashuToken) -> Unit)? = null
) {
Card(
modifier = modifier
.clickable { handleCashuPayment(token) },
.clickable {
onPaymentClick?.invoke(token) ?: handleCashuPayment(token)
},
shape = RoundedCornerShape(12.dp),
colors = CardDefaults.cardColors(
containerColor = Color(0xFF2E8B57) // Sea green
containerColor = Color(0xFF00C851) // bitchat green
),
elevation = CardDefaults.cardElevation(defaultElevation = 4.dp)
) {
@@ -113,36 +118,58 @@ fun CashuPaymentChip(
verticalAlignment = Alignment.CenterVertically,
horizontalArrangement = Arrangement.spacedBy(8.dp)
) {
// Payment icon
// Payment icon
Text(
text = "",
fontSize = 16.sp,
color = Color.White
)
// Payment text
Text(
text = "Cashu Payment: ${token.amount} ${token.unit}",
fontSize = 13.sp,
color = Color.White,
fontWeight = FontWeight.Medium,
fontFamily = FontFamily.Monospace
)
// Payment info
Column {
Text(
text = "Cashu Payment",
fontSize = 12.sp,
color = Color.White.copy(alpha = 0.9f),
fontWeight = FontWeight.Medium,
fontFamily = FontFamily.Monospace
)
Text(
text = "${token.amount} ${token.unit}",
fontSize = 14.sp,
color = Color.White,
fontWeight = FontWeight.Bold,
fontFamily = FontFamily.Monospace
)
if (token.memo?.isNotBlank() == true) {
Text(
text = "\"${token.memo}\"",
fontSize = 10.sp,
color = Color.White.copy(alpha = 0.8f),
fontFamily = FontFamily.Monospace
)
}
}
Spacer(modifier = Modifier.weight(1f))
// Receive button
Button(
onClick = { handleCashuPayment(token) },
modifier = Modifier.height(28.dp),
contentPadding = PaddingValues(horizontal = 8.dp, vertical = 0.dp),
onClick = {
onPaymentClick?.invoke(token) ?: handleCashuPayment(token)
},
modifier = Modifier.height(32.dp),
contentPadding = PaddingValues(horizontal = 12.dp, vertical = 0.dp),
colors = ButtonDefaults.buttonColors(
containerColor = Color.White,
contentColor = Color(0xFF2E8B57)
contentColor = Color(0xFF00C851)
)
) {
Text(
text = "Receive",
fontSize = 11.sp,
fontWeight = FontWeight.Bold
fontSize = 12.sp,
fontWeight = FontWeight.Bold,
fontFamily = FontFamily.Monospace
)
}
}
@@ -0,0 +1,110 @@
package com.bitchat.android.ui
import androidx.compose.material3.MaterialTheme
import androidx.compose.runtime.Composable
import androidx.compose.ui.graphics.Color
import java.util.regex.Pattern
/**
* Cashu token parsing utilities for chat messages
*/
object CashuTokenParser {
private const val CASHU_TOKEN_PATTERN = "cashu[A-Za-z0-9+/_-]{50,}"
private val cashuTokenRegex = Pattern.compile(CASHU_TOKEN_PATTERN)
/**
* Check if a string contains a Cashu token
*/
fun containsCashuToken(text: String): Boolean {
return cashuTokenRegex.matcher(text).find()
}
/**
* Extract Cashu token from text
*/
fun extractCashuToken(text: String): String? {
val matcher = cashuTokenRegex.matcher(text)
return if (matcher.find()) {
matcher.group()
} else {
null
}
}
/**
* Parse token to extract basic information without CDK
* This is a simplified version that tries to decode the token format
*/
fun parseTokenInfo(token: String): CashuTokenInfo? {
return try {
if (token.startsWith("cashu")) {
// For now, return mock info since we don't have CDK integration yet
// In a real implementation, this would use CDK to decode the token
CashuTokenInfo(
token = token,
amount = estimateTokenAmount(token), // Simple heuristic
mint = "Unknown Mint",
unit = "sat"
)
} else {
null
}
} catch (e: Exception) {
null
}
}
/**
* Simple heuristic to estimate token amount based on token length/structure
* This is just for demo purposes - real implementation would decode the token
*/
private fun estimateTokenAmount(token: String): Long {
return when {
token.length < 100 -> 1L
token.length < 200 -> 21L
token.length < 300 -> 100L
token.length < 500 -> 1000L
else -> 10000L
}
}
}
/**
* Simplified Cashu token information
*/
data class CashuTokenInfo(
val token: String,
val amount: Long,
val mint: String,
val unit: String
)
/**
* Colors for Cashu token highlighting
*/
@Composable
fun getCashuTokenColors(): Pair<Color, Color> {
val isDarkTheme = MaterialTheme.colorScheme.background.luminance() < 0.5f
return if (isDarkTheme) {
// Dark theme: bright orange background, dark text
Pair(
Color(0xFFFF9500).copy(alpha = 0.3f), // background
Color(0xFFFF9500) // text
)
} else {
// Light theme: light orange background, dark orange text
Pair(
Color(0xFFFF9500).copy(alpha = 0.15f), // background
Color(0xFFE67E00) // text
)
}
}
/**
* Extension function to get luminance from Color
*/
private fun Color.luminance(): Float {
return (0.299f * red + 0.587f * green + 0.114f * blue)
}
@@ -145,7 +145,8 @@ fun ChatHeaderContent(
onBackClick: () -> Unit,
onSidebarClick: () -> Unit,
onTripleClick: () -> Unit,
onShowAppInfo: () -> Unit
onShowAppInfo: () -> Unit,
onWalletClick: () -> Unit = {}
) {
val colorScheme = MaterialTheme.colorScheme
var tripleClickCount by remember { mutableStateOf(0) }
@@ -191,6 +192,7 @@ fun ChatHeaderContent(
}
},
onSidebarClick = onSidebarClick,
onWalletClick = onWalletClick,
viewModel = viewModel
)
}
@@ -346,6 +348,7 @@ private fun MainHeader(
onNicknameChange: (String) -> Unit,
onTitleClick: () -> Unit,
onSidebarClick: () -> Unit,
onWalletClick: () -> Unit = {},
viewModel: ChatViewModel
) {
val colorScheme = MaterialTheme.colorScheme
@@ -376,13 +379,30 @@ private fun MainHeader(
)
}
PeerCounter(
connectedPeers = connectedPeers.filter { it != viewModel.meshService.myPeerID },
joinedChannels = joinedChannels,
hasUnreadChannels = hasUnreadChannels,
hasUnreadPrivateMessages = hasUnreadPrivateMessages,
isConnected = isConnected,
onClick = onSidebarClick
)
Row(verticalAlignment = Alignment.CenterVertically) {
// Wallet button
IconButton(
onClick = onWalletClick,
modifier = Modifier.size(32.dp)
) {
Icon(
imageVector = Icons.Filled.AccountBalanceWallet,
contentDescription = "Open Wallet",
modifier = Modifier.size(18.dp),
tint = Color(0xFF00C851) // bitchat green
)
}
Spacer(modifier = Modifier.width(8.dp))
PeerCounter(
connectedPeers = connectedPeers.filter { it != viewModel.meshService.myPeerID },
joinedChannels = joinedChannels,
hasUnreadChannels = hasUnreadChannels,
hasUnreadPrivateMessages = hasUnreadPrivateMessages,
isConnected = isConnected,
onClick = onSidebarClick
)
}
}
}
@@ -33,6 +33,7 @@ import androidx.compose.ui.zIndex
import com.bitchat.android.model.BitchatMessage
import com.bitchat.android.model.DeliveryStatus
import com.bitchat.android.mesh.BluetoothMeshService
import com.bitchat.android.parsing.ParsedCashuToken
import java.text.SimpleDateFormat
import java.util.*
@@ -48,7 +49,10 @@ import java.util.*
*/
@OptIn(ExperimentalMaterial3Api::class)
@Composable
fun ChatScreen(viewModel: ChatViewModel) {
fun ChatScreen(
viewModel: ChatViewModel,
onWalletClick: () -> Unit = {}
) {
val colorScheme = MaterialTheme.colorScheme
val messages by viewModel.messages.observeAsState(emptyList())
val connectedPeers by viewModel.connectedPeers.observeAsState(emptyList())
@@ -105,6 +109,12 @@ fun ChatScreen(viewModel: ChatViewModel) {
messages = displayMessages,
currentUserNickname = nickname,
meshService = viewModel.meshService,
onCashuPaymentClick = { token ->
// Open wallet with the receive dialog pre-filled with this token
onWalletClick()
// TODO: We need to pass the token to the wallet somehow
// This could be done through a shared state, intent, or callback
},
modifier = Modifier.fillMaxSize()
)
}
@@ -144,7 +154,8 @@ fun ChatScreen(viewModel: ChatViewModel) {
colorScheme = colorScheme,
onSidebarToggle = { viewModel.showSidebar() },
onShowAppInfo = { viewModel.showAppInfo() },
onPanicClear = { viewModel.panicClearAllData() }
onPanicClear = { viewModel.panicClearAllData() },
onWalletClick = onWalletClick
)
// Sidebar overlay
@@ -249,7 +260,8 @@ private fun ChatFloatingHeader(
colorScheme: ColorScheme,
onSidebarToggle: () -> Unit,
onShowAppInfo: () -> Unit,
onPanicClear: () -> Unit
onPanicClear: () -> Unit,
onWalletClick: () -> Unit = {}
) {
Surface(
modifier = Modifier
@@ -275,7 +287,8 @@ private fun ChatFloatingHeader(
},
onSidebarClick = onSidebarToggle,
onTripleClick = onPanicClear,
onShowAppInfo = onShowAppInfo
onShowAppInfo = onShowAppInfo,
onWalletClick = onWalletClick
)
},
colors = TopAppBarDefaults.topAppBarColors(
@@ -31,8 +31,11 @@ fun MainAppScreen(
Column(modifier = modifier.fillMaxSize()) {
// Content based on selected tab
when (selectedTab) {
0 -> ChatScreen(viewModel = chatViewModel) // Existing chat screen
1 -> WalletScreen(walletViewModel = walletViewModel) // New wallet screen
0 -> ChatScreen(
viewModel = chatViewModel,
onWalletClick = { selectedTab = 1 } // Switch to wallet tab when header button is clicked
)
1 -> WalletScreen(walletViewModel = walletViewModel)
}
// Bottom Navigation
@@ -20,6 +20,7 @@ import com.bitchat.android.model.DeliveryStatus
import com.bitchat.android.mesh.BluetoothMeshService
import com.bitchat.android.parsing.MessageElement
import com.bitchat.android.parsing.ParsedMessageContent
import com.bitchat.android.parsing.ParsedCashuToken
import java.text.SimpleDateFormat
import java.util.*
@@ -33,7 +34,8 @@ fun MessagesList(
messages: List<BitchatMessage>,
currentUserNickname: String,
meshService: BluetoothMeshService,
modifier: Modifier = Modifier
modifier: Modifier = Modifier,
onCashuPaymentClick: ((ParsedCashuToken) -> Unit)? = null
) {
val listState = rememberLazyListState()
@@ -54,7 +56,8 @@ fun MessagesList(
MessageItem(
message = message,
currentUserNickname = currentUserNickname,
meshService = meshService
meshService = meshService,
onCashuPaymentClick = onCashuPaymentClick
)
}
}
@@ -65,7 +68,8 @@ fun MessagesList(
fun MessageItem(
message: BitchatMessage,
currentUserNickname: String,
meshService: BluetoothMeshService
meshService: BluetoothMeshService,
onCashuPaymentClick: ((ParsedCashuToken) -> Unit)? = null
) {
val colorScheme = MaterialTheme.colorScheme
val timeFormatter = remember { SimpleDateFormat("HH:mm:ss", Locale.getDefault()) }
@@ -96,6 +100,7 @@ fun MessageItem(
// Parsed content with inline special elements
ParsedMessageContent(
elements = parsedElements,
onCashuPaymentClick = onCashuPaymentClick,
modifier = Modifier.padding(start = 16.dp) // Indent content slightly
)
}