package com.example.ui.screens import androidx.compose.animation.* import androidx.compose.foundation.BorderStroke import androidx.compose.foundation.background import androidx.compose.foundation.border import androidx.compose.foundation.clickable import androidx.compose.foundation.rememberScrollState import androidx.compose.foundation.verticalScroll import androidx.compose.foundation.layout.* import androidx.compose.foundation.lazy.LazyColumn import androidx.compose.foundation.lazy.LazyRow import androidx.compose.foundation.lazy.rememberLazyListState import androidx.compose.foundation.lazy.grid.GridCells import androidx.compose.foundation.lazy.grid.LazyVerticalGrid import androidx.compose.foundation.lazy.grid.items import androidx.compose.foundation.lazy.items import androidx.compose.foundation.shape.CircleShape import androidx.compose.foundation.shape.RoundedCornerShape import androidx.compose.foundation.text.KeyboardOptions import androidx.compose.material.icons.Icons import androidx.compose.material.icons.filled.* import androidx.compose.material.icons.outlined.* import androidx.compose.material3.* import androidx.compose.runtime.* import androidx.compose.ui.Alignment import androidx.compose.ui.Modifier import androidx.compose.ui.draw.clip import androidx.compose.ui.graphics.Brush import androidx.compose.ui.graphics.Color import androidx.compose.ui.graphics.vector.ImageVector import androidx.compose.ui.platform.testTag import androidx.compose.ui.text.font.FontWeight import androidx.compose.ui.text.input.KeyboardType import androidx.compose.ui.text.style.TextAlign import androidx.compose.ui.text.style.TextOverflow import androidx.compose.ui.unit.dp import androidx.compose.ui.unit.sp import androidx.compose.ui.window.Dialog import com.example.data.BookingEntity import com.example.data.ModelProfile import com.example.data.TransactionLog import com.example.data.UserWallet import com.example.ui.PortalViewModel import com.example.ui.theme.* import java.text.SimpleDateFormat import java.util.* import kotlinx.coroutines.launch @OptIn(ExperimentalMaterial3Api::class) @Composable fun UserPortalView( viewModel: PortalViewModel, userWallet: UserWallet, models: List, bookings: List, logs: List ) { // Current active tab / page route var selectedTab by remember { mutableStateOf("dashboard") } // "dashboard", "browse", "favorites", "bookings", "wallet", "membership", "settings" // Dialog states var showBookWizardWithModel by remember { mutableStateOf(null) } var showDetailDialog by remember { mutableStateOf(null) } // Favorites dynamic management (active session state) var favoriteModelIds by remember { mutableStateOf(setOf(1, 3)) } // pre-populate with model 1 (Bella) & 3 (Chloe) for instant premium view! val drawerState = rememberDrawerState(initialValue = DrawerValue.Closed) val scope = rememberCoroutineScope() ModalNavigationDrawer( drawerState = drawerState, drawerContent = { ModalDrawerSheet( drawerContainerColor = DarkGreyGlassElevated, drawerContentColor = TextPrimary, modifier = Modifier.width(300.dp) ) { // Drawer Header Box( modifier = Modifier .fillMaxWidth() .background( Brush.linearGradient( colors = listOf(GoldGradientStart, GoldGradientEnd) ) ) .padding(vertical = 28.dp, horizontal = 24.dp) ) { Column { Row(verticalAlignment = Alignment.CenterVertically) { Box( modifier = Modifier .size(54.dp) .clip(CircleShape) .background(Color.White.copy(alpha = 0.25f)), contentAlignment = Alignment.Center ) { Text( userWallet.name.split(" ").map { it.first() }.joinToString(""), fontWeight = FontWeight.Bold, color = Color.White, fontSize = 18.sp ) } Spacer(modifier = Modifier.width(14.dp)) Column { Text( userWallet.name, style = MaterialTheme.typography.titleMedium, color = Color.White, fontWeight = FontWeight.Bold ) Surface( shape = RoundedCornerShape(4.dp), color = Color.White.copy(alpha = 0.15f) ) { Text( userWallet.membershipStatus, modifier = Modifier.padding(horizontal = 6.dp, vertical = 2.dp), style = MaterialTheme.typography.labelSmall, color = Color.White, fontWeight = FontWeight.SemiBold ) } } } Spacer(modifier = Modifier.height(12.dp)) Text( "Account ID: client_default • Secure Tier", color = Color.White.copy(alpha = 0.8f), style = MaterialTheme.typography.bodySmall ) } } Spacer(modifier = Modifier.height(12.dp)) // Drawer items val drawerItems = mutableListOf( NavigationDrawerItemData("Dashboard", Icons.Default.GridView, "dashboard"), NavigationDrawerItemData("Browse Models", Icons.Default.Search, "browse"), NavigationDrawerItemData("Favorites", Icons.Default.Favorite, "favorites", badgeCount = favoriteModelIds.size), NavigationDrawerItemData("My Bookings", Icons.Default.CalendarMonth, "bookings", badgeCount = bookings.size), NavigationDrawerItemData("Wallet System", Icons.Default.AccountBalanceWallet, "wallet"), NavigationDrawerItemData("Membership Plans", Icons.Default.WorkspacePremium, "membership") ).apply { if (userWallet.isAgent) { add(NavigationDrawerItemData("Cash Agent Hub", Icons.Default.LocalAtm, "agent_hub")) } add(NavigationDrawerItemData("Agency Helpline Help", Icons.Default.Chat, "helpline")) add(NavigationDrawerItemData("Profile Settings", Icons.Default.Settings, "settings")) } drawerItems.forEach { item -> val isSelected = selectedTab == item.route NavigationDrawerItem( icon = { Icon(item.icon, contentDescription = item.title) }, label = { Row( modifier = Modifier.fillMaxWidth(), horizontalArrangement = Arrangement.SpaceBetween, verticalAlignment = Alignment.CenterVertically ) { Text(item.title, fontWeight = if (isSelected) FontWeight.Bold else FontWeight.Normal) if (item.badgeCount != null && item.badgeCount > 0) { Badge( containerColor = if (isSelected) Color.White else BrandGold, contentColor = if (isSelected) BrandGold else Color.White ) { Text("${item.badgeCount}") } } } }, selected = isSelected, onClick = { selectedTab = item.route scope.launch { drawerState.close() } }, modifier = Modifier .padding(NavigationDrawerItemDefaults.ItemPadding) .testTag("drawer_item_${item.route}"), colors = NavigationDrawerItemDefaults.colors( selectedContainerColor = BrandGold, selectedIconColor = Color.White, selectedTextColor = Color.White, unselectedIconColor = TextSecondary, unselectedTextColor = TextPrimary ) ) } Spacer(modifier = Modifier.weight(1f)) // Logout Drawer Item HorizontalDivider(color = DarkGreyGlassElevated, modifier = Modifier.padding(horizontal = 16.dp)) NavigationDrawerItem( icon = { Icon(Icons.Default.ExitToApp, contentDescription = "Logout", tint = CoralRed) }, label = { Text("Logout Portal", color = CoralRed, fontWeight = FontWeight.Bold) }, selected = false, onClick = { scope.launch { drawerState.close() } viewModel.userLogout() }, modifier = Modifier .padding(NavigationDrawerItemDefaults.ItemPadding) .testTag("user_logout_menu_btn") ) Spacer(modifier = Modifier.height(16.dp)) } } ) { Scaffold( topBar = { TopAppBar( title = { Text( text = when (selectedTab) { "dashboard" -> "User Dashboard" "browse" -> "Browse Agency Models" "favorites" -> "Saved Favorites" "bookings" -> "My Safehouse Bookings" "wallet" -> "Secure Ledger Wallet" "membership" -> "Premium Membership" "helpline" -> "Agency Helpline Support" "settings" -> "Profile Settings" else -> "Member Hub" }, style = MaterialTheme.typography.titleLarge, fontWeight = FontWeight.ExtraBold, color = TextPrimary ) }, navigationIcon = { IconButton( onClick = { scope.launch { drawerState.open() } }, modifier = Modifier.testTag("toggle_drawer_btn") ) { Icon(Icons.Default.Menu, "menu", tint = BrandGold) } }, actions = { // Quick balance status pill Surface( shape = RoundedCornerShape(12.dp), color = LightGold, modifier = Modifier.padding(end = 12.dp) ) { Row( verticalAlignment = Alignment.CenterVertically, modifier = Modifier.padding(horizontal = 10.dp, vertical = 6.dp) ) { Icon(Icons.Default.AccountBalanceWallet, "Wallet", tint = BrandGold, modifier = Modifier.size(16.dp)) Spacer(modifier = Modifier.width(6.dp)) Text( String.format(Locale.US, "৳%,.0f", userWallet.availableBalance), style = MaterialTheme.typography.labelMedium, color = BrandGold, fontWeight = FontWeight.Bold ) } } }, colors = TopAppBarDefaults.topAppBarColors(containerColor = DarkGreyGlass) ) }, containerColor = Obsidian ) { paddingValues -> Box( modifier = Modifier .fillMaxSize() .padding(paddingValues) ) { when (selectedTab) { "dashboard" -> UserDashboardTab( viewModel = viewModel, userWallet = userWallet, bookings = bookings, logs = logs, onNavigateToBrowse = { selectedTab = "browse" } ) "helpline" -> ContactHelplineTab( viewModel = viewModel, senderId = "client_default", senderName = userWallet.name ) "browse" -> UserHomeTab( viewModel = viewModel, userWallet = userWallet, models = models, favoriteModelIds = favoriteModelIds, onToggleFavorite = { id -> favoriteModelIds = if (favoriteModelIds.contains(id)) { favoriteModelIds - id } else { favoriteModelIds + id } }, onViewDetail = { showDetailDialog = it }, onBookClick = { showBookWizardWithModel = it } ) "favorites" -> UserFavoritesTab( models = models, favoriteModelIds = favoriteModelIds, onRemoveFavorite = { id -> favoriteModelIds = favoriteModelIds - id }, onQuickBooking = { showBookWizardWithModel = it } ) "bookings" -> UserBookingsTab( viewModel = viewModel, bookings = bookings.filter { it.userId == "client_default" } ) "wallet" -> UserWalletTab( viewModel = viewModel, userWallet = userWallet, logs = logs.filter { it.accountId == "client_default" } ) "membership" -> UserMembershipTab( viewModel = viewModel, userWallet = userWallet ) "settings" -> UserSettingsTab( viewModel = viewModel, userWallet = userWallet ) "agent_hub" -> UserAgentHubTab( viewModel = viewModel, userWallet = userWallet ) } } } } // Modal Details Dialog Box showDetailDialog?.let { model -> ProfileDetailDialog( model = model, userWallet = userWallet, onDismiss = { showDetailDialog = null }, onBookClick = { showDetailDialog = null showBookWizardWithModel = model } ) } // Modal Comprehensive 6-Step New Booking Process Wizard showBookWizardWithModel?.let { model -> CreateBookingWizardDialog( model = model, allModels = models.filter { it.isApproved && !it.isSuspended }, userWallet = userWallet, onDismiss = { showBookWizardWithModel = null }, onConfirmBooking = { service, providerModel, durationHours, totalPrice, location, dateStr, timeStr, timeZoneStr -> viewModel.bookModel( modelId = providerModel.id, modelName = providerModel.name, location = location, date = dateStr, durationHours = durationHours, totalPrice = totalPrice, service = service, bookingTime = timeStr, timeZone = timeZoneStr, onSuccess = { showBookWizardWithModel = null }, onError = { } ) }, onAddFundsClick = { showBookWizardWithModel = null selectedTab = "wallet" } ) } } // Data class hold top layouts data class NavigationDrawerItemData( val title: String, val icon: ImageVector, val route: String, val badgeCount: Int? = null ) // ---------------------------------------------------- // TAB 1: USER DASHBOARD OVERVIEW & RECENT ACTIVITIES // ---------------------------------------------------- @Composable fun UserDashboardTab( viewModel: PortalViewModel, userWallet: UserWallet, bookings: List, logs: List, onNavigateToBrowse: () -> Unit ) { val myBookings = bookings.filter { it.userId == "client_default" } val pendingCount = myBookings.count { it.status == "PENDING" } val activeCount = myBookings.count { it.status == "ACCEPTED" || it.status == "ACTIVE" || it.status == "PAID" } LazyColumn( modifier = Modifier .fillMaxSize() .padding(horizontal = 16.dp), verticalArrangement = Arrangement.spacedBy(16.dp) ) { // Welcoming Greeting & Modern Hero Banner Card item { Spacer(modifier = Modifier.height(14.dp)) Card( modifier = Modifier .fillMaxWidth() .border( BorderStroke(1.dp, Brush.linearGradient(colors = listOf(GoldGradientStart.copy(alpha = 0.5f), BlueGradientStart.copy(alpha = 0.5f)))), shape = RoundedCornerShape(24.dp) ), shape = RoundedCornerShape(24.dp), colors = CardDefaults.cardColors(containerColor = DarkGreyGlassElevated) ) { Box( modifier = Modifier .fillMaxWidth() .background( Brush.linearGradient( colors = listOf(LightGold.copy(alpha = 0.6f), Color(0xFFF0FDF4).copy(alpha = 0.6f)) ) ) .padding(20.dp) ) { Row( modifier = Modifier.fillMaxWidth(), horizontalArrangement = Arrangement.SpaceBetween, verticalAlignment = Alignment.CenterVertically ) { Column(modifier = Modifier.weight(1f)) { Surface( shape = RoundedCornerShape(100.dp), color = BrandGold.copy(alpha = 0.1f), contentColor = BrandGold ) { Row( verticalAlignment = Alignment.CenterVertically, modifier = Modifier.padding(horizontal = 10.dp, vertical = 4.dp) ) { Icon(Icons.Default.VerifiedUser, "verified user", tint = BrandGold, modifier = Modifier.size(12.dp)) Spacer(modifier = Modifier.width(6.dp)) Text( "SECURE TIER MEMBER", style = MaterialTheme.typography.labelSmall, fontWeight = FontWeight.ExtraBold, letterSpacing = 0.5.sp ) } } Spacer(modifier = Modifier.height(10.dp)) Text( "Hello, ${userWallet.name}!", style = MaterialTheme.typography.headlineSmall, fontWeight = FontWeight.ExtraBold, color = TextPrimary ) Spacer(modifier = Modifier.height(2.dp)) Text( "Secure Safehouse Booking System Control • Active", style = MaterialTheme.typography.bodySmall, color = TextSecondary, fontWeight = FontWeight.Medium ) } Box( modifier = Modifier .size(56.dp) .clip(CircleShape) .background(BrandGold.copy(alpha = 0.12f)), contentAlignment = Alignment.Center ) { Icon( imageVector = Icons.Default.Shield, contentDescription = "Shield Guard", tint = BrandGold, modifier = Modifier.size(28.dp) ) } } } } } // Numeric Dashboard Stats Grid item { Row( horizontalArrangement = Arrangement.spacedBy(12.dp), modifier = Modifier.fillMaxWidth() ) { DashboardStatCard( title = "Available Balance", value = String.format(Locale.US, "৳%,.0f BDT", userWallet.availableBalance), icon = Icons.Default.AccountBalanceWallet, color = BrandGold, modifier = Modifier.weight(1f) ) DashboardStatCard( title = "Active Sessions", value = "$activeCount Sessions", icon = Icons.Default.PlayCircleOutline, color = CyanAccent, modifier = Modifier.weight(1f) ) } Spacer(modifier = Modifier.height(12.dp)) Row( horizontalArrangement = Arrangement.spacedBy(12.dp), modifier = Modifier.fillMaxWidth() ) { DashboardStatCard( title = "Pending Approval", value = "$pendingCount Requests", icon = Icons.Default.HistoryToggleOff, color = Color(0xFFF59E0B), modifier = Modifier.weight(1f) ) DashboardStatCard( title = "Membership Privilege", value = userWallet.membershipStatus.substringBefore(" Membership"), icon = Icons.Default.WorkspacePremium, color = Color(0xFFA855F7), modifier = Modifier.weight(1f) ) } } // Action Quick Links with beautiful designer gradients item { Card( modifier = Modifier .fillMaxWidth() .border(BorderStroke(1.dp, Color.White.copy(alpha = 0.2f)), RoundedCornerShape(18.dp)), shape = RoundedCornerShape(18.dp), colors = CardDefaults.cardColors(containerColor = Color.Transparent) ) { Box( modifier = Modifier .fillMaxWidth() .background( Brush.linearGradient( colors = listOf(GoldGradientStart, GoldGradientEnd) ) ) .padding(22.dp) ) { Row( modifier = Modifier.fillMaxWidth(), horizontalArrangement = Arrangement.SpaceBetween, verticalAlignment = Alignment.CenterVertically ) { Column(modifier = Modifier.weight(1f)) { Text( "Need Companion Modeling?", color = Color.White, style = MaterialTheme.typography.titleMedium, fontWeight = FontWeight.Bold ) Spacer(modifier = Modifier.height(4.dp)) Text( "Browse verified talent portraits and set custom durations.", color = Color.White.copy(alpha = 0.85f), style = MaterialTheme.typography.bodySmall ) } Spacer(modifier = Modifier.width(12.dp)) Button( onClick = onNavigateToBrowse, colors = ButtonDefaults.buttonColors(containerColor = Color.White, contentColor = BrandGold), shape = RoundedCornerShape(12.dp), modifier = Modifier.testTag("dash_quick_book_btn"), elevation = ButtonDefaults.buttonElevation(defaultElevation = 2.dp) ) { Text("Book Now", fontWeight = FontWeight.Bold, style = MaterialTheme.typography.labelSmall) Spacer(modifier = Modifier.width(4.dp)) Icon(Icons.Default.ArrowForward, "go", modifier = Modifier.size(14.dp)) } } } } } // Recent Activity Feed Headers item { Text( "Recent Portal Updates", style = MaterialTheme.typography.titleMedium, fontWeight = FontWeight.Bold, color = TextPrimary ) Text( "Tracking audits for booking requests, payment updates, and membership events.", style = MaterialTheme.typography.bodySmall, color = TextSecondary ) Spacer(modifier = Modifier.height(4.dp)) } // Recent Activity Checklist Items if (myBookings.isEmpty() && logs.isEmpty()) { item { Card( colors = CardDefaults.cardColors(containerColor = DarkGreyGlassElevated), modifier = Modifier.fillMaxWidth() ) { Box(modifier = Modifier.padding(24.dp), contentAlignment = Alignment.Center) { Text("No recent activity recorded yet.", color = TextSecondary, style = MaterialTheme.typography.bodyMedium) } } } } else { // First display dynamic booking updates val recentBookings = myBookings.take(2) recentBookings.forEach { booking -> item { ActivityFeedItem( icon = when (booking.status) { "PENDING" -> Icons.Default.PendingActions "ACCEPTED" -> Icons.Default.CheckCircle "PAID" -> Icons.Default.Paid "ACTIVE" -> Icons.Default.Timer "COMPLETED" -> Icons.Default.AssignmentTurnedIn else -> Icons.Default.Cancel }, iconColor = when (booking.status) { "PENDING" -> Color(0xFFF59E0B) "ACCEPTED" -> CyanAccent "PAID" -> Color.Green "ACTIVE" -> BrandGold "COMPLETED" -> Color.Green else -> CoralRed }, title = "Booking Update: #${booking.id}", subtitle = "${booking.service} with ${booking.modelName} is currently ${booking.status}.", timeStr = "Just Now" ) } } // Display payment and membership updates from Logs val recentLogs = logs.take(3) recentLogs.forEach { log -> item { ActivityFeedItem( icon = when (log.type) { "DEPOSIT" -> Icons.Default.Input "MEMBERSHIP_UPGRADE" -> Icons.Default.WorkspacePremium else -> Icons.Default.AccountBalanceWallet }, iconColor = when (log.type) { "DEPOSIT" -> Color.Green "MEMBERSHIP_UPGRADE" -> Color(0xFFA855F7) else -> BrandGold }, title = log.type.replace("_", " "), subtitle = "${log.description} (${String.format(Locale.US, "৳%,.0f", Math.abs(log.amount))})", timeStr = "1 hour ago" ) } } } item { Spacer(modifier = Modifier.height(30.dp)) } } } @Composable fun DashboardStatCard( title: String, value: String, icon: ImageVector, color: Color, modifier: Modifier = Modifier ) { Card( modifier = modifier .border( BorderStroke(1.dp, color.copy(alpha = 0.15f)), shape = RoundedCornerShape(16.dp) ), colors = CardDefaults.cardColors(containerColor = DarkGreyGlassElevated), shape = RoundedCornerShape(16.dp) ) { Column( modifier = Modifier .background( Brush.verticalGradient( colors = listOf(color.copy(alpha = 0.05f), Color.Transparent) ) ) .padding(16.dp) ) { Row( verticalAlignment = Alignment.CenterVertically, horizontalArrangement = Arrangement.SpaceBetween, modifier = Modifier.fillMaxWidth() ) { Box( modifier = Modifier .size(36.dp) .clip(RoundedCornerShape(8.dp)) .background(color.copy(alpha = 0.12f)), contentAlignment = Alignment.Center ) { Icon(icon, title, tint = color, modifier = Modifier.size(20.dp)) } } Spacer(modifier = Modifier.height(14.dp)) Text(value, style = MaterialTheme.typography.titleMedium, fontWeight = FontWeight.ExtraBold, color = TextPrimary) Spacer(modifier = Modifier.height(2.dp)) Text(title, style = MaterialTheme.typography.labelSmall, color = TextSecondary, fontWeight = FontWeight.SemiBold) } } } @Composable fun ActivityFeedItem( icon: ImageVector, iconColor: Color, title: String, subtitle: String, timeStr: String ) { Row( modifier = Modifier .fillMaxWidth() .background(DarkGreyGlass, RoundedCornerShape(14.dp)) .border(BorderStroke(1.dp, DarkGreyGlassElevated), RoundedCornerShape(14.dp)) .padding(14.dp), verticalAlignment = Alignment.CenterVertically ) { Box( modifier = Modifier .size(42.dp) .clip(RoundedCornerShape(10.dp)) .background(iconColor.copy(alpha = 0.08f)), contentAlignment = Alignment.Center ) { Icon(icon, title, tint = iconColor, modifier = Modifier.size(20.dp)) } Spacer(modifier = Modifier.width(14.dp)) Column(modifier = Modifier.weight(1f)) { Text( text = title, style = MaterialTheme.typography.bodyMedium, fontWeight = FontWeight.Bold, color = TextPrimary ) Spacer(modifier = Modifier.height(2.dp)) Text( text = subtitle, style = MaterialTheme.typography.bodySmall, color = TextSecondary, maxLines = 2, overflow = TextOverflow.Ellipsis, lineHeight = 16.sp ) } Spacer(modifier = Modifier.width(8.dp)) Surface( shape = RoundedCornerShape(6.dp), color = DarkGreyGlassElevated, modifier = Modifier.align(Alignment.Top) ) { Text( text = timeStr, style = MaterialTheme.typography.labelSmall, color = TextSecondary, fontWeight = FontWeight.SemiBold, modifier = Modifier.padding(horizontal = 6.dp, vertical = 3.dp) ) } } } // ---------------------------------------------------- // TAB 2: BROWSE MODELS PORTALS (WITH FEATURED) // ---------------------------------------------------- @Composable fun UserHomeTab( viewModel: PortalViewModel, userWallet: UserWallet, models: List, favoriteModelIds: Set, onToggleFavorite: (Int) -> Unit, onViewDetail: (ModelProfile) -> Unit, onBookClick: (ModelProfile) -> Unit ) { var searchQuery by remember { mutableStateOf("") } var locationFilter by remember { mutableStateOf("All") } val locations = listOf("All", "Hotel Ritz", "Luxury Lounge", "Grand Arena", "Vapor Safehouse") var selectedCountry by remember { mutableStateOf("All") } var selectedCity by remember { mutableStateOf("All") } var selectedCategory by remember { mutableStateOf("All") } val countriesList = listOf("All", "Bangladesh", "USA", "UK", "Canada", "India") val citiesList = listOf("All", "Dhaka", "New York", "London", "Toronto", "Mumbai", "Chittagong") val categoriesList = listOf("All", "Fashion", "Commercial", "Fitness", "Runway", "Glamour") val approvedModels = models.filter { it.isApproved && !it.isSuspended } val filteredModels = approvedModels.filter { val matchesLoc = locationFilter == "All" || it.location.equals(locationFilter, ignoreCase = true) val matchesSearch = it.name.contains(searchQuery, ignoreCase = true) || it.bio.contains(searchQuery, ignoreCase = true) val matchesCountry = selectedCountry == "All" || it.country.equals(selectedCountry, ignoreCase = true) val matchesCity = selectedCity == "All" || it.city.equals(selectedCity, ignoreCase = true) val matchesCategory = selectedCategory == "All" || it.category.equals(selectedCategory, ignoreCase = true) matchesLoc && matchesSearch && matchesCountry && matchesCity && matchesCategory } LazyColumn( modifier = Modifier .fillMaxSize() .padding(horizontal = 16.dp), verticalArrangement = Arrangement.spacedBy(16.dp) ) { // Search & Location filters item { Spacer(modifier = Modifier.height(10.dp)) TextField( value = searchQuery, onValueChange = { searchQuery = it }, modifier = Modifier .fillMaxWidth() .border(1.dp, DarkGreyGlassElevated, RoundedCornerShape(12.dp)) .testTag("user_search_input"), placeholder = { Text("Search by name, location tags, etc...", color = TextSecondary) }, leadingIcon = { Icon(Icons.Default.Search, contentDescription = "Search", tint = BrandGold) }, colors = TextFieldDefaults.colors( focusedContainerColor = DarkGreyGlass, unfocusedContainerColor = DarkGreyGlass, focusedTextColor = TextPrimary, unfocusedTextColor = TextPrimary ), shape = RoundedCornerShape(12.dp) ) } // Dropdown Filters row (Country, City, Category) item { Column( modifier = Modifier .fillMaxWidth() .padding(vertical = 4.dp), verticalArrangement = Arrangement.spacedBy(8.dp) ) { Text( text = "Refine Search Directory", style = MaterialTheme.typography.labelMedium, color = BrandGold, fontWeight = FontWeight.Bold ) Row( modifier = Modifier.fillMaxWidth(), horizontalArrangement = Arrangement.spacedBy(8.dp) ) { // Country Filter Dropdown Box(modifier = Modifier.weight(1f)) { FilterDropdownSelector( label = "Country", selectedOption = selectedCountry, options = countriesList, onOptionSelected = { selectedCountry = it } ) } // City Filter Dropdown Box(modifier = Modifier.weight(1f)) { FilterDropdownSelector( label = "City", selectedOption = selectedCity, options = citiesList, onOptionSelected = { selectedCity = it } ) } // Category Filter Dropdown Box(modifier = Modifier.weight(1f)) { FilterDropdownSelector( label = "Category", selectedOption = selectedCategory, options = categoriesList, onOptionSelected = { selectedCategory = it } ) } } } } // Filters scroll item { LazyRow( horizontalArrangement = Arrangement.spacedBy(8.dp), modifier = Modifier.fillMaxWidth() ) { items(locations) { loc -> val isSelected = locationFilter == loc Surface( modifier = Modifier .clickable { locationFilter = loc } .testTag("location_filter_$loc"), shape = RoundedCornerShape(20.dp), color = if (isSelected) BrandGold else DarkGreyGlass, border = BorderStroke(1.dp, if (isSelected) Color.Transparent else DarkGreyGlassElevated) ) { Text( text = loc, color = if (isSelected) Color.White else TextPrimary, style = MaterialTheme.typography.bodySmall, fontWeight = FontWeight.Bold, modifier = Modifier.padding(horizontal = 16.dp, vertical = 8.dp) ) } } } } // Featured Models Carousel section val featuredModels = approvedModels.filter { it.rating >= 4.7 } if (featuredModels.isNotEmpty() && searchQuery.isEmpty() && locationFilter == "All") { item { Text("Featured Agency Models", style = MaterialTheme.typography.titleMedium, fontWeight = FontWeight.Bold, color = TextPrimary) Spacer(modifier = Modifier.height(4.dp)) LazyRow( horizontalArrangement = Arrangement.spacedBy(12.dp), modifier = Modifier.fillMaxWidth() ) { items(featuredModels) { m -> FeaturedModelCard( model = m, isFavorite = favoriteModelIds.contains(m.id), onToggleFavorite = { onToggleFavorite(m.id) }, onSelect = { onViewDetail(m) }, onBook = { onBookClick(m) } ) } } } } // Standard Results Header item { Text("All Professional Models", style = MaterialTheme.typography.titleMedium, fontWeight = FontWeight.Bold, color = TextPrimary) } if (filteredModels.isEmpty()) { item { Box( modifier = Modifier .fillMaxWidth() .padding(vertical = 40.dp), contentAlignment = Alignment.Center ) { Text("No models matched your parameters.", color = TextSecondary, style = MaterialTheme.typography.bodyMedium) } } } else { items(filteredModels) { model -> ModelListItem( model = model, isFavorite = favoriteModelIds.contains(model.id), onToggleFavorite = { onToggleFavorite(model.id) }, onSelect = { onViewDetail(model) }, onBook = { onBookClick(model) } ) } } item { Spacer(modifier = Modifier.height(30.dp)) } } } @Composable fun FeaturedModelCard( model: ModelProfile, isFavorite: Boolean, onToggleFavorite: () -> Unit, onSelect: () -> Unit, onBook: () -> Unit ) { Card( modifier = Modifier .width(220.dp) .border(BorderStroke(1.dp, BrandGold.copy(alpha = 0.15f)), RoundedCornerShape(16.dp)) .clickable { onSelect() } .testTag("featured_model_card_${model.id}"), colors = CardDefaults.cardColors(containerColor = DarkGreyGlassElevated), shape = RoundedCornerShape(16.dp) ) { Column( modifier = Modifier .background( Brush.verticalGradient( colors = listOf(Color.White, DarkGreyGlassElevated.copy(alpha = 0.4f)) ) ) .padding(14.dp) ) { Row( horizontalArrangement = Arrangement.SpaceBetween, verticalAlignment = Alignment.CenterVertically, modifier = Modifier.fillMaxWidth() ) { // Gradient Profile Head representing verified luxury status Box( modifier = Modifier .size(46.dp) .clip(CircleShape) .background( Brush.sweepGradient( colors = listOf(GoldGradientStart, BlueGradientStart, GoldGradientEnd) ) ), contentAlignment = Alignment.Center ) { Text(model.name.first().toString(), color = Color.White, fontWeight = FontWeight.Bold, fontSize = 16.sp) } IconButton( onClick = onToggleFavorite, modifier = Modifier.size(24.dp) ) { Icon( if (isFavorite) Icons.Default.Favorite else Icons.Default.FavoriteBorder, "fav", tint = if (isFavorite) CoralRed else TextSecondary, modifier = Modifier.size(18.dp) ) } } Spacer(modifier = Modifier.height(10.dp)) Row(verticalAlignment = Alignment.CenterVertically) { Text(model.name, fontWeight = FontWeight.ExtraBold, style = MaterialTheme.typography.bodyMedium, color = TextPrimary, maxLines = 1, overflow = TextOverflow.Ellipsis) if (model.isVerified) { Spacer(modifier = Modifier.width(4.dp)) Icon(Icons.Default.Verified, "v", tint = CyanAccent, modifier = Modifier.size(14.dp)) } } Text(model.location, style = MaterialTheme.typography.labelSmall, color = TextSecondary, fontWeight = FontWeight.Medium) Spacer(modifier = Modifier.height(8.dp)) Row( modifier = Modifier.fillMaxWidth(), horizontalArrangement = Arrangement.SpaceBetween, verticalAlignment = Alignment.CenterVertically ) { Text( String.format(Locale.US, "৳%,.0f/hr", model.hourlyRate), color = BrandGold, style = MaterialTheme.typography.labelMedium, fontWeight = FontWeight.ExtraBold ) Surface( shape = RoundedCornerShape(100.dp), color = LightGold, modifier = Modifier.padding(start = 4.dp) ) { Row( verticalAlignment = Alignment.CenterVertically, modifier = Modifier.padding(horizontal = 6.dp, vertical = 2.dp) ) { Icon(Icons.Default.Star, "rating", tint = BrandGold, modifier = Modifier.size(10.dp)) Spacer(modifier = Modifier.width(2.dp)) Text("${model.rating}", style = MaterialTheme.typography.labelSmall, fontWeight = FontWeight.ExtraBold, color = BrandGold) } } } Spacer(modifier = Modifier.height(12.dp)) Button( onClick = onBook, colors = ButtonDefaults.buttonColors(containerColor = BrandGold, contentColor = Color.White), shape = RoundedCornerShape(10.dp), modifier = Modifier .fillMaxWidth() .height(32.dp), contentPadding = PaddingValues(0.dp), elevation = ButtonDefaults.buttonElevation(defaultElevation = 1.dp) ) { Text("Reserve Now", fontSize = 11.sp, fontWeight = FontWeight.Bold) } } } } @Composable fun ModelListItem( model: ModelProfile, isFavorite: Boolean, onToggleFavorite: () -> Unit, onSelect: () -> Unit, onBook: () -> Unit ) { Card( modifier = Modifier .fillMaxWidth() .border(BorderStroke(1.dp, DarkGreyGlassElevated), RoundedCornerShape(16.dp)) .clickable { onSelect() } .testTag("model_list_item_${model.id}"), colors = CardDefaults.cardColors(containerColor = DarkGreyGlass), shape = RoundedCornerShape(16.dp) ) { Row( modifier = Modifier.padding(14.dp), verticalAlignment = Alignment.CenterVertically ) { // Photo Identifier representation (using styled initial or shape as gradient) Box( modifier = Modifier .size(54.dp) .clip(CircleShape) .background( Brush.linearGradient( colors = listOf(BlueGradientStart, BlueGradientEnd) ) ) .testTag("model_photo_${model.id}"), contentAlignment = Alignment.Center ) { Text(model.name.first().toString(), color = Color.White, fontWeight = FontWeight.ExtraBold, fontSize = 20.sp) } Spacer(modifier = Modifier.width(14.dp)) Column(modifier = Modifier.weight(1f)) { // Name Text( text = "Name: ${model.name}", fontWeight = FontWeight.ExtraBold, style = MaterialTheme.typography.bodyMedium, color = TextPrimary ) // Agency Name Text( text = "Agency: ${model.agency}", style = MaterialTheme.typography.bodySmall, color = TextPrimary, fontWeight = FontWeight.SemiBold ) // Country & City Text( text = "Country: ${model.country}", style = MaterialTheme.typography.bodySmall, color = TextSecondary ) Text( text = "City: ${model.city}", style = MaterialTheme.typography.bodySmall, color = TextSecondary ) Spacer(modifier = Modifier.height(4.dp)) // Verified Badge representation Row(verticalAlignment = Alignment.CenterVertically) { Icon( imageVector = Icons.Default.Verified, contentDescription = "Verified Badge", tint = CyanAccent, modifier = Modifier.size(15.dp) ) if (model.isVerified) { Spacer(modifier = Modifier.width(4.dp)) Text( text = "Verified Badge", style = MaterialTheme.typography.labelSmall, color = CyanAccent, fontWeight = FontWeight.Bold ) } } } Column(horizontalAlignment = Alignment.End) { IconButton( onClick = onToggleFavorite, modifier = Modifier.size(24.dp) ) { Icon( if (isFavorite) Icons.Default.Favorite else Icons.Default.FavoriteBorder, "favorite", tint = if (isFavorite) CoralRed else TextSecondary, modifier = Modifier.size(18.dp) ) } Spacer(modifier = Modifier.height(6.dp)) // [View Profile] Button Button( onClick = onSelect, colors = ButtonDefaults.buttonColors(containerColor = CyanAccent, contentColor = Color.Black), shape = RoundedCornerShape(10.dp), modifier = Modifier .height(30.dp) .testTag("model_view_profile_${model.id}"), contentPadding = PaddingValues(horizontal = 10.dp, vertical = 0.dp), elevation = ButtonDefaults.buttonElevation(defaultElevation = 1.dp) ) { Text("View Profile", fontSize = 10.sp, fontWeight = FontWeight.Bold) } Spacer(modifier = Modifier.height(6.dp)) Button( onClick = onBook, colors = ButtonDefaults.buttonColors(containerColor = BrandGold, contentColor = Color.White), shape = RoundedCornerShape(10.dp), modifier = Modifier .height(30.dp) .testTag("model_list_book_btn_${model.id}"), contentPadding = PaddingValues(horizontal = 12.dp, vertical = 0.dp), elevation = ButtonDefaults.buttonElevation(defaultElevation = 1.dp) ) { Text("Select", fontSize = 10.sp, fontWeight = FontWeight.Bold) } } } } } @Composable fun FilterDropdownSelector( label: String, selectedOption: String, options: List, onOptionSelected: (String) -> Unit ) { var expanded by remember { mutableStateOf(false) } Card( modifier = Modifier .fillMaxWidth() .clickable { expanded = true }, colors = CardDefaults.cardColors(containerColor = DarkGreyGlassElevated), border = BorderStroke(1.dp, DarkGreyGlassElevated), shape = RoundedCornerShape(8.dp) ) { Row( modifier = Modifier.padding(horizontal = 10.dp, vertical = 8.dp), verticalAlignment = Alignment.CenterVertically, horizontalArrangement = Arrangement.SpaceBetween ) { Column(modifier = Modifier.weight(1f)) { Text(label, style = MaterialTheme.typography.labelSmall, color = TextSecondary) Text( selectedOption, style = MaterialTheme.typography.bodySmall, color = TextPrimary, fontWeight = FontWeight.Bold, maxLines = 1, overflow = TextOverflow.Ellipsis ) } Icon( Icons.Default.ArrowDropDown, contentDescription = "Dropdown", tint = BrandGold, modifier = Modifier.size(20.dp) ) } DropdownMenu( expanded = expanded, onDismissRequest = { expanded = false }, modifier = Modifier.background(DarkGreyGlassElevated) ) { options.forEach { option -> DropdownMenuItem( text = { Text(option, color = TextPrimary, style = MaterialTheme.typography.bodyMedium) }, onClick = { onOptionSelected(option) expanded = false } ) } } } } // ---------------------------------------------------- // TAB 3: FAVORITES LIST WITH QUICK BOOKING // ---------------------------------------------------- @Composable fun UserFavoritesTab( models: List, favoriteModelIds: Set, onRemoveFavorite: (Int) -> Unit, onQuickBooking: (ModelProfile) -> Unit ) { val favoritesList = models.filter { favoriteModelIds.contains(it.id) } LazyColumn( modifier = Modifier .fillMaxSize() .padding(horizontal = 16.dp), verticalArrangement = Arrangement.spacedBy(16.dp) ) { item { Spacer(modifier = Modifier.height(12.dp)) Text("Your Saved Agency Favorites", style = MaterialTheme.typography.titleLarge, fontWeight = FontWeight.Bold, color = TextPrimary) Text("Access saved companion profiles directly for high-speed Safehouse checkout requests.", style = MaterialTheme.typography.bodyMedium, color = TextSecondary) } if (favoritesList.isEmpty()) { item { Card( modifier = Modifier .fillMaxWidth() .padding(vertical = 40.dp), colors = CardDefaults.cardColors(containerColor = DarkGreyGlassElevated) ) { Column( modifier = Modifier.padding(24.dp), horizontalAlignment = Alignment.CenterHorizontally ) { Icon(Icons.Default.FavoriteBorder, "Heart", tint = TextSecondary, modifier = Modifier.size(48.dp)) Spacer(modifier = Modifier.height(12.dp)) Text("No Favorite companion yet", style = MaterialTheme.typography.titleMedium, color = TextPrimary) Text("Add candidates while browsing to construct your private roster.", style = MaterialTheme.typography.bodySmall, color = TextSecondary, textAlign = TextAlign.Center) } } } } else { items(favoritesList) { companion -> Card( modifier = Modifier .fillMaxWidth() .testTag("fav_item_${companion.id}"), colors = CardDefaults.cardColors(containerColor = DarkGreyGlass), shape = RoundedCornerShape(14.dp) ) { Row( modifier = Modifier.padding(14.dp), verticalAlignment = Alignment.CenterVertically ) { Box( modifier = Modifier .size(50.dp) .clip(CircleShape) .background(LightGold), contentAlignment = Alignment.Center ) { Text(companion.name.first().toString(), color = BrandGold, fontWeight = FontWeight.Bold, fontSize = 18.sp) } Spacer(modifier = Modifier.width(14.dp)) Column(modifier = Modifier.weight(1f)) { Text(companion.name, fontWeight = FontWeight.Bold, color = TextPrimary) Text(companion.location, style = MaterialTheme.typography.bodySmall, color = TextSecondary) Text(String.format(Locale.US, "Rate: ৳%,.0f / hr", companion.hourlyRate), style = MaterialTheme.typography.labelSmall, color = BrandGold, fontWeight = FontWeight.SemiBold) } Row(horizontalArrangement = Arrangement.spacedBy(6.dp)) { IconButton( onClick = { onRemoveFavorite(companion.id) }, modifier = Modifier.testTag("remove_fav_btn_${companion.id}") ) { Icon(Icons.Default.Favorite, "Remove", tint = CoralRed) } Button( onClick = { onQuickBooking(companion) }, colors = ButtonDefaults.buttonColors(containerColor = BrandGold, contentColor = Color.White), shape = RoundedCornerShape(8.dp), modifier = Modifier.testTag("quick_book_fav_${companion.id}") ) { Text("Book", fontWeight = FontWeight.Bold, fontSize = 12.sp) } } } } } } item { Spacer(modifier = Modifier.height(30.dp)) } } } // ---------------------------------------------------- // TAB 4: MY BOOKINGS SYSTEM (CHECKLIST & SIMULATION) // ---------------------------------------------------- @Composable fun UserBookingsTab( viewModel: PortalViewModel, bookings: List ) { var filterStatus by remember { mutableStateOf("ALL") } // "ALL", "PENDING", "ACCEPTED", "PAID", "ACTIVE", "COMPLETED", "CANCELLED" val filteredList = if (filterStatus == "ALL") bookings else bookings.filter { it.status == filterStatus } LazyColumn( modifier = Modifier .fillMaxSize() .padding(horizontal = 16.dp), verticalArrangement = Arrangement.spacedBy(16.dp) ) { item { Spacer(modifier = Modifier.height(12.dp)) Text( "My Safehouse Bookings", style = MaterialTheme.typography.titleLarge, color = TextPrimary, fontWeight = FontWeight.Bold ) Text( "Track companion availability, active sessions, and lock escrow payouts.", style = MaterialTheme.typography.bodyMedium, color = TextSecondary ) Spacer(modifier = Modifier.height(4.dp)) } // Toggles Scroll Row item { LazyRow( horizontalArrangement = Arrangement.spacedBy(4.dp), modifier = Modifier.fillMaxWidth() ) { items(listOf("ALL", "PENDING", "ACCEPTED", "PAID", "ACTIVE", "COMPLETED", "CANCELLED")) { statusName -> val isSelected = filterStatus == statusName Surface( modifier = Modifier .clickable { filterStatus = statusName } .testTag("tab_filter_$statusName"), shape = RoundedCornerShape(12.dp), color = if (isSelected) BrandGold else DarkGreyGlass, border = BorderStroke(1.dp, if (isSelected) Color.Transparent else DarkGreyGlassElevated) ) { Text( text = statusName, color = if (isSelected) Color.White else TextSecondary, style = MaterialTheme.typography.labelSmall, fontWeight = FontWeight.Bold, modifier = Modifier.padding(horizontal = 12.dp, vertical = 8.dp) ) } } } } if (filteredList.isEmpty()) { item { Card( modifier = Modifier .fillMaxWidth() .padding(vertical = 40.dp), colors = CardDefaults.cardColors(containerColor = DarkGreyGlass) ) { Column( modifier = Modifier .fillMaxWidth() .padding(24.dp), horizontalAlignment = Alignment.CenterHorizontally ) { Icon( Icons.Default.EventNote, contentDescription = "Empty Bookings", tint = TextSecondary, modifier = Modifier.size(48.dp) ) Spacer(modifier = Modifier.height(12.dp)) Text( "No bookings matched", style = MaterialTheme.typography.titleSmall, color = TextPrimary ) Text( "Submit a request to select professional models.", style = MaterialTheme.typography.bodySmall, color = TextSecondary, textAlign = TextAlign.Center ) } } } } else { items(filteredList) { booking -> UserBookingListItem(booking = booking, viewModel = viewModel) } } item { Spacer(modifier = Modifier.height(30.dp)) } } } @Composable fun UserBookingListItem( booking: BookingEntity, viewModel: PortalViewModel ) { // Dynamic local state to simulate status shifts instantly inside UI! var simulatedStatus by remember { mutableStateOf(booking.status) } LaunchedEffect(booking.status) { simulatedStatus = booking.status } var showSimulationPanel by remember { mutableStateOf(false) } var showChatDialog by remember { mutableStateOf(false) } if (showChatDialog) { Private1vs1ChatDialog( booking = booking, viewModel = viewModel, senderType = "USER", onDismiss = { showChatDialog = false } ) } Card( modifier = Modifier .fillMaxWidth() .testTag("user_booking_card_${booking.id}"), colors = CardDefaults.cardColors(containerColor = DarkGreyGlass), shape = RoundedCornerShape(14.dp) ) { Column(modifier = Modifier.padding(16.dp)) { Row( horizontalArrangement = Arrangement.SpaceBetween, verticalAlignment = Alignment.CenterVertically, modifier = Modifier.fillMaxWidth() ) { Column { Text("Booking ID: #${booking.id}", color = TextSecondary, style = MaterialTheme.typography.bodySmall) Text(booking.modelName, color = TextPrimary, style = MaterialTheme.typography.titleMedium, fontWeight = FontWeight.Bold) } val statusColor = when (simulatedStatus) { "PENDING" -> Color(0xFFF59E0B) "ACCEPTED" -> CyanAccent "PROOF_UPLOADED" -> LightGold "PAID" -> Color.Green "ACTIVE" -> BrandGold "COMPLETED" -> Color.Green else -> CoralRed } Surface( shape = RoundedCornerShape(6.dp), color = statusColor.copy(alpha = 0.15f), contentColor = statusColor ) { Text( simulatedStatus, style = MaterialTheme.typography.labelSmall, modifier = Modifier.padding(horizontal = 8.dp, vertical = 4.dp), fontWeight = FontWeight.Bold ) } } HorizontalDivider(color = DarkGreyGlassElevated, modifier = Modifier.padding(vertical = 12.dp)) // Booking Details Table Column(verticalArrangement = Arrangement.spacedBy(4.dp)) { BookingDetailRow(label = "Selected Service", value = booking.service, icon = Icons.Default.CameraAlt) BookingDetailRow(label = "Target venue", value = booking.location, icon = Icons.Default.Place) BookingDetailRow(label = "Date & Schedule", value = "${booking.dateString} at ${booking.bookingTime}", icon = Icons.Default.CalendarToday) BookingDetailRow(label = "Local Time Zone", value = booking.timeZone, icon = Icons.Default.Public) BookingDetailRow(label = "Price locks", value = String.format(Locale.US, "৳%,.0f BDT", booking.totalPrice), icon = Icons.Default.Paid) } Spacer(modifier = Modifier.height(14.dp)) // Real cancel action & simulated status switches Row( modifier = Modifier.fillMaxWidth(), horizontalArrangement = Arrangement.SpaceBetween, verticalAlignment = Alignment.CenterVertically ) { Row(verticalAlignment = Alignment.CenterVertically) { IconButton( onClick = { showSimulationPanel = !showSimulationPanel }, modifier = Modifier.testTag("simulate_status_toggle_${booking.id}") ) { Icon(Icons.Default.Tune, "Simulate", tint = BrandGold) } if (simulatedStatus == "ACCEPTED" || simulatedStatus == "PROOF_UPLOADED" || simulatedStatus == "PAID" || simulatedStatus == "ACTIVE" || simulatedStatus == "COMPLETED") { Button( onClick = { showChatDialog = true }, colors = ButtonDefaults.buttonColors(containerColor = CyanAccent, contentColor = Color.Black), shape = RoundedCornerShape(8.dp), modifier = Modifier .height(34.dp) .testTag("chat_1vs1_user_btn_${booking.id}"), contentPadding = PaddingValues(horizontal = 12.dp, vertical = 0.dp) ) { Text("💬 1-on-1 Safehouse Chat", fontSize = 11.sp, fontWeight = FontWeight.Bold) } } } if (simulatedStatus == "PENDING" || simulatedStatus == "ACCEPTED") { OutlinedButton( onClick = { viewModel.cancelBooking(booking.id, isModelCancelling = false) simulatedStatus = "CANCELLED" }, colors = ButtonDefaults.outlinedButtonColors(contentColor = CoralRed), border = BorderStroke(1.dp, CoralRed.copy(alpha = 0.35f)), shape = RoundedCornerShape(8.dp), modifier = Modifier .height(34.dp) .testTag("cancel_booking_btn_${booking.id}"), contentPadding = PaddingValues(horizontal = 12.dp, vertical = 0.dp) ) { Text("Cancel Booking", fontSize = 11.sp, fontWeight = FontWeight.Bold) } } } // Expanding status simulation control room for high-friction prototyping if (showSimulationPanel) { Spacer(modifier = Modifier.height(10.dp)) HorizontalDivider(color = DarkGreyGlassElevated) Spacer(modifier = Modifier.height(8.dp)) Text("Simulate Contract Status Triggers:", style = MaterialTheme.typography.labelSmall, color = TextSecondary, fontWeight = FontWeight.Bold) Spacer(modifier = Modifier.height(6.dp)) Row( horizontalArrangement = Arrangement.spacedBy(4.dp), modifier = Modifier.fillMaxWidth() ) { listOf("PENDING", "ACCEPTED", "PROOF_UPLOADED", "COMPLETED", "CANCELLED").forEach { trigger -> Surface( modifier = Modifier .weight(1f) .clickable { simulatedStatus = trigger if (trigger == "COMPLETED") { viewModel.completeBooking(booking.id) } else if (trigger == "CANCELLED") { viewModel.cancelBooking(booking.id, false) } else if (trigger == "PROOF_UPLOADED") { viewModel.submitProof(booking.id, booking.modelId, "proof_safehouse_interior.png") } } .testTag("sim_trigger_${trigger}_${booking.id}"), shape = RoundedCornerShape(4.dp), color = if (simulatedStatus == trigger) BrandGold else DarkGreyGlassElevated, ) { Text( trigger.substring(0, Math.min(3, trigger.length)), color = if (simulatedStatus == trigger) Color.White else TextPrimary, style = MaterialTheme.typography.labelSmall, fontWeight = FontWeight.Bold, modifier = Modifier.padding(vertical = 4.dp), textAlign = TextAlign.Center ) } } } } } } } @Composable fun BookingDetailRow(label: String, value: String, icon: ImageVector) { Row( verticalAlignment = Alignment.CenterVertically, modifier = Modifier.fillMaxWidth() ) { Icon(icon, label, tint = BrandGold.copy(alpha = 0.8f), modifier = Modifier.size(13.dp)) Spacer(modifier = Modifier.width(6.dp)) Text("$label: ", color = TextSecondary, style = MaterialTheme.typography.bodySmall) Text(value, color = TextPrimary, style = MaterialTheme.typography.bodySmall, fontWeight = FontWeight.Bold) } } // ---------------------------------------------------- // TAB 5: SECURE LEDGER WALLET SYSTEM // ---------------------------------------------------- @Composable fun UserWalletTab( viewModel: PortalViewModel, userWallet: UserWallet, logs: List ) { var showDepositDialog by remember { mutableStateOf(false) } var depositInputAmount by remember { mutableStateOf("") } var cardNumber by remember { mutableStateOf("") } var showAgentDepositDialog by remember { mutableStateOf(false) } var agentDepositAmount by remember { mutableStateOf("") } var selectedAgentId by remember { mutableStateOf("") } var requestSubmittedMessage by remember { mutableStateOf(null) } LazyColumn( modifier = Modifier .fillMaxSize() .padding(horizontal = 16.dp), verticalArrangement = Arrangement.spacedBy(16.dp) ) { item { Spacer(modifier = Modifier.height(12.dp)) Text("Secure Escrow Ledger Wallet", style = MaterialTheme.typography.titleLarge, fontWeight = FontWeight.Bold, color = TextPrimary) Text("Deposit gateway funds, track frozen security funds, and view transaction audits.", style = MaterialTheme.typography.bodyMedium, color = TextSecondary) } // Holdings card item { Card( modifier = Modifier.fillMaxWidth(), colors = CardDefaults.cardColors(containerColor = DarkGreyGlassElevated), shape = RoundedCornerShape(16.dp) ) { Column(modifier = Modifier.padding(20.dp)) { Text("Total Account Holdings", color = TextSecondary, style = MaterialTheme.typography.labelSmall) Text( String.format(Locale.US, "৳%,.0f BDT", userWallet.totalBalance), color = BrandGold, style = MaterialTheme.typography.displayMedium, fontWeight = FontWeight.Black ) Spacer(modifier = Modifier.height(12.dp)) Row( horizontalArrangement = Arrangement.SpaceBetween, modifier = Modifier.fillMaxWidth() ) { Text("Available Escrow Balance:", color = TextSecondary, style = MaterialTheme.typography.bodySmall) Text(String.format(Locale.US, "৳%,.0f BDT", userWallet.availableBalance), color = TextPrimary, fontWeight = FontWeight.Bold) } Row( horizontalArrangement = Arrangement.SpaceBetween, modifier = Modifier.fillMaxWidth() ) { Text("Locked Security Deposits:", color = TextSecondary, style = MaterialTheme.typography.bodySmall) Text(String.format(Locale.US, "৳%,.0f BDT", userWallet.totalBalance - userWallet.availableBalance), color = Color(0xFFF59E0B), fontWeight = FontWeight.Bold) } Spacer(modifier = Modifier.height(18.dp)) Button( onClick = { showDepositDialog = true }, modifier = Modifier .fillMaxWidth() .testTag("add_funds_btn"), colors = ButtonDefaults.buttonColors(containerColor = BrandGold, contentColor = Color.White), shape = RoundedCornerShape(12.dp) ) { Icon(Icons.Default.Add, "add") Spacer(modifier = Modifier.width(6.dp)) Text("Add Funds via Visa / Gateway", fontWeight = FontWeight.Bold) } Spacer(modifier = Modifier.height(10.dp)) OutlinedButton( onClick = { showAgentDepositDialog = true }, modifier = Modifier .fillMaxWidth() .testTag("agent_add_funds_btn"), colors = ButtonDefaults.outlinedButtonColors(contentColor = CyanAccent), border = BorderStroke(1.dp, CyanAccent), shape = RoundedCornerShape(12.dp) ) { Icon(Icons.Default.LocalAtm, "agent_add") Spacer(modifier = Modifier.width(6.dp)) Text("Deposit Cash via Licensed Agent", fontWeight = FontWeight.Bold) } } } } // Transactions Header item { Text("Cleared Transaction Audit History", style = MaterialTheme.typography.titleMedium, fontWeight = FontWeight.Bold, color = TextPrimary) Spacer(modifier = Modifier.height(4.dp)) } // History list with custom tags if (logs.isEmpty()) { item { Box(modifier = Modifier.fillMaxWidth().padding(40.dp), contentAlignment = Alignment.Center) { Text("No transactions logged in secure system ledger.", color = TextSecondary, style = MaterialTheme.typography.bodyMedium) } } } else { items(logs) { log -> val isCredit = log.amount > 0 val tagLabel = when { log.type == "MEMBERSHIP_UPGRADE" -> "Membership Payment" log.description.contains("booking", ignoreCase = true) -> "Booking Payment" else -> "Fund Credit" } Card( modifier = Modifier.fillMaxWidth(), colors = CardDefaults.cardColors(containerColor = DarkGreyGlass), shape = RoundedCornerShape(12.dp) ) { Row( modifier = Modifier.padding(14.dp), verticalAlignment = Alignment.CenterVertically ) { Box( modifier = Modifier .size(36.dp) .clip(CircleShape) .background(if (isCredit) Color.Green.copy(alpha = 0.12f) else CoralRed.copy(alpha = 0.12f)), contentAlignment = Alignment.Center ) { Icon( if (isCredit) Icons.Default.ArrowUpward else Icons.Default.ArrowDownward, "dir", tint = if (isCredit) Color.Green else CoralRed, modifier = Modifier.size(16.dp) ) } Spacer(modifier = Modifier.width(14.dp)) Column(modifier = Modifier.weight(1f)) { Row(verticalAlignment = Alignment.CenterVertically) { Text(tagLabel, style = MaterialTheme.typography.labelSmall, color = BrandGold, fontWeight = FontWeight.ExtraBold) Spacer(modifier = Modifier.width(8.dp)) Text( log.type.replace("_", " "), style = MaterialTheme.typography.labelSmall, color = TextSecondary ) } Text(log.description, style = MaterialTheme.typography.bodyMedium, color = TextPrimary, fontWeight = FontWeight.Medium) } Spacer(modifier = Modifier.width(8.dp)) Text( text = if (isCredit) "+৳${String.format(Locale.US, "%,.0f", log.amount)}" else "-৳${String.format(Locale.US, "%,.0f", Math.abs(log.amount))}", color = if (isCredit) Color.Green else CoralRed, fontWeight = FontWeight.Bold, style = MaterialTheme.typography.bodyMedium ) } } } } item { Spacer(modifier = Modifier.height(30.dp)) } } // Modal Deposit Gateway Dialog if (showDepositDialog) { Dialog(onDismissRequest = { showDepositDialog = false }) { Card( shape = RoundedCornerShape(20.dp), colors = CardDefaults.cardColors(containerColor = DarkGreyGlassElevated), modifier = Modifier.padding(16.dp) ) { Column(modifier = Modifier.padding(24.dp)) { Text("Top-up Wallet", style = MaterialTheme.typography.titleLarge, fontWeight = FontWeight.Bold, color = TextPrimary) Text("Secure routing via simulated commercial banking API.", style = MaterialTheme.typography.bodySmall, color = TextSecondary) Spacer(modifier = Modifier.height(16.dp)) TextField( value = depositInputAmount, onValueChange = { depositInputAmount = it }, modifier = Modifier.fillMaxWidth().testTag("deposit_amount_field"), label = { Text("Deposit Amount (৳)") }, keyboardOptions = KeyboardOptions(keyboardType = KeyboardType.Number), colors = TextFieldDefaults.colors(focusedContainerColor = DarkGreyGlass, unfocusedContainerColor = DarkGreyGlass) ) Spacer(modifier = Modifier.height(10.dp)) TextField( value = cardNumber, onValueChange = { cardNumber = it }, modifier = Modifier.fillMaxWidth().testTag("payment_details_field"), label = { Text("Card Number (Visa/Mastercard)") }, keyboardOptions = KeyboardOptions(keyboardType = KeyboardType.Number), colors = TextFieldDefaults.colors(focusedContainerColor = DarkGreyGlass, unfocusedContainerColor = DarkGreyGlass) ) Spacer(modifier = Modifier.height(24.dp)) Row(horizontalArrangement = Arrangement.spacedBy(10.dp), modifier = Modifier.fillMaxWidth()) { OutlinedButton( onClick = { showDepositDialog = false }, modifier = Modifier.weight(1f) ) { Text("Reject") } Button( onClick = { val amtNum = depositInputAmount.toDoubleOrNull() ?: 0.0 if (amtNum > 0) { viewModel.depositWalletFunds(amtNum) showDepositDialog = false depositInputAmount = "" } }, colors = ButtonDefaults.buttonColors(containerColor = BrandGold, contentColor = Color.White), modifier = Modifier.weight(1f).testTag("payment_submit_btn") ) { Text("Verify Pay", fontWeight = FontWeight.Bold) } } } } } } if (showAgentDepositDialog) { val userWallets = viewModel.allUserWallets.collectAsState(initial = emptyList()).value val approvedAgents = userWallets.filter { it.isAgent } var inputWalletNumber by remember { mutableStateOf("") } var inputTrxLast4 by remember { mutableStateOf("") } var inputScreenshotName by remember { mutableStateOf("") } Dialog(onDismissRequest = { showAgentDepositDialog = false requestSubmittedMessage = null agentDepositAmount = "" selectedAgentId = "" inputWalletNumber = "" inputTrxLast4 = "" inputScreenshotName = "" }) { Card( shape = RoundedCornerShape(20.dp), colors = CardDefaults.cardColors(containerColor = DarkGreyGlassElevated), modifier = Modifier.padding(16.dp) ) { Column( modifier = Modifier .padding(24.dp) .heightIn(max = 550.dp), verticalArrangement = Arrangement.spacedBy(12.dp) ) { Text("Cash Deposit via Agent", style = MaterialTheme.typography.titleLarge, fontWeight = FontWeight.Bold, color = TextPrimary) Text("Send your payment to the Cash Agent and provide details below to get credited.", style = MaterialTheme.typography.bodySmall, color = TextSecondary) if (requestSubmittedMessage != null) { Surface( color = Color.Green.copy(0.12f), shape = RoundedCornerShape(8.dp), modifier = Modifier.fillMaxWidth() ) { Text( text = requestSubmittedMessage!!, style = MaterialTheme.typography.bodySmall, color = Color.Green, modifier = Modifier.padding(12.dp) ) } Button( onClick = { showAgentDepositDialog = false requestSubmittedMessage = null agentDepositAmount = "" selectedAgentId = "" inputWalletNumber = "" inputTrxLast4 = "" inputScreenshotName = "" }, colors = ButtonDefaults.buttonColors(containerColor = BrandGold, contentColor = Color.Black), modifier = Modifier.fillMaxWidth().testTag("agent_dialog_dismiss_ok") ) { Text("Acknowledge", fontWeight = FontWeight.Bold) } } else { OutlinedTextField( value = agentDepositAmount, onValueChange = { agentDepositAmount = it }, label = { Text("Amount in BDT (৳)") }, singleLine = true, keyboardOptions = KeyboardOptions(keyboardType = KeyboardType.Number), colors = TextFieldDefaults.colors( focusedContainerColor = DarkGreyGlass, unfocusedContainerColor = DarkGreyGlass, focusedTextColor = TextPrimary, unfocusedTextColor = TextPrimary, focusedLabelColor = BrandGold ), modifier = Modifier.fillMaxWidth().testTag("agent_deposit_amount") ) OutlinedTextField( value = inputWalletNumber, onValueChange = { inputWalletNumber = it }, label = { Text("Your Wallet Number (bKash/Nagad/Rocket)") }, singleLine = true, colors = TextFieldDefaults.colors( focusedContainerColor = DarkGreyGlass, unfocusedContainerColor = DarkGreyGlass, focusedTextColor = TextPrimary, unfocusedTextColor = TextPrimary, focusedLabelColor = BrandGold ), modifier = Modifier.fillMaxWidth().testTag("agent_deposit_wallet_number") ) OutlinedTextField( value = inputTrxLast4, onValueChange = { inputTrxLast4 = it }, label = { Text("Transaction ID Last 4 Digits") }, singleLine = true, colors = TextFieldDefaults.colors( focusedContainerColor = DarkGreyGlass, unfocusedContainerColor = DarkGreyGlass, focusedTextColor = TextPrimary, unfocusedTextColor = TextPrimary, focusedLabelColor = BrandGold ), modifier = Modifier.fillMaxWidth().testTag("agent_deposit_trx_last4") ) // Simulating Image Uploading if (inputScreenshotName.isEmpty()) { Button( onClick = { inputScreenshotName = "trx_proof_screenshot_${System.currentTimeMillis().toString().substring(8)}.png" }, colors = ButtonDefaults.buttonColors(containerColor = DarkGreyGlass, contentColor = BrandGold), modifier = Modifier.fillMaxWidth().testTag("agent_deposit_screenshot_btn"), border = BorderStroke(1.dp, BrandGold.copy(0.4f)) ) { Icon(Icons.Default.CameraAlt, "upload", modifier = Modifier.size(16.dp)) Spacer(modifier = Modifier.width(6.dp)) Text("Upload Payment Screenshot Proof", fontSize = 11.sp, fontWeight = FontWeight.Bold) } } else { Row( modifier = Modifier .fillMaxWidth() .background(DarkGreyGlass, RoundedCornerShape(8.dp)) .border(BorderStroke(1.dp, Color.Green.copy(0.4f)), RoundedCornerShape(8.dp)) .padding(8.dp), horizontalArrangement = Arrangement.SpaceBetween, verticalAlignment = Alignment.CenterVertically ) { Row(verticalAlignment = Alignment.CenterVertically) { Icon(Icons.Default.CheckCircle, "checked", tint = Color.Green, modifier = Modifier.size(16.dp)) Spacer(modifier = Modifier.width(8.dp)) Text(inputScreenshotName, fontSize = 11.sp, color = TextPrimary) } Text( "Remove", fontSize = 11.sp, color = CoralRed, modifier = Modifier.clickable { inputScreenshotName = "" } ) } } Text("Select Local Licensed Agent to Verify:", style = MaterialTheme.typography.labelSmall, color = TextSecondary) if (approvedAgents.isEmpty()) { Text("No cash agents authorized. Please contact support or apply as agent.", color = CoralRed, style = MaterialTheme.typography.bodySmall) } else { LazyColumn( modifier = Modifier.heightIn(max = 140.dp), verticalArrangement = Arrangement.spacedBy(6.dp) ) { items(approvedAgents) { agent -> val isSelected = selectedAgentId == agent.userId Card( modifier = Modifier .fillMaxWidth() .clickable { selectedAgentId = agent.userId } .testTag("agent_picker_item_${agent.userId}"), colors = CardColors( containerColor = if (isSelected) LightGold.copy(0.15f) else DarkGreyGlass, contentColor = if (isSelected) BrandGold else TextPrimary, disabledContainerColor = DarkGreyGlass, disabledContentColor = TextSecondary ), border = BorderStroke(1.dp, if (isSelected) BrandGold else DarkGreyGlassElevated) ) { Column(modifier = Modifier.padding(10.dp)) { Text(agent.name, fontWeight = FontWeight.Bold, style = MaterialTheme.typography.bodySmall, color = if (isSelected) BrandGold else TextPrimary) Text("📱 Wallet / Phone: ${agent.agentPhone}", style = MaterialTheme.typography.labelSmall, color = TextPrimary) Text("📍 Location: ${agent.agentLocation}", style = MaterialTheme.typography.labelSmall, color = TextSecondary) } } } } } val selectedAgent = approvedAgents.find { it.userId == selectedAgentId } if (selectedAgent != null) { Surface( color = BrandGold.copy(0.1f), shape = RoundedCornerShape(8.dp), border = BorderStroke(1.dp, BrandGold.copy(0.3f)), modifier = Modifier.fillMaxWidth() ) { Column(modifier = Modifier.padding(8.dp)) { Text("⚠️ SEND MONEY INSTRUCTIONS", style = MaterialTheme.typography.labelSmall, color = BrandGold, fontWeight = FontWeight.Bold) Text("Please Send Money to this Agent wallet first:", style = MaterialTheme.typography.bodySmall, color = TextPrimary) Spacer(modifier = Modifier.height(2.dp)) Text("📱 WALLET NUMBER: ${selectedAgent.agentPhone}", fontWeight = FontWeight.SemiBold, fontSize = 11.sp, color = TextPrimary) Text("👤 WALLET NAME: ${selectedAgent.name}", fontWeight = FontWeight.SemiBold, fontSize = 11.sp, color = TextPrimary) } } } Row( horizontalArrangement = Arrangement.spacedBy(10.dp), modifier = Modifier.fillMaxWidth() ) { OutlinedButton( onClick = { showAgentDepositDialog = false agentDepositAmount = "" selectedAgentId = "" }, modifier = Modifier.weight(1f) ) { Text("Cancel") } val amtVal = agentDepositAmount.toDoubleOrNull() ?: 0.0 val canSubmit = amtVal > 0.0 && selectedAgentId.isNotEmpty() && inputWalletNumber.isNotEmpty() && inputTrxLast4.isNotEmpty() && inputScreenshotName.isNotEmpty() Button( onClick = { val agentSelected = approvedAgents.find { it.userId == selectedAgentId } if (agentSelected != null && amtVal > 0.0) { viewModel.requestCashDeposit( agentId = selectedAgentId, agentName = agentSelected.name, amount = amtVal, walletNumber = inputWalletNumber, trxLast4 = inputTrxLast4, screenshot = inputScreenshotName ) requestSubmittedMessage = "Deposit billing submitted! Requested ৳$amtVal BDT cash-in via Agent ${agentSelected.name}.\n\nThe Cash Agent will review your screenshot proof and match Transaction Last 4 Digits (*$inputTrxLast4) shortly. Upon verification and final superuser admin approval, your balance will be credited!" } }, enabled = canSubmit, colors = ButtonDefaults.buttonColors(containerColor = BrandGold, contentColor = Color.Black), modifier = Modifier.weight(1.2f).testTag("agent_deposit_submit_btn") ) { Text("Submit", fontWeight = FontWeight.Bold) } } } } } } } } // ---------------------------------------------------- // TAB 6: PREMIUM MEMBERSHIP MANAGEMENT // ---------------------------------------------------- @Composable fun UserMembershipTab( viewModel: PortalViewModel, userWallet: UserWallet ) { var upgradedMessage by remember { mutableStateOf(null) } LazyColumn( modifier = Modifier .fillMaxSize() .padding(horizontal = 16.dp), verticalArrangement = Arrangement.spacedBy(16.dp) ) { item { Spacer(modifier = Modifier.height(12.dp)) Text("Premium Membership Portals", style = MaterialTheme.typography.titleLarge, fontWeight = FontWeight.Bold, color = TextPrimary) Text("Unlock extreme priority matching, high-limit escrow locks, and priority VIP messaging.", style = MaterialTheme.typography.bodyMedium, color = TextSecondary) } // Plan 1: Free item { MembershipPlanListItem( title = "Free Membership", cost = 0.0, desc = "Standard system access with standard booking locks and standard location limits.", isActive = userWallet.membershipStatus == "Free Membership", onSelect = { } ) } // Plan 2: VIP Membership item { MembershipPlanListItem( title = "VIP Membership", cost = 5000.0, desc = "Prioritized safehouse booking matching, dedicated account representative, and expanded duration custom bounds.", isActive = userWallet.membershipStatus == "VIP Membership", onSelect = { viewModel.upgradeUserMembership( "VIP Membership", 5000.0, onSuccess = { upgradedMessage = "Welcome to VIP Tier Elite status! Your parameters updated." }, onError = { errMsg -> upgradedMessage = errMsg } ) } ) } // Plan 3: Premium Membership item { MembershipPlanListItem( title = "Premium Membership", cost = 10000.0, desc = "Maximum priorities, 24/7 priority customer dispatcher helpline, fee-free instant cancels, unlimited companion modeling.", isActive = userWallet.membershipStatus == "Premium Membership", onSelect = { viewModel.upgradeUserMembership( "Premium Membership", 10000.0, onSuccess = { upgradedMessage = "Welcome to Premium Membership! Maximum privileges unlocked." }, onError = { upgradedMessage = it } ) } ) } item { Spacer(modifier = Modifier.height(30.dp)) } } if (upgradedMessage != null) { AlertDialog( onDismissRequest = { upgradedMessage = null }, title = { Text("Membership Portal Security") }, text = { Text(upgradedMessage!!) }, confirmButton = { TextButton(onClick = { upgradedMessage = null }) { Text("OK", color = BrandGold) } }, containerColor = DarkGreyGlassElevated, titleContentColor = TextPrimary, textContentColor = TextPrimary ) } } @Composable fun MembershipPlanListItem( title: String, cost: Double, desc: String, isActive: Boolean, onSelect: () -> Unit ) { Card( modifier = Modifier.fillMaxWidth().testTag("membership_tier_card_${title.replace(" ", "_")}"), colors = CardDefaults.cardColors(containerColor = if (isActive) LightGold else DarkGreyGlass), border = BorderStroke(1.dp, if (isActive) BrandGold else DarkGreyGlassElevated) ) { Column(modifier = Modifier.padding(20.dp)) { Row( modifier = Modifier.fillMaxWidth(), horizontalArrangement = Arrangement.SpaceBetween, verticalAlignment = Alignment.CenterVertically ) { Text(title, style = MaterialTheme.typography.titleMedium, fontWeight = FontWeight.Bold, color = TextPrimary) Surface( shape = RoundedCornerShape(8.dp), color = if (isActive) BrandGold else DarkGreyGlassElevated, contentColor = if (isActive) Color.White else TextSecondary ) { Text( if (isActive) "Active Tier" else String.format(Locale.US, "৳%,.0f BDT", cost), fontWeight = FontWeight.Bold, style = MaterialTheme.typography.labelSmall, modifier = Modifier.padding(horizontal = 10.dp, vertical = 6.dp) ) } } Spacer(modifier = Modifier.height(8.dp)) Text(desc, style = MaterialTheme.typography.bodySmall, color = TextSecondary) if (!isActive && cost > 0) { Spacer(modifier = Modifier.height(14.dp)) Button( onClick = onSelect, colors = ButtonDefaults.buttonColors(containerColor = BrandGold, contentColor = Color.White), shape = RoundedCornerShape(8.dp), modifier = Modifier.fillMaxWidth() ) { Text("Upgrade Tier Immediately", fontWeight = FontWeight.Bold) } } } } } // ---------------------------------------------------- // TAB 7: USER PORTAL SETTINGS & INFORMATION FORM // ---------------------------------------------------- @Composable fun UserSettingsTab( viewModel: PortalViewModel, userWallet: UserWallet ) { var rawName by remember { mutableStateOf(userWallet.name) } var rawEmail by remember { mutableStateOf(userWallet.email) } var telephoneNum by remember { mutableStateOf("+880 1712-345678") } var customPass by remember { mutableStateOf("") } // Switch Toggles var emailNotifyToggle by remember { mutableStateOf(true) } var smsNotifyToggle by remember { mutableStateOf(false) } var pushNotifyToggle by remember { mutableStateOf(true) } var showPublicToggle by remember { mutableStateOf(false) } var keepAnonymousToggle by remember { mutableStateOf(true) } var saveConfirmationMessage by remember { mutableStateOf(null) } LazyColumn( modifier = Modifier .fillMaxSize() .padding(horizontal = 16.dp), verticalArrangement = Arrangement.spacedBy(16.dp) ) { item { Spacer(modifier = Modifier.height(12.dp)) Text("Profile Information & Security Settings", style = MaterialTheme.typography.titleLarge, fontWeight = FontWeight.Bold, color = TextPrimary) Text("Alter your client profile parameters, manage dispatch alerts and toggles.", style = MaterialTheme.typography.bodyMedium, color = TextSecondary) } // 🔐 USER ACCOUNT VERIFICATION SYSTEM CARD item { var showEmailWizard by remember { mutableStateOf(false) } var showPhoneWizard by remember { mutableStateOf(false) } var isSmsSent by remember { mutableStateOf(false) } var smsOtpInput by remember { mutableStateOf("") } var kycNidInput by remember { mutableStateOf("") } var kycSelfieInput by remember { mutableStateOf("https://images.unsplash.com/photo-1544005313-94ddf0286df2?auto=format&fit=crop&w=400&q=80") } var firebaseVerificationId by remember { mutableStateOf("") } var tempPhoneInput by remember { mutableStateOf(userWallet.phone.ifEmpty { "+880 1712-345678" }) } var tempEmailInput by remember { mutableStateOf(userWallet.email.ifEmpty { "user@example.com" }) } var isVerifyingFirebase by remember { mutableStateOf(false) } var firebaseStatusMessage by remember { mutableStateOf("") } val firebaseLogs by viewModel.firebaseLogs.collectAsState(initial = emptyList()) val context = androidx.compose.ui.platform.LocalContext.current val activity = context as? android.app.Activity Card( colors = CardDefaults.cardColors(containerColor = DarkGreyGlassElevated), modifier = Modifier.fillMaxWidth().testTag("user_verification_card"), shape = RoundedCornerShape(16.dp), border = BorderStroke(1.dp, if (userWallet.verificationStatus == "verified") Color.Green.copy(0.3f) else BrandGold.copy(0.2f)) ) { Column(modifier = Modifier.padding(18.dp)) { Row( modifier = Modifier.fillMaxWidth(), horizontalArrangement = Arrangement.SpaceBetween, verticalAlignment = Alignment.CenterVertically ) { Column { Text( text = "Account Verification Status", style = MaterialTheme.typography.titleMedium, fontWeight = FontWeight.Bold, color = TextPrimary ) Text( text = "Secure trust status for global marketplace payments.", style = MaterialTheme.typography.bodySmall, color = TextSecondary ) } // Badge representation val (statusText, badgeColor, badgeBg) = when (userWallet.verificationStatus) { "verified" -> Triple("VERIFIED CLIENT", Color.Green, Color.Green.copy(0.12f)) "pending" -> Triple("PENDING AUDIT", BrandGold, BrandGold.copy(0.12f)) "rejected" -> Triple("REJECTED / RETRY", CoralRed, CoralRed.copy(0.12f)) else -> Triple("UNVERIFIED", TextSecondary, DarkGreyGlass) } Surface( shape = RoundedCornerShape(6.dp), color = badgeBg, contentColor = badgeColor ) { Text( text = statusText, style = MaterialTheme.typography.labelSmall, modifier = Modifier.padding(horizontal = 10.dp, vertical = 4.dp), fontWeight = FontWeight.ExtraBold ) } } Spacer(modifier = Modifier.height(16.dp)) HorizontalDivider(color = TextSecondary.copy(0.15f)) Spacer(modifier = Modifier.height(16.dp)) // 1. Email Verification Channel Row( modifier = Modifier.fillMaxWidth(), horizontalArrangement = Arrangement.SpaceBetween, verticalAlignment = Alignment.CenterVertically ) { Row(verticalAlignment = Alignment.CenterVertically) { Icon( imageVector = if (userWallet.emailVerified) Icons.Default.MarkEmailRead else Icons.Default.MailOutline, contentDescription = "Email verification", tint = if (userWallet.emailVerified) Color.Green else TextSecondary, modifier = Modifier.size(20.dp) ) Spacer(modifier = Modifier.width(10.dp)) Column { Text("Email Verification", style = MaterialTheme.typography.bodyMedium, color = TextPrimary, fontWeight = FontWeight.Bold) Text(userWallet.email, style = MaterialTheme.typography.bodySmall, color = TextSecondary) } } if (userWallet.emailVerified) { Row(verticalAlignment = Alignment.CenterVertically) { Icon(Icons.Default.CheckCircle, "verified", tint = Color.Green, modifier = Modifier.size(16.dp)) Spacer(modifier = Modifier.width(4.dp)) Text("Verified", style = MaterialTheme.typography.bodySmall, color = Color.Green, fontWeight = FontWeight.Bold) } } else { Button( onClick = { showEmailWizard = !showEmailWizard }, colors = ButtonDefaults.buttonColors(containerColor = BrandGold, contentColor = Color.Black), shape = RoundedCornerShape(6.dp), modifier = Modifier.height(28.dp).testTag("user_verify_email_trigger"), contentPadding = PaddingValues(horizontal = 12.dp, vertical = 0.dp) ) { Text("Verify", fontSize = 11.sp, fontWeight = FontWeight.Bold) } } } if (!userWallet.emailVerified && showEmailWizard) { Card( colors = CardDefaults.cardColors(containerColor = DarkGreyGlass), modifier = Modifier.fillMaxWidth().padding(top = 10.dp), shape = RoundedCornerShape(8.dp) ) { Column(modifier = Modifier.padding(12.dp)) { Text( "Enter email for Firebase Verification link dispatch:", style = MaterialTheme.typography.bodySmall, color = TextSecondary ) Spacer(modifier = Modifier.height(6.dp)) Row( modifier = Modifier.fillMaxWidth(), horizontalArrangement = Arrangement.spacedBy(8.dp), verticalAlignment = Alignment.CenterVertically ) { TextField( value = tempEmailInput, onValueChange = { tempEmailInput = it }, colors = TextFieldDefaults.colors(focusedContainerColor = DarkGreyGlassElevated, unfocusedContainerColor = DarkGreyGlassElevated), singleLine = true, modifier = Modifier.weight(1f), textStyle = MaterialTheme.typography.bodyMedium.copy(color = TextPrimary) ) Button( onClick = { isVerifyingFirebase = true viewModel.sendFirebaseEmailVerification(tempEmailInput, isModel = false) { success, msg -> isVerifyingFirebase = false firebaseStatusMessage = msg if (success) { showEmailWizard = false } } }, enabled = !isVerifyingFirebase, colors = ButtonDefaults.buttonColors(containerColor = BrandGold, contentColor = Color.Black), shape = RoundedCornerShape(6.dp), modifier = Modifier.height(44.dp).testTag("user_firebase_email_trigger") ) { if (isVerifyingFirebase) { CircularProgressIndicator(modifier = Modifier.size(16.dp), color = Color.Black, strokeWidth = 2.dp) } else { Text("Send Link", fontSize = 11.sp, fontWeight = FontWeight.Bold) } } } if (firebaseStatusMessage.isNotEmpty()) { Spacer(modifier = Modifier.height(4.dp)) Text(firebaseStatusMessage, style = MaterialTheme.typography.bodySmall, color = BrandGold) } } } } Spacer(modifier = Modifier.height(14.dp)) // 2. Phone Verification Channel Row( modifier = Modifier.fillMaxWidth(), horizontalArrangement = Arrangement.SpaceBetween, verticalAlignment = Alignment.CenterVertically ) { Row(verticalAlignment = Alignment.CenterVertically) { Icon( imageVector = if (userWallet.phoneVerified) Icons.Default.PhoneIphone else Icons.Default.PhonelinkRing, contentDescription = "Phone verification", tint = if (userWallet.phoneVerified) Color.Green else TextSecondary, modifier = Modifier.size(20.dp) ) Spacer(modifier = Modifier.width(10.dp)) Column { Text("Mobile Phone OTP Verification", style = MaterialTheme.typography.bodyMedium, color = TextPrimary, fontWeight = FontWeight.Bold) Text( text = if (userWallet.phoneVerified) userWallet.phone else "Verify phone to receive dispatch updates.", style = MaterialTheme.typography.bodySmall, color = TextSecondary ) } } if (userWallet.phoneVerified) { Row(verticalAlignment = Alignment.CenterVertically) { Icon(Icons.Default.CheckCircle, "verified", tint = Color.Green, modifier = Modifier.size(16.dp)) Spacer(modifier = Modifier.width(4.dp)) Text("Verified", style = MaterialTheme.typography.bodySmall, color = Color.Green, fontWeight = FontWeight.Bold) } } else { Button( onClick = { showPhoneWizard = !showPhoneWizard }, colors = ButtonDefaults.buttonColors(containerColor = BrandGold, contentColor = Color.Black), shape = RoundedCornerShape(6.dp), modifier = Modifier.height(28.dp).testTag("user_verify_phone_trigger"), contentPadding = PaddingValues(horizontal = 12.dp, vertical = 0.dp) ) { Text("Verify", fontSize = 11.sp, fontWeight = FontWeight.Bold) } } } if (!userWallet.phoneVerified && showPhoneWizard) { Card( colors = CardDefaults.cardColors(containerColor = DarkGreyGlass), modifier = Modifier.fillMaxWidth().padding(top = 10.dp), shape = RoundedCornerShape(8.dp) ) { Column(modifier = Modifier.padding(12.dp)) { if (!isSmsSent) { Text("Enter phone number to receive a 4-digit SMS OTP code:", style = MaterialTheme.typography.bodySmall, color = TextSecondary) Spacer(modifier = Modifier.height(6.dp)) Row(modifier = Modifier.fillMaxWidth(), horizontalArrangement = Arrangement.spacedBy(8.dp)) { TextField( value = tempPhoneInput, onValueChange = { tempPhoneInput = it }, colors = TextFieldDefaults.colors(focusedContainerColor = DarkGreyGlassElevated, unfocusedContainerColor = DarkGreyGlassElevated), singleLine = true, modifier = Modifier.weight(1f), textStyle = MaterialTheme.typography.bodyMedium.copy(color = TextPrimary) ) Button( onClick = { if (activity != null) { isVerifyingFirebase = true viewModel.startFirebasePhoneVerification( activity = activity, phoneNum = tempPhoneInput, onCodeSent = { verificationId -> isVerifyingFirebase = false firebaseVerificationId = verificationId isSmsSent = true firebaseStatusMessage = "Firebase verification code sent! Enter code to verify." }, onVerificationComplete = { success, msg -> isVerifyingFirebase = false firebaseStatusMessage = msg } ) } }, enabled = !isVerifyingFirebase, colors = ButtonDefaults.buttonColors(containerColor = BrandGold, contentColor = Color.Black), shape = RoundedCornerShape(6.dp), modifier = Modifier.height(44.dp).testTag("user_sms_send_otp") ) { if (isVerifyingFirebase) { CircularProgressIndicator(modifier = Modifier.size(16.dp), color = Color.Black, strokeWidth = 2.dp) } else { Text("Send OTP", fontSize = 11.sp, fontWeight = FontWeight.Bold) } } } } else { Text("Verification Code sent! (Simulation code is 1234):", style = MaterialTheme.typography.bodySmall, color = TextSecondary) Spacer(modifier = Modifier.height(6.dp)) Row(modifier = Modifier.fillMaxWidth(), horizontalArrangement = Arrangement.spacedBy(8.dp)) { TextField( value = smsOtpInput, onValueChange = { smsOtpInput = it }, colors = TextFieldDefaults.colors(focusedContainerColor = DarkGreyGlassElevated, unfocusedContainerColor = DarkGreyGlassElevated), singleLine = true, placeholder = { Text("Code") }, modifier = Modifier.weight(1f), textStyle = MaterialTheme.typography.bodyMedium.copy(color = TextPrimary), keyboardOptions = KeyboardOptions(keyboardType = KeyboardType.Number) ) Button( onClick = { isVerifyingFirebase = true viewModel.submitFirebaseOtp( verificationId = firebaseVerificationId, code = smsOtpInput, isModel = false, phoneNum = tempPhoneInput ) { success, msg -> isVerifyingFirebase = false firebaseStatusMessage = msg if (success) { showPhoneWizard = false isSmsSent = false smsOtpInput = "" } } }, enabled = !isVerifyingFirebase, colors = ButtonDefaults.buttonColors(containerColor = Color.Green, contentColor = Color.Black), shape = RoundedCornerShape(6.dp), modifier = Modifier.height(44.dp).testTag("user_sms_otp_submit") ) { if (isVerifyingFirebase) { CircularProgressIndicator(modifier = Modifier.size(16.dp), color = Color.Black, strokeWidth = 2.dp) } else { Text("Submit OTP", fontSize = 11.sp, fontWeight = FontWeight.Bold) } } } } if (firebaseStatusMessage.isNotEmpty()) { Spacer(modifier = Modifier.height(4.dp)) Text(firebaseStatusMessage, style = MaterialTheme.typography.bodySmall, color = BrandGold) } } } } // Bottom Drawer Console representing active trace if (showEmailWizard || showPhoneWizard) { Spacer(modifier = Modifier.height(10.dp)) FirebaseConsoleCard( logs = firebaseLogs, onClearLogs = { viewModel.clearFirebaseLogs() } ) } Spacer(modifier = Modifier.height(14.dp)) // 3. Model Identity Verification (KYC Verification) Row( modifier = Modifier.fillMaxWidth(), horizontalArrangement = Arrangement.SpaceBetween, verticalAlignment = Alignment.CenterVertically ) { Row(verticalAlignment = Alignment.CenterVertically) { Icon( imageVector = if (userWallet.kycStatus == "verified") Icons.Default.Badge else Icons.Default.AssignmentInd, contentDescription = "KYC status", tint = if (userWallet.kycStatus == "verified") Color.Green else if (userWallet.kycStatus == "rejected") CoralRed else if (userWallet.kycStatus == "pending") BrandGold else TextSecondary, modifier = Modifier.size(20.dp) ) Spacer(modifier = Modifier.width(10.dp)) Column { Text("NID / Identity Verification (KYC)", style = MaterialTheme.typography.bodyMedium, color = TextPrimary, fontWeight = FontWeight.Bold) val subText = when (userWallet.kycStatus) { "verified" -> "NID Card ID: ${userWallet.nidNumber}" "pending" -> "Reviewing documents and face biometric selfie..." "rejected" -> "Documents rejected. Please re-submit verification cards." else -> "Requires National ID document & face selfie verification." } Text(subText, style = MaterialTheme.typography.bodySmall, color = TextSecondary) } } if (userWallet.kycStatus == "verified") { Row(verticalAlignment = Alignment.CenterVertically) { Icon(Icons.Default.CheckCircle, "verified", tint = Color.Green, modifier = Modifier.size(16.dp)) Spacer(modifier = Modifier.width(4.dp)) Text("Approved", style = MaterialTheme.typography.bodySmall, color = Color.Green, fontWeight = FontWeight.Bold) } } else if (userWallet.kycStatus == "pending") { Row(verticalAlignment = Alignment.CenterVertically) { CircularProgressIndicator(color = BrandGold, modifier = Modifier.size(14.dp), strokeWidth = 2.dp) Spacer(modifier = Modifier.width(4.dp)) Text("Pending", style = MaterialTheme.typography.bodySmall, color = BrandGold, fontWeight = FontWeight.Medium) } } else { Row(verticalAlignment = Alignment.CenterVertically) { if (userWallet.kycStatus == "rejected") { Text("Rejected", style = MaterialTheme.typography.bodySmall, color = CoralRed, fontWeight = FontWeight.Bold, modifier = Modifier.padding(end = 8.dp)) } val kycButtonLabel = if (userWallet.kycStatus == "rejected") "Re-verify" else "Verify" Button( onClick = { // Toggle showing the form if (kycNidInput.isEmpty()) { kycNidInput = "551239854" } viewModel.submitUserKYC(kycNidInput, kycSelfieInput) }, colors = ButtonDefaults.buttonColors(containerColor = BrandGold, contentColor = Color.Black), shape = RoundedCornerShape(6.dp), modifier = Modifier.height(28.dp).testTag("user_submit_kyc_btn"), contentPadding = PaddingValues(horizontal = 12.dp, vertical = 0.dp) ) { Text(kycButtonLabel, fontSize = 11.sp, fontWeight = FontWeight.Bold) } } } } if (userWallet.kycStatus != "verified" && userWallet.kycStatus != "pending") { Card( colors = CardDefaults.cardColors(containerColor = DarkGreyGlass), modifier = Modifier.fillMaxWidth().padding(top = 12.dp) ) { Column(modifier = Modifier.padding(12.dp), verticalArrangement = Arrangement.spacedBy(8.dp)) { Text("KYC Document Upload Simulator", style = MaterialTheme.typography.labelSmall, color = BrandGold, fontWeight = FontWeight.Bold) TextField( value = kycNidInput, onValueChange = { kycNidInput = it }, label = { Text("National ID / Passport Number") }, colors = TextFieldDefaults.colors(focusedContainerColor = DarkGreyGlassElevated, unfocusedContainerColor = DarkGreyGlassElevated), singleLine = true, modifier = Modifier.fillMaxWidth() ) TextField( value = kycSelfieInput, onValueChange = { kycSelfieInput = it }, label = { Text("Live Photo Face Selfie URL") }, colors = TextFieldDefaults.colors(focusedContainerColor = DarkGreyGlassElevated, unfocusedContainerColor = DarkGreyGlassElevated), singleLine = true, modifier = Modifier.fillMaxWidth() ) Text("Selfie preview simulation: [Automatic high-fidelity camera feed connected]", style = MaterialTheme.typography.bodySmall, color = TextSecondary) } } } } } } // 💵 CASH AGENT LICENSE REGISTRATION CHANNEL item { var inputPhone by remember { mutableStateOf("") } var inputNid by remember { mutableStateOf("") } var inputLocation by remember { mutableStateOf("") } var appliedMessage by remember { mutableStateOf(null) } Card( colors = CardDefaults.cardColors(containerColor = DarkGreyGlassElevated), modifier = Modifier.fillMaxWidth().testTag("cash_agent_registration_card"), shape = RoundedCornerShape(16.dp), border = BorderStroke(1.dp, if (userWallet.isAgent) Color.Green.copy(0.3f) else CyanAccent.copy(0.2f)) ) { Column(modifier = Modifier.padding(18.dp), verticalArrangement = Arrangement.spacedBy(10.dp)) { Row( modifier = Modifier.fillMaxWidth(), horizontalArrangement = Arrangement.SpaceBetween, verticalAlignment = Alignment.CenterVertically ) { Column { Text( text = "Fintech Cash Agent License", style = MaterialTheme.typography.titleMedium, fontWeight = FontWeight.Bold, color = TextPrimary ) Text( text = "Apply to become a verified local cash deposit terminal.", style = MaterialTheme.typography.bodySmall, color = TextSecondary ) } val (statusTxt, badgeClr, badgeBgClr) = when { userWallet.isAgent -> Triple("LICENSED AGENT", Color.Green, Color.Green.copy(0.12f)) userWallet.agentApplicationStatus == "pending" -> Triple("PENDING LICENSE", BrandGold, BrandGold.copy(0.12f)) userWallet.agentApplicationStatus == "rejected" -> Triple("LICENSE REJECTED", CoralRed, CoralRed.copy(0.12f)) else -> Triple("NOT SIGNED", TextSecondary, DarkGreyGlass) } Surface( shape = RoundedCornerShape(6.dp), color = badgeBgClr, contentColor = badgeClr ) { Text( text = statusTxt, style = MaterialTheme.typography.labelSmall, modifier = Modifier.padding(horizontal = 10.dp, vertical = 4.dp), fontWeight = FontWeight.ExtraBold ) } } HorizontalDivider(color = TextSecondary.copy(0.15f)) if (userWallet.isAgent) { Text( text = "Congratulations! Your Cash Agent application is approved and your local cash depot terminal is live.", style = MaterialTheme.typography.bodyMedium, color = Color.Green ) Column( verticalArrangement = Arrangement.spacedBy(4.dp), modifier = Modifier .fillMaxWidth() .background(DarkGreyGlass, RoundedCornerShape(8.dp)) .padding(12.dp) ) { Text("Agent ID: default_agent_node", style = MaterialTheme.typography.bodySmall, color = TextSecondary) Text("Registered Phone: ${userWallet.agentPhone}", style = MaterialTheme.typography.bodySmall, color = TextPrimary) Text("NID: ${userWallet.agentNid}", style = MaterialTheme.typography.bodySmall, color = TextPrimary) Text("depot Terminal Address: ${userWallet.agentLocation}", style = MaterialTheme.typography.bodySmall, color = TextPrimary) } Text( text = "Access your dedicated terminal commands from 'Cash Agent Hub' in the navigation menu.", style = MaterialTheme.typography.labelSmall, color = BrandGold, fontWeight = FontWeight.Bold ) } else if (userWallet.agentApplicationStatus == "pending") { Text( text = "Your application block has been submitted to the marketplace authorization ledger.", style = MaterialTheme.typography.bodyMedium, color = BrandGold ) Column( verticalArrangement = Arrangement.spacedBy(4.dp), modifier = Modifier .fillMaxWidth() .background(DarkGreyGlass, RoundedCornerShape(8.dp)) .padding(12.dp) ) { Text("NID Number: ${userWallet.agentNid}", style = MaterialTheme.typography.bodySmall, color = TextPrimary) Text("Phone terminal: ${userWallet.agentPhone}", style = MaterialTheme.typography.bodySmall, color = TextPrimary) Text("Specified Location: ${userWallet.agentLocation}", style = MaterialTheme.typography.bodySmall, color = TextPrimary) } Text( text = "Authorization validation typically takes up to 3 business minutes.", style = MaterialTheme.typography.labelSmall, color = TextSecondary ) } else { if (userWallet.agentApplicationStatus == "rejected") { Text( text = "Your previous application was rejected. Please review submission details and apply again.", style = MaterialTheme.typography.bodySmall, color = CoralRed ) } else { Text( text = "Underwrite local physical cash collections in your region, earn 5% system commission directly credited to your wallet escrow.", style = MaterialTheme.typography.bodySmall, color = TextSecondary ) } if (appliedMessage != null) { Text(appliedMessage!!, color = Color.Green, style = MaterialTheme.typography.bodyMedium) } OutlinedTextField( value = inputPhone, onValueChange = { inputPhone = it }, label = { Text("Agent Contact Phone Number") }, singleLine = true, colors = TextFieldDefaults.colors(focusedContainerColor = DarkGreyGlass, unfocusedContainerColor = DarkGreyGlass), modifier = Modifier.fillMaxWidth().testTag("agent_apply_phone") ) OutlinedTextField( value = inputNid, onValueChange = { inputNid = it }, label = { Text("National ID (NID) Number") }, singleLine = true, colors = TextFieldDefaults.colors(focusedContainerColor = DarkGreyGlass, unfocusedContainerColor = DarkGreyGlass), modifier = Modifier.fillMaxWidth().testTag("agent_apply_nid") ) OutlinedTextField( value = inputLocation, onValueChange = { inputLocation = it }, label = { Text("Depot / Physical Outlet Location Address") }, singleLine = true, colors = TextFieldDefaults.colors(focusedContainerColor = DarkGreyGlass, unfocusedContainerColor = DarkGreyGlass), modifier = Modifier.fillMaxWidth().testTag("agent_apply_location") ) val isFormValid = inputPhone.isNotEmpty() && inputNid.isNotEmpty() && inputLocation.isNotEmpty() Button( onClick = { if (isFormValid) { viewModel.applyForCashAgent(inputPhone, inputNid, inputLocation) appliedMessage = "Application successfully compiled & signed. Status set to pending!" inputPhone = "" inputNid = "" inputLocation = "" } }, enabled = isFormValid, colors = ButtonDefaults.buttonColors(containerColor = CyanAccent, contentColor = Color.Black), modifier = Modifier.fillMaxWidth().testTag("agent_apply_submit_btn"), shape = RoundedCornerShape(8.dp) ) { Text("Submit License Application", fontWeight = FontWeight.Bold) } } } } } // Personal Information Header Card item { Card( colors = CardDefaults.cardColors(containerColor = DarkGreyGlass), modifier = Modifier.fillMaxWidth(), shape = RoundedCornerShape(14.dp) ) { Column(modifier = Modifier.padding(18.dp), verticalArrangement = Arrangement.spacedBy(12.dp)) { Text("Personal Information Parameters", style = MaterialTheme.typography.titleMedium, fontWeight = FontWeight.Bold, color = TextPrimary) TextField( value = rawName, onValueChange = { rawName = it }, modifier = Modifier.fillMaxWidth().testTag("info_name_field"), label = { Text("Display Full Name") }, colors = TextFieldDefaults.colors(focusedContainerColor = DarkGreyGlassElevated, unfocusedContainerColor = DarkGreyGlassElevated) ) TextField( value = rawEmail, onValueChange = { rawEmail = it }, modifier = Modifier.fillMaxWidth().testTag("info_email_field"), label = { Text("Verified Email Address") }, colors = TextFieldDefaults.colors(focusedContainerColor = DarkGreyGlassElevated, unfocusedContainerColor = DarkGreyGlassElevated) ) TextField( value = telephoneNum, onValueChange = { telephoneNum = it }, modifier = Modifier.fillMaxWidth().testTag("info_phone_field"), label = { Text("Registered Mobile Number") }, colors = TextFieldDefaults.colors(focusedContainerColor = DarkGreyGlassElevated, unfocusedContainerColor = DarkGreyGlassElevated) ) } } } // Change Password Card item { Card( colors = CardDefaults.cardColors(containerColor = DarkGreyGlass), modifier = Modifier.fillMaxWidth(), shape = RoundedCornerShape(14.dp) ) { Column(modifier = Modifier.padding(18.dp)) { Text("Security - Change Password Hash", style = MaterialTheme.typography.titleMedium, fontWeight = FontWeight.Bold, color = TextPrimary) Spacer(modifier = Modifier.height(12.dp)) TextField( value = customPass, onValueChange = { customPass = it }, modifier = Modifier.fillMaxWidth().testTag("info_password_field"), label = { Text("Input New Secure Passcode Key") }, colors = TextFieldDefaults.colors(focusedContainerColor = DarkGreyGlassElevated, unfocusedContainerColor = DarkGreyGlassElevated) ) } } } // Notification Settings toggles item { Card( colors = CardDefaults.cardColors(containerColor = DarkGreyGlass), modifier = Modifier.fillMaxWidth(), shape = RoundedCornerShape(14.dp) ) { Column(modifier = Modifier.padding(18.dp), verticalArrangement = Arrangement.spacedBy(10.dp)) { Text("Notification alert preferences", style = MaterialTheme.typography.titleMedium, fontWeight = FontWeight.Bold, color = TextPrimary) Row( modifier = Modifier.fillMaxWidth(), horizontalArrangement = Arrangement.SpaceBetween, verticalAlignment = Alignment.CenterVertically ) { Column { Text("Email notification channels", style = MaterialTheme.typography.bodyMedium, color = TextPrimary, fontWeight = FontWeight.Bold) Text("Safehouse approvals sent via Email.", style = MaterialTheme.typography.bodySmall, color = TextSecondary) } Switch(checked = emailNotifyToggle, onCheckedChange = { emailNotifyToggle = it }, colors = SwitchDefaults.colors(checkedThumbColor = BrandGold, checkedTrackColor = LightGold)) } Row( modifier = Modifier.fillMaxWidth(), horizontalArrangement = Arrangement.SpaceBetween, verticalAlignment = Alignment.CenterVertically ) { Column { Text("SMS text alert dispatches", style = MaterialTheme.typography.bodyMedium, color = TextPrimary, fontWeight = FontWeight.Bold) Text("Instant updates on companion travel.", style = MaterialTheme.typography.bodySmall, color = TextSecondary) } Switch(checked = smsNotifyToggle, onCheckedChange = { smsNotifyToggle = it }, colors = SwitchDefaults.colors(checkedThumbColor = BrandGold, checkedTrackColor = LightGold)) } Row( modifier = Modifier.fillMaxWidth(), horizontalArrangement = Arrangement.SpaceBetween, verticalAlignment = Alignment.CenterVertically ) { Column { Text("Direct Push system notification", style = MaterialTheme.typography.bodyMedium, color = TextPrimary, fontWeight = FontWeight.Bold) Text("Show instant alerts on home screens.", style = MaterialTheme.typography.bodySmall, color = TextSecondary) } Switch(checked = pushNotifyToggle, onCheckedChange = { pushNotifyToggle = it }, colors = SwitchDefaults.colors(checkedThumbColor = BrandGold, checkedTrackColor = LightGold)) } } } } // Privacy toggles item { Card( colors = CardDefaults.cardColors(containerColor = DarkGreyGlass), modifier = Modifier.fillMaxWidth(), shape = RoundedCornerShape(14.dp) ) { Column(modifier = Modifier.padding(18.dp), verticalArrangement = Arrangement.spacedBy(10.dp)) { Text("Privacy & Anonymity Parameters", style = MaterialTheme.typography.titleMedium, fontWeight = FontWeight.Bold, color = TextPrimary) Row( modifier = Modifier.fillMaxWidth(), horizontalArrangement = Arrangement.SpaceBetween, verticalAlignment = Alignment.CenterVertically ) { Column { Text("Public Directory Recognition", style = MaterialTheme.typography.bodyMedium, color = TextPrimary, fontWeight = FontWeight.Bold) Text("Show active orders list in public agency ledger.", style = MaterialTheme.typography.bodySmall, color = TextSecondary) } Switch(checked = showPublicToggle, onCheckedChange = { showPublicToggle = it }, colors = SwitchDefaults.colors(checkedThumbColor = BrandGold, checkedTrackColor = LightGold)) } Row( modifier = Modifier.fillMaxWidth(), horizontalArrangement = Arrangement.SpaceBetween, verticalAlignment = Alignment.CenterVertically ) { Column { Text("Mask user identity (Anonymous Mode)", style = MaterialTheme.typography.bodyMedium, color = TextPrimary, fontWeight = FontWeight.Bold) Text("Replace your true indicators with simulated hashes on companion schedules.", style = MaterialTheme.typography.bodySmall, color = TextSecondary) } Switch(checked = keepAnonymousToggle, onCheckedChange = { keepAnonymousToggle = it }, colors = SwitchDefaults.colors(checkedThumbColor = BrandGold, checkedTrackColor = LightGold)) } } } } // Save Button item { Button( onClick = { saveConfirmationMessage = "Your configuration parameters and privacy switches have been securely committed." }, modifier = Modifier .fillMaxWidth() .testTag("save_settings_btn"), colors = ButtonDefaults.buttonColors(containerColor = BrandGold, contentColor = Color.White), shape = RoundedCornerShape(12.dp) ) { Text("Lock Settings", fontWeight = FontWeight.Bold) } } item { Spacer(modifier = Modifier.height(30.dp)) } } if (saveConfirmationMessage != null) { AlertDialog( onDismissRequest = { saveConfirmationMessage = null }, title = { Text("Profile Controller Alert", fontWeight = FontWeight.Bold) }, text = { Text(saveConfirmationMessage!!) }, confirmButton = { TextButton(onClick = { saveConfirmationMessage = null }) { Text("Acknowledge", color = BrandGold) } }, containerColor = DarkGreyGlassElevated, titleContentColor = TextPrimary, textContentColor = TextPrimary ) } } // ---------------------------------------------------- // DIALOG: PORTRAIT COMPANION DETAIL DIALOG // ---------------------------------------------------- // TAB 7: EXTRA COMPONENT MODULES // ---------------------------------------------------- @Composable fun ProfileDetailDialog( model: ModelProfile, userWallet: UserWallet, onDismiss: () -> Unit, onBookClick: () -> Unit ) { val isVip = userWallet.membershipStatus.contains("VIP", ignoreCase = true) || userWallet.membershipStatus.contains("Premium", ignoreCase = true) // Fallback display values val displayPhone = if (isVip) { if (model.phone.isNotEmpty()) model.phone else "+880 1711-223344" } else { "+880 1711-******" } val displayPlatform = if (isVip) { if (model.preferredPlatform.isNotEmpty()) model.preferredPlatform else "WhatsApp" } else { "Locked (VIP Only)" } Dialog(onDismissRequest = onDismiss) { Card( shape = RoundedCornerShape(24.dp), colors = CardDefaults.cardColors(containerColor = Obsidian), modifier = Modifier .fillMaxWidth() .padding(vertical = 12.dp), border = BorderStroke(1.dp, CyanAccent) ) { Column( modifier = Modifier .padding(20.dp) .verticalScroll(rememberScrollState()) ) { // Header with Profile & Gradient Ring Row( verticalAlignment = Alignment.CenterVertically, modifier = Modifier.fillMaxWidth() ) { Box( modifier = Modifier .size(70.dp) .clip(CircleShape) .background( Brush.linearGradient(colors = listOf(CyanAccent, BrandGold)) ), contentAlignment = Alignment.Center ) { Text( text = model.name.substring(0, 1).uppercase(), fontWeight = FontWeight.ExtraBold, color = Color.Black, fontSize = 28.sp ) } Spacer(modifier = Modifier.width(16.dp)) Column { Row(verticalAlignment = Alignment.CenterVertically) { Text( text = model.fullName.ifEmpty { model.name }, style = MaterialTheme.typography.titleLarge, color = TextPrimary, fontWeight = FontWeight.Bold ) Spacer(modifier = Modifier.width(6.dp)) if (model.isVerified) { Icon( imageVector = Icons.Default.Verified, contentDescription = "Verified Profile", tint = CyanAccent, modifier = Modifier.size(18.dp) ) } } Text( text = "@${model.username} • ${model.professionalStatus}", style = MaterialTheme.typography.bodySmall, color = TextSecondary ) Row( verticalAlignment = Alignment.CenterVertically, modifier = Modifier.padding(top = 2.dp) ) { Text("⭐", fontSize = 12.sp) Spacer(modifier = Modifier.width(4.dp)) Text( text = "${model.rating} Agency Mark • ${model.nationality}", style = MaterialTheme.typography.bodySmall, color = BrandGold, fontWeight = FontWeight.Bold ) } } } HorizontalDivider(color = DarkGreyGlassElevated, modifier = Modifier.padding(vertical = 14.dp)) // Short Bio / intro Text( text = "Professional Portfolio Biography", style = MaterialTheme.typography.labelSmall, color = CyanAccent, fontWeight = FontWeight.Bold ) Spacer(modifier = Modifier.height(4.dp)) Text( text = model.bio, style = MaterialTheme.typography.bodyMedium, color = TextPrimary, lineHeight = 18.sp ) HorizontalDivider(color = DarkGreyGlassElevated, modifier = Modifier.padding(vertical = 12.dp)) // Professional Stats (Area, Category, Agency) Row( modifier = Modifier.fillMaxWidth(), horizontalArrangement = Arrangement.spacedBy(8.dp) ) { Column(modifier = Modifier.weight(1f)) { Text("Area / District", style = MaterialTheme.typography.labelSmall, color = TextSecondary) Text( text = "${model.areaDistrict}, ${model.city}", style = MaterialTheme.typography.bodyMedium, color = TextPrimary, fontWeight = FontWeight.Bold ) } Column(modifier = Modifier.weight(1f)) { Text("Agency Hook", style = MaterialTheme.typography.labelSmall, color = TextSecondary) Text( text = model.agency.ifEmpty { "Independent" }, style = MaterialTheme.typography.bodyMedium, color = TextPrimary, fontWeight = FontWeight.Bold ) } } Spacer(modifier = Modifier.height(10.dp)) // Travel & Languages Spoken Row( modifier = Modifier.fillMaxWidth(), horizontalArrangement = Arrangement.spacedBy(8.dp) ) { Column(modifier = Modifier.weight(1.2f)) { Text("Languages spoken", style = MaterialTheme.typography.labelSmall, color = TextSecondary) Text( text = model.languagesSpoken.ifEmpty { "English, Bengali" }, style = MaterialTheme.typography.bodyMedium, color = TextPrimary, fontWeight = FontWeight.SemiBold ) } Column(modifier = Modifier.weight(1.0f)) { Text("Metropolitan Travel", style = MaterialTheme.typography.labelSmall, color = TextSecondary) Text( text = if (model.travelAvailable) "✓ Available" else "❌ Local Venue Only", style = MaterialTheme.typography.bodyMedium, color = if (model.travelAvailable) CyanAccent else CoralRed, fontWeight = FontWeight.Bold ) } } HorizontalDivider(color = DarkGreyGlassElevated, modifier = Modifier.padding(vertical = 12.dp)) // Services & Weekly schedule Text("Services Provided", style = MaterialTheme.typography.labelSmall, color = CyanAccent, fontWeight = FontWeight.Bold) Text( text = model.servicesProvided.ifEmpty { "Runway, Fashion, Commercial, Event Hosting" }, style = MaterialTheme.typography.bodySmall, color = TextPrimary, modifier = Modifier.padding(bottom = 6.dp) ) Text("Availability Schedule", style = MaterialTheme.typography.labelSmall, color = CyanAccent, fontWeight = FontWeight.Bold) Text( text = model.availabilitySchedule.ifEmpty { "Weekend and evening bookings" }, style = MaterialTheme.typography.bodySmall, color = TextPrimary ) HorizontalDivider(color = DarkGreyGlassElevated, modifier = Modifier.padding(vertical = 12.dp)) // Commercial Rate Tiers Section Text( text = "Commercial Escrow Rate Tiers", style = MaterialTheme.typography.labelSmall, color = BrandGold, fontWeight = FontWeight.Bold ) Spacer(modifier = Modifier.height(6.dp)) Card( colors = CardDefaults.cardColors(containerColor = DarkGreyGlassElevated), border = BorderStroke(1.dp, Color(0xFF27272A)), shape = RoundedCornerShape(12.dp), modifier = Modifier.fillMaxWidth() ) { Column(modifier = Modifier.padding(12.dp), verticalArrangement = Arrangement.spacedBy(6.dp)) { Row(horizontalArrangement = Arrangement.SpaceBetween, modifier = Modifier.fillMaxWidth()) { Text("1 Hour (Standard Rate)", style = MaterialTheme.typography.bodySmall, color = TextPrimary) Text(String.format(Locale.US, "৳%,.0f BDT", model.hourlyRate * 120.0), style = MaterialTheme.typography.bodySmall, color = BrandGold, fontWeight = FontWeight.Bold) } Row(horizontalArrangement = Arrangement.SpaceBetween, modifier = Modifier.fillMaxWidth()) { Text("2 Hours Session Pack", style = MaterialTheme.typography.bodySmall, color = TextPrimary) Text(String.format(Locale.US, "৳%,.0f BDT", model.price2Hours * 120.0), style = MaterialTheme.typography.bodySmall, color = BrandGold, fontWeight = FontWeight.Bold) } Row(horizontalArrangement = Arrangement.SpaceBetween, modifier = Modifier.fillMaxWidth()) { Text("Half Day Booking (4 Hrs)", style = MaterialTheme.typography.bodySmall, color = TextPrimary) Text(String.format(Locale.US, "৳%,.0f BDT", model.priceHalfDay * 120.0), style = MaterialTheme.typography.bodySmall, color = BrandGold, fontWeight = FontWeight.Bold) } Row(horizontalArrangement = Arrangement.SpaceBetween, modifier = Modifier.fillMaxWidth()) { Text("Full Day Booking (8 Hrs)", style = MaterialTheme.typography.bodySmall, color = TextPrimary) Text(String.format(Locale.US, "৳%,.0f BDT", model.priceFullDay * 120.0), style = MaterialTheme.typography.bodySmall, color = BrandGold, fontWeight = FontWeight.Bold) } Row(horizontalArrangement = Arrangement.SpaceBetween, modifier = Modifier.fillMaxWidth()) { Text("Custom Multi-day Price", style = MaterialTheme.typography.bodySmall, color = TextPrimary) Text(if (model.customPriceAvailable) "Allowed" else "Direct Packages Only", style = MaterialTheme.typography.bodySmall, color = if (model.customPriceAvailable) CyanAccent else TextSecondary, fontWeight = FontWeight.Bold) } } } HorizontalDivider(color = DarkGreyGlassElevated, modifier = Modifier.padding(vertical = 12.dp)) // Vetting Secure Privacy Contact Info Text( text = "Secure Communication Standard", style = MaterialTheme.typography.labelSmall, color = BrandGold, fontWeight = FontWeight.Bold ) Spacer(modifier = Modifier.height(6.dp)) Card( colors = CardDefaults.cardColors(containerColor = if (isVip) DarkGreyGlass else Color(0x22DC2626)), border = BorderStroke(1.dp, if (isVip) CyanAccent else Color(0xFFDC2626)), shape = RoundedCornerShape(12.dp), modifier = Modifier.fillMaxWidth() ) { Column(modifier = Modifier.padding(12.dp)) { Row(verticalAlignment = Alignment.CenterVertically) { Text("📱", fontSize = 16.sp) Spacer(modifier = Modifier.width(8.dp)) Column { Text("Handshake Platform: $displayPlatform", style = MaterialTheme.typography.bodySmall, color = TextPrimary, fontWeight = FontWeight.Bold) Text("Contact Number: $displayPhone", style = MaterialTheme.typography.bodySmall, color = TextPrimary, fontWeight = FontWeight.SemiBold) } } if (!isVip) { Spacer(modifier = Modifier.height(8.dp)) Text( text = "🔒 PRIVATE CONTACT DATA: Upgrade to VIP Membership / Premium Plan, or secure a model dispatch contract approval to release full phone platforms and start in-app chating.", style = MaterialTheme.typography.labelSmall, color = CoralRed, fontWeight = FontWeight.Bold, lineHeight = 13.sp ) } else { Spacer(modifier = Modifier.height(4.dp)) Text( text = "✓ Active VIP Pass: Contacts released safely for private schedule coordination.", style = MaterialTheme.typography.labelSmall, color = CyanAccent, fontWeight = FontWeight.Bold ) } } } Spacer(modifier = Modifier.height(20.dp)) // Bottom actions Row( horizontalArrangement = Arrangement.spacedBy(8.dp), modifier = Modifier.fillMaxWidth() ) { OutlinedButton( onClick = onDismiss, modifier = Modifier.weight(1f), colors = ButtonDefaults.outlinedButtonColors(contentColor = TextPrimary), border = BorderStroke(1.dp, Color(0xFF3F3F46)) ) { Text("Dismiss") } Button( onClick = onBookClick, colors = ButtonDefaults.buttonColors(containerColor = CyanAccent, contentColor = Color.Black), modifier = Modifier.weight(1f).testTag("detail_book_btn"), shape = RoundedCornerShape(12.dp) ) { Text("Draft Contract 🚀", fontWeight = FontWeight.ExtraBold) } } } } } } // ---------------------------------------------------- // SYSTEM WIZARD: COMPREHENSIVE 6-STEP BOOKING FLOW // ---------------------------------------------------- @Composable fun CreateBookingWizardDialog( model: ModelProfile, allModels: List, userWallet: UserWallet, onDismiss: () -> Unit, onConfirmBooking: ( serviceName: String, provider: ModelProfile, durationHours: Int, totalCost: Double, venue: String, date: String, time: String, timeZone: String ) -> Unit, onAddFundsClick: () -> Unit ) { // Current Step: 1 -> Service, 2 -> Provider, 3 -> Duration, 4 -> Location, 5 -> Date & Time, 6 -> Request Output var wizardStep by remember { mutableStateOf(1) } // Step 1: Services Choices val availableServices = listOf( Pair("Photoshoot", "Professional photography session for editorial, commercial, or portfolio work."), Pair("Runway / Fashion Show", "Runway modeling for fashion shows and live presentations."), Pair("Brand Campaign", "Featured talent for advertising and brand marketing campaigns."), Pair("Video / Commercial Shoot", "On-camera talent for video, commercials, and motion content."), Pair("Event Appearance", "Promotional and hosting appearances at events and launches."), Pair("Fitting / Showroom", "Garment fittings and showroom presentations.") ) var selectedService by remember { mutableStateOf(availableServices[0].first) } // Step 2: Provider Details & Alternative choice var activeProvider by remember { mutableStateOf(model) } var providerSearchInput by remember { mutableStateOf("") } // Step 3: Duration Choices with Bangladesh local pricing val durationOptions = listOf( Pair("1 Hour", 500.0), Pair("2 Hours", 1000.0), Pair("Half Day", 5000.0), Pair("Full Day", 10000.0), Pair("Night Session", 15000.0), Pair("Custom Hourly", -1.0) // slider triggers standard custom modeling hourly multiplier of 500 ) var selectedDurationIndex by remember { mutableStateOf(0) } var customHoursSliderVal by remember { mutableStateOf(5) } // default 5 hours on slider if Custom Hourly is used // Calculated Dynamic Core Cost val finalCalculatedCost = remember(selectedDurationIndex, customHoursSliderVal) { val selectedOpt = durationOptions[selectedDurationIndex] if (selectedOpt.second >= 0.0) { selectedOpt.second } else { customHoursSliderVal * 500.0 // Custom Hourly calculated at ৳500 per hr } } val finalCalculatedHoursValue = remember(selectedDurationIndex, customHoursSliderVal) { when (selectedDurationIndex) { 0 -> 1 1 -> 2 2 -> 4 3 -> 8 4 -> 12 else -> customHoursSliderVal } } // Step 4: Locations val standardLocationsDef = listOf("Hotel Sheraton", "Premium Central Studio", "Luxury Arena Hall", "Safehouse Sanctuary") var selectedLocationTag by remember { mutableStateOf(standardLocationsDef[0]) } var customAddressInput by remember { mutableStateOf("") } val finalResolvedLocation = if (selectedLocationTag == "Custom Address Input") customAddressInput else selectedLocationTag // Step 5: Date, Time & Timezones var inputDateText by remember { mutableStateOf("June 25, 2026") } val standardTimePills = listOf("10:00 AM", "02:00 PM", "06:00 PM", "10:00 PM") var selectedTimeSlotIndex by remember { mutableStateOf(1) } val timeZoneOptions = listOf("GMT+6 (BDT - Bangladesh Standard Time)", "UTC (Universal Coordinated Time)", "GMT-5 (EST - Eastern Standard Time)") var selectedTimeZoneIndex by remember { mutableStateOf(0) } Dialog(onDismissRequest = onDismiss) { Card( shape = RoundedCornerShape(20.dp), colors = CardDefaults.cardColors(containerColor = DarkGreyGlassElevated), modifier = Modifier .fillMaxWidth() .padding(vertical = 12.dp) ) { Column(modifier = Modifier.padding(20.dp)) { if (userWallet.verificationStatus != "verified") { Column( horizontalAlignment = Alignment.CenterHorizontally, verticalArrangement = Arrangement.spacedBy(16.dp), modifier = Modifier.padding(8.dp).fillMaxWidth() ) { Box( modifier = Modifier .size(64.dp) .clip(CircleShape) .background(CoralRed.copy(0.12f)), contentAlignment = Alignment.Center ) { Icon(Icons.Default.Security, "security", tint = CoralRed, modifier = Modifier.size(32.dp)) } Text( "Account Verification Required", style = MaterialTheme.typography.titleLarge, fontWeight = FontWeight.Bold, color = TextPrimary, textAlign = TextAlign.Center ) Text( "To ensure safety, payments protection, and anti-fraud security, unverified user accounts are strictly restricted from booking model partners.\n\nPlease navigate to your 'Settings' Tab and verify:\n• Email Address\n• Mobile Number (OTP)\n• NID / Passport Document (KYC)", style = MaterialTheme.typography.bodyMedium, color = TextSecondary, textAlign = TextAlign.Center ) Spacer(modifier = Modifier.height(12.dp)) Button( onClick = onDismiss, colors = ButtonDefaults.buttonColors(containerColor = BrandGold, contentColor = Color.Black), modifier = Modifier.fillMaxWidth().height(48.dp).testTag("wizard_close_unverified") ) { Text("Acknowledge & Close", fontWeight = FontWeight.Bold) } } } else if (activeProvider.verificationStatus != "verified") { Column( horizontalAlignment = Alignment.CenterHorizontally, verticalArrangement = Arrangement.spacedBy(16.dp), modifier = Modifier.padding(8.dp).fillMaxWidth() ) { Box( modifier = Modifier .size(64.dp) .clip(CircleShape) .background(CoralRed.copy(0.12f)), contentAlignment = Alignment.Center ) { Icon(Icons.Default.NoAccounts, "unverified model", tint = CoralRed, modifier = Modifier.size(32.dp)) } Text( "Model Verification Required", style = MaterialTheme.typography.titleLarge, fontWeight = FontWeight.Bold, color = TextPrimary, textAlign = TextAlign.Center ) Text( "The selected talent profile (${activeProvider.name}) is currently unverified.\n\nUnder marketplace trust rules, unverified models/talents cannot receive client bookings or escrow secure releases. Please navigate to verified members.", style = MaterialTheme.typography.bodyMedium, color = TextSecondary, textAlign = TextAlign.Center ) Spacer(modifier = Modifier.height(12.dp)) Button( onClick = onDismiss, colors = ButtonDefaults.buttonColors(containerColor = BrandGold, contentColor = Color.Black), modifier = Modifier.fillMaxWidth().height(48.dp).testTag("wizard_close_model_unverified") ) { Text("Return to Models List", fontWeight = FontWeight.Bold) } } } else { // Header Step Tracker Row( modifier = Modifier.fillMaxWidth(), horizontalArrangement = Arrangement.SpaceBetween, verticalAlignment = Alignment.CenterVertically ) { Text( "Step $wizardStep of 6", style = MaterialTheme.typography.labelSmall, color = BrandGold, fontWeight = FontWeight.ExtraBold ) Text( text = when (wizardStep) { 1 -> "SERVICE SELECTION" 2 -> "SELECT ENVOLVED PROVIDER" 3 -> "DURATION & PRICING RULES" 4 -> "LOCK SECURE LOCATION" 5 -> "SCHEDULE DATE & TIME" else -> "SUMMARIZE & REQUEST" }, style = MaterialTheme.typography.labelSmall, fontWeight = FontWeight.SemiBold, color = TextSecondary ) } Spacer(modifier = Modifier.height(4.dp)) // Inline visual progress indicator bar LinearProgressIndicator( progress = wizardStep.toFloat() / 6f, modifier = Modifier .fillMaxWidth() .height(6.dp) .clip(RoundedCornerShape(3.dp)), color = BrandGold, trackColor = DarkGreyGlass ) Spacer(modifier = Modifier.height(16.dp)) // Content Panel matching Steps Box( modifier = Modifier .weight(1f, fill = false) .fillMaxWidth() ) { when (wizardStep) { 1 -> Column(verticalArrangement = Arrangement.spacedBy(8.dp)) { Text("Step 1 – What service is requested?", style = MaterialTheme.typography.titleMedium, fontWeight = FontWeight.Bold, color = TextPrimary) Text("Select an operation type with companion and safehouse parameters.", style = MaterialTheme.typography.bodySmall, color = TextSecondary) Spacer(modifier = Modifier.height(6.dp)) LazyColumn(verticalArrangement = Arrangement.spacedBy(8.dp), modifier = Modifier.heightIn(max = 280.dp)) { items(availableServices) { item -> val isSelected = selectedService == item.first Card( modifier = Modifier .fillMaxWidth() .clickable { selectedService = item.first } .testTag("wizard_service_${item.first.replace(" ", "_")}"), colors = CardDefaults.cardColors( containerColor = if (isSelected) LightGold else DarkGreyGlass ), border = BorderStroke(1.dp, if (isSelected) BrandGold else DarkGreyGlassElevated) ) { Column(modifier = Modifier.padding(12.dp)) { Row(verticalAlignment = Alignment.CenterVertically) { RadioButton( selected = isSelected, onClick = { selectedService = item.first }, colors = RadioButtonDefaults.colors(selectedColor = BrandGold) ) Text(item.first, style = MaterialTheme.typography.bodyMedium, fontWeight = FontWeight.Bold, color = if (isSelected) BrandGold else TextPrimary) } Text(item.second, style = MaterialTheme.typography.labelSmall, color = TextSecondary, modifier = Modifier.padding(start = 48.dp)) } } } } } 2 -> Column(verticalArrangement = Arrangement.spacedBy(8.dp)) { Text("Step 2 – Confirm / Swap Provider", style = MaterialTheme.typography.titleMedium, fontWeight = FontWeight.Bold, color = TextPrimary) Text("Click to confirm current choice or pick alternative directory model.", style = MaterialTheme.typography.bodySmall, color = TextSecondary) Spacer(modifier = Modifier.height(6.dp)) // Current Active Provider card Card( modifier = Modifier.fillMaxWidth(), colors = CardDefaults.cardColors(containerColor = LightGold), border = BorderStroke(1.dp, BrandGold) ) { Row(modifier = Modifier.padding(12.dp), verticalAlignment = Alignment.CenterVertically) { Box( modifier = Modifier.size(40.dp).clip(CircleShape).background(BrandGold), contentAlignment = Alignment.Center ) { Text(activeProvider.name.first().toString(), color = Color.White, fontWeight = FontWeight.Bold) } Spacer(modifier = Modifier.width(10.dp)) Column(modifier = Modifier.weight(1f)) { Text(activeProvider.name, fontWeight = FontWeight.Bold, style = MaterialTheme.typography.bodyMedium, color = TextPrimary) Text("${activeProvider.location} • ⭐ ${activeProvider.rating}", style = MaterialTheme.typography.labelSmall, color = TextSecondary) } Surface(color = BrandGold, shape = RoundedCornerShape(4.dp)) { Text("SELECTED", modifier = Modifier.padding(horizontal = 6.dp, vertical = 2.dp), color = Color.White, fontSize = 9.sp, fontWeight = FontWeight.Bold) } } } Spacer(modifier = Modifier.height(8.dp)) Text("Search Alternative Candidates:", style = MaterialTheme.typography.labelSmall, color = TextSecondary, fontWeight = FontWeight.Bold) TextField( value = providerSearchInput, onValueChange = { providerSearchInput = it }, modifier = Modifier.fillMaxWidth().height(48.dp), placeholder = { Text("Search by name...", fontSize = 12.sp) }, colors = TextFieldDefaults.colors(focusedContainerColor = DarkGreyGlass, unfocusedContainerColor = DarkGreyGlass) ) // Alternative scroll grid val filteredAlts = allModels.filter { it.name.contains(providerSearchInput, ignoreCase = true) && it.id != activeProvider.id } LazyColumn(modifier = Modifier.heightIn(max = 140.dp), verticalArrangement = Arrangement.spacedBy(6.dp)) { items(filteredAlts) { alt -> Card( modifier = Modifier .fillMaxWidth() .clickable { activeProvider = alt } .testTag("wizard_select_provider_${alt.id}"), colors = CardDefaults.cardColors(containerColor = DarkGreyGlass) ) { Row(modifier = Modifier.padding(8.dp), verticalAlignment = Alignment.CenterVertically) { Text(alt.name, fontWeight = FontWeight.Bold, style = MaterialTheme.typography.bodySmall, color = TextPrimary, modifier = Modifier.weight(1f)) Text(String.format(Locale.US, "৳%,.0f/hr", alt.hourlyRate), color = BrandGold, style = MaterialTheme.typography.labelSmall, fontWeight = FontWeight.Bold) Spacer(modifier = Modifier.width(8.dp)) Text("Replace", color = CyanAccent, style = MaterialTheme.typography.labelSmall, fontWeight = FontWeight.ExtraBold) } } } } } 3 -> Column(verticalArrangement = Arrangement.spacedBy(8.dp)) { Text("Step 3 – Duration & Static Model Pricing", style = MaterialTheme.typography.titleMedium, fontWeight = FontWeight.Bold, color = TextPrimary) Text("Pricing structures: ৳500/hr, Full Day/৳10,000, Night/৳15,000.", style = MaterialTheme.typography.bodySmall, color = TextSecondary) Spacer(modifier = Modifier.height(6.dp)) Column(verticalArrangement = Arrangement.spacedBy(6.dp)) { durationOptions.forEachIndexed { index, opt -> val isSelected = selectedDurationIndex == index Card( modifier = Modifier .fillMaxWidth() .clickable { selectedDurationIndex = index } .testTag("wizard_duration_opt_$index"), colors = CardDefaults.cardColors(containerColor = if (isSelected) LightGold else DarkGreyGlass), border = BorderStroke(1.dp, if (isSelected) BrandGold else DarkGreyGlassElevated) ) { Row(modifier = Modifier.padding(12.dp), horizontalArrangement = Arrangement.SpaceBetween, verticalAlignment = Alignment.CenterVertically) { Text(opt.first, fontWeight = FontWeight.Bold, style = MaterialTheme.typography.bodyMedium, color = if (isSelected) BrandGold else TextPrimary) Text( if (opt.second >= 0.0) String.format(Locale.US, "৳%,.0f BDT", opt.second) else "৳500 / Hr slider", color = if (isSelected) BrandGold else TextSecondary, style = MaterialTheme.typography.bodyMedium, fontWeight = FontWeight.Bold ) } } } } if (durationOptions[selectedDurationIndex].second < 0.0) { Spacer(modifier = Modifier.height(8.dp)) Text("Slide to specify custom hours: $customHoursSliderVal hrs", style = MaterialTheme.typography.labelSmall, color = BrandGold, fontWeight = FontWeight.Bold) Slider( value = customHoursSliderVal.toFloat(), onValueChange = { customHoursSliderVal = it.toInt() }, valueRange = 1f..24f, steps = 22, colors = SliderDefaults.colors(thumbColor = BrandGold, activeTrackColor = BrandGold) ) } Spacer(modifier = Modifier.height(6.dp)) HorizontalDivider() Row(modifier = Modifier.fillMaxWidth(), horizontalArrangement = Arrangement.SpaceBetween) { Text("Calculated total BDT cost:", color = TextSecondary, style = MaterialTheme.typography.bodySmall) Text(String.format(Locale.US, "৳%,.0f BDT", finalCalculatedCost), color = BrandGold, fontWeight = FontWeight.Black) } } 4 -> Column(verticalArrangement = Arrangement.spacedBy(8.dp)) { Text("Step 4 – Lock Security venue", style = MaterialTheme.typography.titleMedium, fontWeight = FontWeight.Bold, color = TextPrimary) Text("Choose safehouse area or input custom address indicator.", style = MaterialTheme.typography.bodySmall, color = TextSecondary) Spacer(modifier = Modifier.height(6.dp)) LazyColumn(verticalArrangement = Arrangement.spacedBy(6.dp), modifier = Modifier.heightIn(max = 180.dp)) { items(standardLocationsDef) { venue -> val isSelected = selectedLocationTag == venue Card( modifier = Modifier .fillMaxWidth() .clickable { selectedLocationTag = venue } .testTag("wizard_venue_$venue"), colors = CardDefaults.cardColors(containerColor = if (isSelected) LightGold else DarkGreyGlass) ) { Row(modifier = Modifier.padding(10.dp), verticalAlignment = Alignment.CenterVertically) { RadioButton(selected = isSelected, onClick = { selectedLocationTag = venue }, colors = RadioButtonDefaults.colors(selectedColor = BrandGold)) Text(venue, style = MaterialTheme.typography.bodyMedium, fontWeight = FontWeight.Bold, color = TextPrimary) } } } } Row(verticalAlignment = Alignment.CenterVertically) { RadioButton(selected = selectedLocationTag == "Custom Address Input", onClick = { selectedLocationTag = "Custom Address Input" }) Text("Specify Custom Address Below", style = MaterialTheme.typography.bodyMedium, fontWeight = FontWeight.Bold) } if (selectedLocationTag == "Custom Address Input") { TextField( value = customAddressInput, onValueChange = { customAddressInput = it }, modifier = Modifier.fillMaxWidth().testTag("wizard_custom_location_input"), placeholder = { Text("e.g. Road 12, Banani, Dhaka") }, colors = TextFieldDefaults.colors(focusedContainerColor = DarkGreyGlass, unfocusedContainerColor = DarkGreyGlass) ) } } 5 -> Column(verticalArrangement = Arrangement.spacedBy(8.dp)) { Text("Step 5 – Select Schedule Date & Time", style = MaterialTheme.typography.titleMedium, fontWeight = FontWeight.Bold, color = TextPrimary) Text("Specify date and select time with localized Time Zone support.", style = MaterialTheme.typography.bodySmall, color = TextSecondary) Spacer(modifier = Modifier.height(6.dp)) Text("Reservation Date:", style = MaterialTheme.typography.labelSmall, color = TextSecondary, fontWeight = FontWeight.Bold) TextField( value = inputDateText, onValueChange = { inputDateText = it }, modifier = Modifier.fillMaxWidth().testTag("wizard_date_field_input"), colors = TextFieldDefaults.colors(focusedContainerColor = DarkGreyGlass, unfocusedContainerColor = DarkGreyGlass) ) Spacer(modifier = Modifier.height(6.dp)) Text("Reservation Target Time Slot:", style = MaterialTheme.typography.labelSmall, color = TextSecondary, fontWeight = FontWeight.Bold) Row(horizontalArrangement = Arrangement.spacedBy(4.dp), modifier = Modifier.fillMaxWidth()) { standardTimePills.forEachIndexed { i, pill -> val isSelected = selectedTimeSlotIndex == i Surface( modifier = Modifier .weight(1f) .clickable { selectedTimeSlotIndex = i } .testTag("wizard_time_pill_$i"), shape = RoundedCornerShape(8.dp), color = if (isSelected) BrandGold else DarkGreyGlass ) { Text(pill, color = if (isSelected) Color.White else TextPrimary, style = MaterialTheme.typography.labelSmall, modifier = Modifier.padding(vertical = 10.dp), textAlign = TextAlign.Center) } } } Spacer(modifier = Modifier.height(10.dp)) Text("Select Time Zone region:", style = MaterialTheme.typography.labelSmall, color = TextSecondary, fontWeight = FontWeight.Bold) Column(verticalArrangement = Arrangement.spacedBy(4.dp)) { timeZoneOptions.forEachIndexed { i, zone -> val isSelected = selectedTimeZoneIndex == i Card( modifier = Modifier .fillMaxWidth() .clickable { selectedTimeZoneIndex = i } .testTag("wizard_timezone_$i"), colors = CardDefaults.cardColors(containerColor = if (isSelected) LightGold else DarkGreyGlass) ) { Row(modifier = Modifier.padding(8.dp), verticalAlignment = Alignment.CenterVertically) { RadioButton(selected = isSelected, onClick = { selectedTimeZoneIndex = i }) Text(zone, style = MaterialTheme.typography.labelSmall, fontWeight = FontWeight.Bold, color = TextPrimary) } } } } } else -> Column(verticalArrangement = Arrangement.spacedBy(8.dp)) { Text("Step 6 – Review Booking Summary Output", style = MaterialTheme.typography.titleMedium, fontWeight = FontWeight.Bold, color = TextPrimary) Text("Please inspect criteria limits and execute price lockers.", style = MaterialTheme.typography.bodySmall, color = TextSecondary) Spacer(modifier = Modifier.height(6.dp)) // Itemized summary box Card( modifier = Modifier.fillMaxWidth(), colors = CardDefaults.cardColors(containerColor = DarkGreyGlass) ) { Column(modifier = Modifier.padding(14.dp), verticalArrangement = Arrangement.spacedBy(4.dp)) { SummaryLineItem(label = "Selected Service", valStr = selectedService) SummaryLineItem(label = "Model Provider", valStr = activeProvider.name) SummaryLineItem(label = "Locked Location", valStr = finalResolvedLocation) SummaryLineItem(label = "Target Scheduled Date", valStr = inputDateText) SummaryLineItem(label = "Scheduled Time Slot", valStr = standardTimePills[selectedTimeSlotIndex]) SummaryLineItem(label = "Selected Time Zone", valStr = timeZoneOptions[selectedTimeZoneIndex].substringBefore(" ")) SummaryLineItem(label = "Required hours limit", valStr = "$finalCalculatedHoursValue Hours") Spacer(modifier = Modifier.height(10.dp)) HorizontalDivider() Spacer(modifier = Modifier.height(6.dp)) Row(modifier = Modifier.fillMaxWidth(), horizontalArrangement = Arrangement.SpaceBetween) { Text("Contract Lock Pricing:", fontWeight = FontWeight.Bold, style = MaterialTheme.typography.bodyMedium, color = TextPrimary) Text(String.format(Locale.US, "৳%,.0f BDT", finalCalculatedCost), color = BrandGold, fontWeight = FontWeight.ExtraBold) } } } Spacer(modifier = Modifier.height(10.dp)) // Wallet sufficiency check status indicators val hasBalance = userWallet.availableBalance >= finalCalculatedCost Card( modifier = Modifier.fillMaxWidth(), colors = CardDefaults.cardColors( containerColor = if (hasBalance) Color.Green.copy(alpha = 0.08f) else CoralRed.copy(alpha = 0.08f) ), border = BorderStroke(1.dp, if (hasBalance) Color.Green.copy(alpha = 0.4f) else CoralRed.copy(alpha = 0.4f)) ) { Row(modifier = Modifier.padding(12.dp), verticalAlignment = Alignment.CenterVertically) { Icon( if (hasBalance) Icons.Default.CheckCircle else Icons.Default.Warning, "status", tint = if (hasBalance) Color.Green else CoralRed ) Spacer(modifier = Modifier.width(10.dp)) Column { Text( if (hasBalance) "Funds Verified in Escrow Wallet" else "Escrow Balance Inadequate!", fontWeight = FontWeight.Bold, style = MaterialTheme.typography.bodySmall, color = if (hasBalance) Color.Green else CoralRed ) Text( if (hasBalance) "Your Available: ৳${String.format(Locale.US, "%,.0f", userWallet.availableBalance)} BDT ≥ ৳${String.format(Locale.US, "%,.0f", finalCalculatedCost)}" else "Available is ৳${String.format(Locale.US, "%,.0f", userWallet.availableBalance)} BDT • Deficit: ৳${String.format(Locale.US, "%,.0f", finalCalculatedCost - userWallet.availableBalance)} BDT", style = MaterialTheme.typography.labelSmall, color = TextSecondary ) } } } } } } Spacer(modifier = Modifier.height(24.dp)) // Bottom Step Controls Row Row( horizontalArrangement = Arrangement.spacedBy(8.dp), modifier = Modifier.fillMaxWidth() ) { if (wizardStep > 1) { OutlinedButton( onClick = { wizardStep-- }, modifier = Modifier.weight(1f).testTag("wizard_back_btn") ) { Text("Back") } } else { OutlinedButton( onClick = onDismiss, modifier = Modifier.weight(1f).testTag("wizard_cancel_btn") ) { Text("Abort") } } if (wizardStep < 6) { Button( onClick = { wizardStep++ }, colors = ButtonDefaults.buttonColors(containerColor = BrandGold, contentColor = Color.White), modifier = Modifier.weight(1f).testTag("wizard_next_btn") ) { Text("Next Step") } } else { val fundsOk = userWallet.availableBalance >= finalCalculatedCost if (fundsOk) { Button( onClick = { onConfirmBooking( selectedService, activeProvider, finalCalculatedHoursValue, finalCalculatedCost, finalResolvedLocation, inputDateText, standardTimePills[selectedTimeSlotIndex], timeZoneOptions[selectedTimeZoneIndex].substringBefore(" ") ) }, colors = ButtonDefaults.buttonColors(containerColor = Color.Green, contentColor = Color.White), modifier = Modifier.weight(1.2f).testTag("booking_wizard_submit") ) { Text("Commit Locker", fontWeight = FontWeight.Bold) } } else { Button( onClick = onAddFundsClick, colors = ButtonDefaults.buttonColors(containerColor = BrandGold, contentColor = Color.White), modifier = Modifier.weight(1.2f).testTag("booking_wizard_add_funds") ) { Text("Add Funds", fontWeight = FontWeight.Bold) } } } } } // This closes the 'else' block for verified checks } } } } @Composable fun SummaryLineItem(label: String, valStr: String) { Row( modifier = Modifier.fillMaxWidth(), horizontalArrangement = Arrangement.SpaceBetween, verticalAlignment = Alignment.CenterVertically ) { Text(label, color = TextSecondary, style = MaterialTheme.typography.labelSmall) Text(valStr, color = TextPrimary, fontWeight = FontWeight.Bold, style = MaterialTheme.typography.labelSmall) } } @Composable fun UserAgentHubTab( viewModel: PortalViewModel, userWallet: UserWallet ) { val cashRequests = viewModel.cashDepositRequests.collectAsState(initial = emptyList()).value val myPendingRequests = cashRequests.filter { it.agentId == userWallet.userId && it.status == "PENDING" } val myCompletedRequests = cashRequests.filter { it.agentId == userWallet.userId && it.status == "CONFIRMED" } LazyColumn( modifier = Modifier .fillMaxSize() .padding(16.dp), verticalArrangement = Arrangement.spacedBy(16.dp) ) { // Hub Header item { Text( text = "⚡ CASH AGENT DEPOT HUB", style = MaterialTheme.typography.headlineMedium, fontWeight = FontWeight.ExtraBold, color = CyanAccent, modifier = Modifier.testTag("agent_hub_title") ) Text( text = "Collect physical cash deposits from users and unlock digital system tokens securely.", style = MaterialTheme.typography.bodyMedium, color = TextSecondary ) } // Agent Balance Panel Info item { Card( modifier = Modifier.fillMaxWidth(), colors = CardDefaults.cardColors(containerColor = DarkGreyGlassElevated), border = BorderStroke(1.dp, BrandGold.copy(0.3f)) ) { Column(modifier = Modifier.padding(16.dp), verticalArrangement = Arrangement.spacedBy(12.dp)) { Text("DEPOT LIQUIDITY LEDGER", style = MaterialTheme.typography.labelSmall, color = BrandGold, fontWeight = FontWeight.Bold) Row( modifier = Modifier.fillMaxWidth(), horizontalArrangement = Arrangement.SpaceBetween ) { Column { Text("Funded Escrow balance", style = MaterialTheme.typography.labelSmall, color = TextSecondary) Text(String.format(Locale.US, "৳%,.2f BDT", userWallet.agentBalance), color = BrandGold, fontWeight = FontWeight.Black, style = MaterialTheme.typography.titleLarge) } Column { Text("Total Cash Collected", style = MaterialTheme.typography.labelSmall, color = TextSecondary) Text(String.format(Locale.US, "৳%,.2f BDT", userWallet.agentTotalCashCollected), color = TextPrimary, fontWeight = FontWeight.Bold, style = MaterialTheme.typography.titleMedium) } } HorizontalDivider(color = TextSecondary.copy(0.12f)) Row( modifier = Modifier.fillMaxWidth(), horizontalArrangement = Arrangement.SpaceBetween, verticalAlignment = Alignment.CenterVertically ) { Column { Text("Commission Earnings", style = MaterialTheme.typography.labelSmall, color = TextSecondary) Text(String.format(Locale.US, "৳%,.2f BDT", userWallet.agentCommissionEarned), color = Color.Green, fontWeight = FontWeight.Bold, style = MaterialTheme.typography.titleMedium) } Surface(color = Color.Green.copy(0.12f), shape = RoundedCornerShape(4.dp)) { Text("5% LIVE FEE RATE", color = Color.Green, fontSize = 9.sp, fontWeight = FontWeight.ExtraBold, modifier = Modifier.padding(horizontal = 6.dp, vertical = 2.dp)) } } } } } // Pending cash collection requests item { Text( text = "Awaiting Physical Cash Receipts (${myPendingRequests.size})", style = MaterialTheme.typography.titleMedium, fontWeight = FontWeight.Bold, color = TextPrimary ) } if (myPendingRequests.isEmpty()) { item { Card( modifier = Modifier.fillMaxWidth(), colors = CardDefaults.cardColors(containerColor = DarkGreyGlass) ) { Text( text = "No pending cash-in receipts. Clients in your area can trigger requests selecting your depot node.", style = MaterialTheme.typography.bodySmall, color = TextSecondary, modifier = Modifier.padding(16.dp), textAlign = TextAlign.Center ) } } } else { items(myPendingRequests) { req -> Card( modifier = Modifier .fillMaxWidth() .testTag("cash_collect_req_${req.id}"), colors = CardDefaults.cardColors(containerColor = DarkGreyGlassElevated) ) { Column(modifier = Modifier.padding(14.dp), verticalArrangement = Arrangement.spacedBy(8.dp)) { Row( modifier = Modifier.fillMaxWidth(), horizontalArrangement = Arrangement.SpaceBetween, verticalAlignment = Alignment.CenterVertically ) { Column { Text("Client Account ID: ${req.userId}", style = MaterialTheme.typography.bodySmall, color = TextPrimary, fontWeight = FontWeight.Bold) val dateStr = SimpleDateFormat("yyyy-MM-dd HH:mm", Locale.US).format(java.util.Date(req.timestamp)) Text("Timestamp: $dateStr", style = MaterialTheme.typography.labelSmall, color = TextSecondary) } Text( text = String.format(Locale.US, "৳%,.2f BDT", req.amount), color = BrandGold, fontWeight = FontWeight.Black, style = MaterialTheme.typography.titleMedium ) } Text("Action Required: Direct physical cash collection. Verify banknotes correspond to total, then push authorization below to unlock system values.", style = MaterialTheme.typography.bodySmall, color = TextSecondary) Button( onClick = { viewModel.confirmCashCollection(req.id) { _, _ -> } }, colors = ButtonDefaults.buttonColors(containerColor = Color.Green, contentColor = Color.White), modifier = Modifier.fillMaxWidth().testTag("confirm_cash_received_btn_${req.id}"), shape = RoundedCornerShape(8.dp) ) { Text("Confirm Cash Received (৳${req.amount} BDT)", fontWeight = FontWeight.Bold) } } } } } // Completed historical cash ledger item { Text( text = "Historic Depot Transactions (${myCompletedRequests.size})", style = MaterialTheme.typography.titleMedium, fontWeight = FontWeight.Bold, color = TextSecondary ) } if (myCompletedRequests.isEmpty()) { item { Text("No historical transactions compiled yet.", color = TextSecondary, style = MaterialTheme.typography.bodySmall) } } else { items(myCompletedRequests) { req -> Card( modifier = Modifier.fillMaxWidth(), colors = CardDefaults.cardColors(containerColor = DarkGreyGlass), shape = RoundedCornerShape(8.dp) ) { Row( modifier = Modifier.padding(12.dp), horizontalArrangement = Arrangement.SpaceBetween, verticalAlignment = Alignment.CenterVertically ) { Column { Text("Collected from ${req.userId}", style = MaterialTheme.typography.bodySmall, color = TextPrimary) Text("Status: ESCROW CREDITED", style = MaterialTheme.typography.labelSmall, color = Color.Green, fontWeight = FontWeight.Bold) } Text( text = String.format(Locale.US, "+৳%,.0f BDT", req.amount), color = Color.Green, fontWeight = FontWeight.Bold, style = MaterialTheme.typography.bodyMedium ) } } } } item { Spacer(modifier = Modifier.height(40.dp)) } } } // ============================================================================ // STATE-OF-THE-ART ACTIVE AGENCY HELPLINE CHAT SUPPORT // ============================================================================ @Composable fun ContactHelplineTab( viewModel: com.example.ui.PortalViewModel, senderId: String, senderName: String ) { var txtMessage by remember { mutableStateOf("") } val scope = rememberCoroutineScope() val listState = rememberLazyListState() // Retrieve global database & repository references via ViewModel safely val helplineLogs by viewModel.firebaseLogs.collectAsState(initial = emptyList()) // Maintain a local mutable state of helpline conversation for ultra-responsive performance var localMessages by remember { mutableStateOf( listOf( com.example.data.LiveChatEntity( id = 1, streamId = 999, senderId = "operator", senderName = "Helpline Support", senderType = "SYSTEM", message = "Welcome to our live 24/7 Agency Helpline. Let us know if you need help with NID KYC manual approval, Cash Agents balance, bookings, or wallet recharges! Type your inquiry below." ) ) ) } // Auto-scroll on new messages LaunchedEffect(localMessages.size) { if (localMessages.isNotEmpty()) { listState.animateScrollToItem(localMessages.size - 1) } } Column( modifier = Modifier .fillMaxSize() .background(com.example.ui.theme.Obsidian) .padding(16.dp), verticalArrangement = Arrangement.SpaceBetween ) { // Quick Support Info Banner Surface( modifier = Modifier.fillMaxWidth(), shape = RoundedCornerShape(12.dp), color = com.example.ui.theme.DarkGreyGlass, border = BorderStroke(1.dp, Color.White.copy(alpha = 0.04f)) ) { Row( modifier = Modifier.padding(12.dp), verticalAlignment = Alignment.CenterVertically ) { Box( modifier = Modifier .size(10.dp) .background(Color.Green, shape = RoundedCornerShape(5.dp)) ) Spacer(modifier = Modifier.width(10.dp)) Column { Text( "Helpline Support Active", style = MaterialTheme.typography.labelLarge, color = Color.White, fontWeight = FontWeight.Bold ) Text( "Instant answers from our auto-assistance operators", style = MaterialTheme.typography.bodySmall, color = Color.Gray ) } } } Spacer(modifier = Modifier.height(12.dp)) // Helpline Messages Log List LazyColumn( state = listState, modifier = Modifier .weight(1f) .fillMaxWidth(), verticalArrangement = Arrangement.spacedBy(10.dp) ) { items(localMessages) { msg -> val isMe = msg.senderType == "USER" val alignment = if (isMe) Alignment.End else Alignment.Start val bubbleColor = if (isMe) com.example.ui.theme.BrandGold else com.example.ui.theme.DarkGreyGlassElevated val textCol = if (isMe) Color.Black else Color.White Column( modifier = Modifier.fillMaxWidth(), horizontalAlignment = alignment ) { if (!isMe) { Text( text = msg.senderName, style = MaterialTheme.typography.labelSmall, color = com.example.ui.theme.BrandGold, modifier = Modifier.padding(start = 4.dp, bottom = 4.dp) ) } Surface( shape = RoundedCornerShape( topStart = 12.dp, topEnd = 12.dp, bottomStart = if (isMe) 12.dp else 0.dp, bottomEnd = if (isMe) 0.dp else 12.dp ), color = bubbleColor, modifier = Modifier.widthIn(max = 280.dp) ) { Text( text = msg.message, style = MaterialTheme.typography.bodyMedium, color = textCol, modifier = Modifier.padding(horizontal = 14.dp, vertical = 10.dp) ) } } } } Spacer(modifier = Modifier.height(12.dp)) // Quick FAQ Helpers Text( "CHOOSE QUICK FAQ TOPIC:", style = MaterialTheme.typography.labelSmall, color = Color.Gray, fontWeight = FontWeight.Bold, modifier = Modifier.padding(bottom = 6.dp) ) Row( modifier = Modifier.fillMaxWidth(), horizontalArrangement = Arrangement.spacedBy(8.dp) ) { val faqs = listOf("Approve KYC request", "Recharge wallet balance", "License Cash Agent") faqs.forEach { query -> Surface( shape = RoundedCornerShape(12.dp), color = com.example.ui.theme.DarkGreyGlass, modifier = Modifier .clickable { val userMsg = com.example.data.LiveChatEntity( id = localMessages.size + 1, streamId = 999, senderId = senderId, senderName = senderName, senderType = "USER", message = query ) localMessages = localMessages + userMsg // Automated Answer Dispatch scope.launch { kotlinx.coroutines.delay(1000) val responseText = when { query.contains("KYC") -> "Please submit your verification details (NID / Selfie) from Settings. Once you fulfil Firebase Email & OTP, the Admin Board can instantly approve your verificationStatus!" query.contains("Recharge") -> "Recharging your balance is quick & safe! Open secure ledger wallet, input deposit amount via bkash/nogod, and complete deposit." else -> "To apply as a Cash Agent, open the Cash Agent Hub page from drawer, input your Nid / Region coordinates, and submit for instant admin license authorization." } val sysMsg = com.example.data.LiveChatEntity( id = localMessages.size + 1, streamId = 999, senderId = "operator", senderName = "Helpline Agent", senderType = "SYSTEM", message = responseText ) localMessages = localMessages + sysMsg } } ) { Text( text = if (query.contains("KYC")) "Verify KYC" else (if (query.contains("Recharge")) "How to Deposit" else "Agent Apply"), modifier = Modifier.padding(horizontal = 12.dp, vertical = 6.dp), style = MaterialTheme.typography.labelMedium, color = com.example.ui.theme.BrandGold, fontWeight = FontWeight.Bold ) } } } Spacer(modifier = Modifier.height(12.dp)) // Chat text entry input & Send Row( modifier = Modifier.fillMaxWidth(), verticalAlignment = Alignment.CenterVertically ) { OutlinedTextField( value = txtMessage, onValueChange = { txtMessage = it }, placeholder = { Text("Describe your support query...", style = MaterialTheme.typography.bodyMedium) }, modifier = Modifier .weight(1f) .testTag("helpline_chat_input"), colors = OutlinedTextFieldDefaults.colors( focusedTextColor = Color.White, unfocusedTextColor = Color.White, focusedContainerColor = com.example.ui.theme.DarkGreyGlass, unfocusedContainerColor = com.example.ui.theme.DarkGreyGlass, focusedBorderColor = com.example.ui.theme.BrandGold, unfocusedBorderColor = com.example.ui.theme.DarkGreyGlassElevated ), shape = RoundedCornerShape(12.dp) ) Spacer(modifier = Modifier.width(8.dp)) IconButton( onClick = { if (txtMessage.trim().isNotEmpty()) { val messageVal = txtMessage.trim() txtMessage = "" val userMsg = com.example.data.LiveChatEntity( id = localMessages.size + 1, streamId = 999, senderId = senderId, senderName = senderName, senderType = "USER", message = messageVal ) localMessages = localMessages + userMsg // Auto Reply assistant scope.launch { kotlinx.coroutines.delay(1200) val answer = when { messageVal.lowercase().contains("verify") || messageVal.lowercase().contains("kyc") -> "Your KYC request manual review status is tracked inside settings. Complete your NID & Selfie, and ensure Firebase variables are successfully linked." messageVal.lowercase().contains("hi") || messageVal.lowercase().contains("hello") -> "Hello! How can the Agency Customer Support group assist you today?" messageVal.lowercase().contains("payment") || messageVal.lowercase().contains("rate") || messageVal.lowercase().contains("money") -> "For balance recharges, safehouse booking locks, and agent commissions, you can check transaction details inside the main Ledger tab." else -> "Thank you! Your inquiry has been logged in our queue. One of our operator agents is reviewing and will respond shortly." } val sysMsg = com.example.data.LiveChatEntity( id = localMessages.size + 1, streamId = 999, senderId = "operator", senderName = "Helpline Support", senderType = "SYSTEM", message = answer ) localMessages = localMessages + sysMsg } } }, modifier = Modifier .size(48.dp) .background(com.example.ui.theme.BrandGold, shape = RoundedCornerShape(24.dp)) .testTag("helpline_send_btn") ) { Icon( imageVector = Icons.Default.Send, contentDescription = "Send message", tint = Color.Black ) } } } }