mirror of
https://github.com/permissionlesstech/bitchat-android.git
synced 2026-07-25 09:05:21 +00:00
finishes
This commit is contained in:
@@ -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
|
||||
)
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user