@Composable
fun ISSMComposableStatistics(
navController: NavController? = null,
premiseViewModel: PremiseViewmodel,
tariffViewModel: TariffViewModel
) {
val context = LocalContext.current
val view = LocalView.current
val window = (view.context as Activity).window
window.statusBarColor = White.toArgb()
val scrollState = rememberScrollState()
WindowInsetsControllerCompat(window, view).isAppearanceLightStatusBars = true
val statisticsViewModel: ISSMStatisticsViewModel = hiltViewModel()
val navigationViewModel: ISSMNavigationViewModel = hiltViewModel()
val connectivityViewModel: ConnectivityViewModel = hiltViewModel()
val dbOrderViewModel: DBOrderViewModel = hiltViewModel()
LaunchedEffect(Unit) {
window?.let {
WindowCompat.setDecorFitsSystemWindows(it, true)
}
resetMissingConnectionStateState()
statisticsViewModel.observeConnectivityState(
connectivityViewModel.isOnline,
connectivityViewModel::isNetworkAvailable
)
statisticsViewModel.getBothStats(_id)
}
DisposableEffect(Unit) {
onDispose {
statisticsViewModel.cleanup()
}
}
val state = statisticsViewModel.combinedStats.observeAsState()
when (val statsState = state.value) {
is CombinedStatisticsUIState.Loading -> {
ShimmerAnimationStatistics()
}
is CombinedStatisticsUIState.Success -> {
Timber.tag("ListApi").d("loadPremise & loadTariffs called from Stats Screen")
premiseViewModel.loadPremise()
tariffViewModel.loadTariffs()
Column(
modifier = Modifier
.fillMaxSize()
.background(White)
) {
Box(
modifier = Modifier
.fillMaxSize()
.background(color = Color.Transparent)
){
Column(modifier = Modifier.padding(16.dp).verticalScroll(scrollState)) {
UserInfoCard(
connectivityViewModel = connectivityViewModel
)
var selectedTabIndex by remember { mutableStateOf(0) }
val tabs = listOf("Current Statistics", "Overall Statistics")
Column {
TabRow(
selectedTabIndex = selectedTabIndex,
containerColor = White,
contentColor = Color(0xFFE91E63), // Pink color from the image
indicator = { tabPositions ->
SecondaryIndicator(
modifier = Modifier.tabIndicatorOffset(tabPositions[selectedTabIndex]),
height = 2.dp,
color = Color(0xFFE91E63)
)
}
) {
tabs.forEachIndexed { index, title ->
Tab(
selected = selectedTabIndex == index,
onClick = { selectedTabIndex = index },
text = {
Text(
text = title,
color = if (selectedTabIndex == index)
Color(0xFFE91E63) else Color.Gray
)
}
)
}
}
when (selectedTabIndex) {
0 -> {
val totalPending = statsState.currentStats.pendingInitialReadOrders + statsState.currentStats.pendingCheckReadOrders
TotalOrdersCard(
totalOrders = statsState.currentStats.totalOrders,
initialReadOrders = statsState.currentStats.pendingInitialReadOrders,
checkReadOrders = statsState.currentStats.pendingCheckReadOrders,
pendingOrders = totalPending,
employeeDoneOrders = statsState.currentStats.employeeDone,
postedToPortalOrders = statsState.currentStats.postedToPortal,
)
Spacer(modifier = Modifier.height(10.dp))
Column(
modifier = Modifier
.fillMaxWidth()
.padding(start = 16.dp),
horizontalAlignment = Alignment.Start
) {
Text(
text = "Status",
fontSize = 18.sp,
fontFamily = Assistant,
fontWeight = FontWeight.SemiBold,
color = Color.Black
)
}
// Status Card
val validProgress = if (statsState.currentStats.progress == "NaN") "0.0" else statsState.currentStats.progress
StatusCard(progress = validProgress)
Spacer(modifier = Modifier.height(8.dp))
// View Order Button
Box(
modifier = Modifier
.fillMaxWidth()
.padding(16.dp),
) {
ISSM_Button(
heading = "View Current Orders",
onClicked = {
navigationViewModel.navigateToScreen(
navController = navController,
route = NavScreen.ScreenSubOffice.route
)
}
)
}
}
1 -> {
val totalPending = statsState.overallStats.pendingInitialReadOrders + statsState.overallStats.pendingCheckReadOrders
TotalOrdersCard(
totalOrders = statsState.overallStats.totalOrders,
initialReadOrders = statsState.overallStats.pendingInitialReadOrders,
checkReadOrders = statsState.overallStats.pendingCheckReadOrders,
pendingOrders = totalPending,
employeeDoneOrders = statsState.overallStats.employeeDone,
postedToPortalOrders = statsState.overallStats.postedToPortal,
picturesTaken = statsState.overallStats.pictureTaken,
)
}
}
}
}
Box(
modifier = Modifier.align(Alignment.TopStart)
) {
DrawerButton(navController = navController)
}
}
}
}
is CombinedStatisticsUIState.Error -> {
ISSM_Logout_Dialog(
onDismissRequest = { },
onConfirmation = {
PreferencesManager.removeAccessToken(context = context)
PreferencesManager.removeEmployeeIdToken(context = context)
navigationViewModel.navigateToScreen(
navController = navController,
route = NavScreen.ScreenLogin.route,
popUpTo = null,
inclusive = true,
launchSingleTop = true,
popUpToId = 0
)
dbOrderViewModel.deleteAllData()
val activity = context as? MainActivity
activity?.checkAndStopLocationServiceIfLoggedOut()
},
onRetry = {
statisticsViewModel.getBothStats(_id)
},
icon = 0,
type = "Logout",
heading = statsState.error,
subHeading = "Try logging in again!"
)
}
null -> Text("Waiting for Data...")
}
}
@Composable
fun TotalOrdersCard(
totalOrders: Int,
initialReadOrders: Int,
checkReadOrders: Int,
pendingOrders: Int,
employeeDoneOrders: Int,
postedToPortalOrders: Int,
picturesTaken: Int? = null,
) {
Card(
modifier = Modifier
.fillMaxWidth()
.padding(16.dp),
shape = RoundedCornerShape(12.dp),
colors = CardDefaults.cardColors(containerColor = Color.White),
elevation = CardDefaults.cardElevation(2.dp)
) {
Column(
modifier = Modifier
.fillMaxWidth()
.padding(16.dp),
horizontalAlignment = Alignment.CenterHorizontally
) {
// Total Orders Count
Text(
text = totalOrders.toString(),
fontSize = 42.sp,
fontWeight = FontWeight.Bold,
fontFamily = FontFamily.SansSerif,
color = Color(0xFFD2386C) // Pink Color
)
Text(
text = "Total Orders",
fontSize = 18.sp,
fontWeight = FontWeight.Medium,
fontFamily = FontFamily.SansSerif,
color = Color.Gray
)
Spacer(modifier = Modifier.height(22.dp))
// Reads Row
Row(
modifier = Modifier.fillMaxWidth(),
horizontalArrangement = Arrangement.spacedBy(8.dp, Alignment.CenterHorizontally) // Adjust spacing
) {
Column(
modifier = Modifier
.background(color = Color(0xFFFBFBFB), shape = RoundedCornerShape(8.dp))
.padding(16.dp),
horizontalAlignment = Alignment.CenterHorizontally
) {
Text(
text = "Initial Read",
fontSize = 14.sp,
fontWeight = FontWeight.Medium,
fontFamily = FontFamily.SansSerif,
color = Color.Gray
)
Text(
text = initialReadOrders.toString(),
fontSize = 18.sp,
fontWeight = FontWeight.Bold,
fontFamily = FontFamily.SansSerif,
color = Color.Black
)
}
Column(
modifier = Modifier
.background(color = Color(0xFFFBFBFB), shape = RoundedCornerShape(8.dp))
.padding(16.dp),
horizontalAlignment = Alignment.CenterHorizontally
) {
Text(
text = "Check Read",
fontSize = 14.sp,
fontWeight = FontWeight.Medium,
fontFamily = FontFamily.SansSerif,
color = Color.Gray
)
Text(
text = checkReadOrders.toString(),
fontSize = 18.sp,
fontWeight = FontWeight.Bold,
fontFamily = FontFamily.SansSerif,
color = Color.Black
)
}
}
Spacer(modifier = Modifier.height(16.dp))
// Additional Info
Column(
modifier = Modifier.fillMaxWidth(),
verticalArrangement = Arrangement.spacedBy(9.dp)
) {
DetailRow(label = "Pending", value = pendingOrders.toString())
DetailRow(label = "Employee Done", value = employeeDoneOrders.toString())
DetailRow(label = "Posted to portal", value = postedToPortalOrders.toString())
if(picturesTaken != null){
DetailRow(label = "Pictures Taken", value = picturesTaken.toString())
}
}
}
}
}
@Composable
fun DetailRow(label: String, value: String) {
Row(
modifier = Modifier.fillMaxWidth(),
horizontalArrangement = Arrangement.SpaceBetween,
verticalAlignment = Alignment.CenterVertically
) {
Text(
text = label,
fontSize = 14.sp,
fontWeight = FontWeight.Medium,
fontFamily = FontFamily.SansSerif,
color = Color.Gray
)
Text(
text = value,
fontSize = 14.sp,
fontWeight = FontWeight.Bold,
fontFamily = FontFamily.SansSerif,
color = Color.Black
)
}
}
@Composable
fun StatusCard(
progress: String
) {
Card(
modifier = Modifier
.fillMaxWidth()
.padding(16.dp),
shape = RoundedCornerShape(12.dp),
colors = CardDefaults.cardColors(containerColor = Color.White),
elevation = CardDefaults.cardElevation(2.dp)
) {
Column(
modifier = Modifier
.fillMaxWidth()
.padding(16.dp),
horizontalAlignment = Alignment.CenterHorizontally
) {
Spacer(modifier = Modifier.height(14.dp))
Box(
modifier = Modifier.fillMaxWidth(),
contentAlignment = Alignment.Center
) {
CircularProgressBarWithPercentage(
progress = progress.toDouble(), // Replace with your dynamic value
circleColor = PrimaryColor,
trackColor = Color.LightGray,
textColor = Color.Black
)
}
Spacer(modifier = Modifier.height(18.dp))
Text(
text = "You have achieved",
fontSize = 16.sp,
fontFamily = FontFamily.SansSerif,
fontWeight = FontWeight.SemiBold,
color = Color.Black
)
Text(
text = "${progress}% of your goal today",
fontSize = 16.sp,
fontFamily = FontFamily.SansSerif,
fontWeight = FontWeight.SemiBold,
color = Color.Black
)
}
}
}
@Composable
fun UserInfoCard(
connectivityViewModel: ConnectivityViewModel
) {
val userDetails = ISSMSingleton.getLoggedUser()
val calendar = Calendar.getInstance()
val currentDay = calendar.getDisplayName(Calendar.DAY_OF_WEEK, Calendar.LONG, Locale.getDefault()) ?: "Unknown"
val formatter = SimpleDateFormat("dd-MM-yyyy", Locale.getDefault())
val formattedDate = formatter.format(calendar.time)
val isOnline by connectivityViewModel.isOnline.collectAsState()
val loggedUser = ISSMSingleton.getLoggedUser()
Row(
modifier = Modifier
.fillMaxWidth()
.padding(top = 22.dp, start = 66.dp, bottom = 16.dp, end = 16.dp),
horizontalArrangement = Arrangement.SpaceBetween
) {
Column(
modifier = Modifier.weight(1f)
) {
Text(
text = userDetails.userFirstName + " " + userDetails.userLastName,
fontSize = 24.sp,
fontFamily = Assistant,
fontWeight = FontWeight.Bold,
color = Color.Black
)
if (userDetails.userPhone.isNotEmpty()){
Text(
text = userDetails.userPhone,
fontSize = 16.sp,
fontFamily = Assistant,
color = Color.Gray
)
}
Text(
text = "$currentDay - $formattedDate",
fontSize = 12.sp,
fontFamily = Assistant,
color = Color.Gray
)
}
Box(
modifier = Modifier.size(50.dp) // Size of the entire container
) {
// Circular user image
GlideImage(
imageRes = loggedUser.userImage,
contentDescription = Const_Profile_Image_Desc,
modifier = Modifier
.size(50.dp)
.clip(CircleShape) // Clip to the circle shape
.background(Color.Gray)
)
// Online/Offline indicator
Box(
modifier = Modifier
.size(12.dp) // Size of the indicator
.clip(CircleShape) // Circular shape
.background(if (isOnline) Color.Green else Color.Red) // Dynamic color
.align(Alignment.TopEnd) // Position at the top-right corner
.padding(4.dp) // Optional: Adjust padding as needed
)
}
}
}
@Composable
fun CircularProgressBarWithPercentage(
progress: Double,
circleColor: Color = Color(0xFF6200EE), // Customize as needed
trackColor: Color = Color(0xFFEEEEEE),
textColor: Color = Color.Black,
modifier: Modifier = Modifier.size(100.dp) // Default size of the circle
) {
Box(
contentAlignment = Alignment.Center,
modifier = modifier
) {
// Track (background circle)
Canvas(modifier = Modifier.fillMaxSize()) {
drawArc(
color = trackColor,
startAngle = -90f, // Start from top
sweepAngle = 360f, // Full circle
useCenter = false,
style = Stroke(width = 12.dp.toPx(), cap = StrokeCap.Round)
)
}
// Progress (foreground circle)
Canvas(modifier = Modifier.fillMaxSize()) {
drawArc(
color = circleColor,
startAngle = -90f, // Start from top
sweepAngle = (progress / 100 * 360).toFloat(), // Calculate the angle based on progress
useCenter = false,
style = Stroke(width = 12.dp.toPx(), cap = StrokeCap.Round)
)
}
// Percentage Text
Text(
text = "${progress}%",
fontSize = 18.sp,
fontWeight = FontWeight.Bold,
color = textColor
)
}
}
----
the logcat shows
2025-01-17 22:15:29.904 28058-28058 ListApi ai.issm.issm_fsm_app D loadPremise & loadTariffs called from Stats Screen
2025-01-17 22:15:29.904 28058-28058 ListApi ai.issm.issm_fsm_app D loadPremise called
2025-01-17 22:15:29.907 28058-28058 ListApi ai.issm.issm_fsm_app D loadTariffs called
2025-01-17 22:15:30.110 28058-28058 ListApi ai.issm.issm_fsm_app D loadPremise & loadTariffs called from Stats Screen
2025-01-17 22:15:30.110 28058-28058 ListApi ai.issm.issm_fsm_app D loadPremise called
2025-01-17 22:15:30.113 28058-28058 ListApi ai.issm.issm_fsm_app D loadTariffs called
2025-01-17 22:15:32.552 28058-28058 ListApi ai.issm.issm_fsm_app D loadPremise & loadTariffs called from Stats Screen
2025-01-17 22:15:32.552 28058-28058 ListApi ai.issm.issm_fsm_app D loadPremise called
2025-01-17 22:15:32.556 28058-28058 ListApi ai.issm.issm_fsm_app D loadTariffs called
meaning the code
premiseViewModel.loadPremise()
tariffViewModel.loadTariffs()
gets calls again although the CombinedStatisticsUIState.Success happens once. I have noticed that when i perform an action inside the statistics screen eg click on the View Current Orders button to naviagte to the next screen, then it calls the
premiseViewModel.loadPremise()
tariffViewModel.loadTariffs()
again