feat(statistics): implement shimmer loading placeholders in statistics view

This commit is contained in:
vergiLgood1 2025-05-27 17:45:43 +07:00
parent b54c204963
commit 8bafa19e31
1 changed files with 231 additions and 70 deletions

View File

@ -2,8 +2,7 @@ import 'package:flutter/material.dart';
import 'package:get/get.dart';
import 'package:sigap/src/features/panic/presentation/controllers/main_safety_indicator_controller.dart';
import 'package:sigap/src/features/panic/presentation/controllers/statistics_view_controller.dart';
import 'package:sigap/src/shared/widgets/loaders/custom_circular_loader.dart';
import 'package:sigap/src/utils/constants/colors.dart';
import 'package:sigap/src/utils/loaders/shimmer.dart';
import 'crime_stats_header.dart';
import 'main_safety_indicator.dart';
@ -24,81 +23,81 @@ class StatisticsView extends StatelessWidget {
// Main content
SingleChildScrollView(
padding: const EdgeInsets.symmetric(horizontal: 20, vertical: 10),
child: Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
// District and date information
CrimeStatsHeader(
district:
statsController.currentDistrictId.value.isNotEmpty
? _getDistrictName(
statsController.currentDistrictId.value,
)
: _getLocalityName(),
month: statsController.currentMonth.value,
year: statsController.currentYear.value,
onMonthChanged: statsController.changeMonth,
onYearChanged: statsController.changeYear,
onRefresh: statsController.refreshStatistics,
),
child:
statsController.isLoading.value
? _buildShimmerLoading(context)
: Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
// District and date information
CrimeStatsHeader(
district:
statsController.currentDistrictId.value.isNotEmpty
? _getDistrictName(
statsController.currentDistrictId.value,
)
: _getLocalityName(),
month: statsController.currentMonth.value,
year: statsController.currentYear.value,
onMonthChanged: statsController.changeMonth,
onYearChanged: statsController.changeYear,
onRefresh: statsController.refreshStatistics,
),
const SizedBox(height: 15),
const SizedBox(height: 15),
// Main indicator - Area Safety Level
MainSafetyIndicator(
progress: safetyController.progress.value,
title: safetyController.title.value,
label: safetyController.label.value,
color: _getSafetyColor(safetyController.progress.value),
),
// Main indicator - Area Safety Level
MainSafetyIndicator(
progress: safetyController.progress.value,
title: safetyController.title.value,
label: safetyController.label.value,
color: _getSafetyColor(
safetyController.progress.value,
),
),
const SizedBox(height: 15),
const SizedBox(height: 15),
// Secondary indicators row with donut charts
Row(
children: [
Expanded(
child: StatIndicatorCard(
progress: statsController.reportsProgress.value,
value: statsController.reportsValue.value,
label: 'Reports',
color: Colors.teal,
),
// Secondary indicators row with donut charts
Row(
children: [
Expanded(
child: StatIndicatorCard(
progress: statsController.reportsProgress.value,
value: statsController.reportsValue.value,
label: 'Reports',
color: Colors.teal,
),
),
const SizedBox(width: 10),
Expanded(
child: StatIndicatorCard(
progress: statsController.zoneMinProgress.value,
value: statsController.zoneMinValue.value,
label: 'Safety Score',
color: Colors.green,
),
),
const SizedBox(width: 10),
Expanded(
child: StatIndicatorCard(
progress: statsController.mindfulProgress.value,
value: statsController.mindfulValue.value,
label: 'Solved Rate',
color: Colors.indigo,
),
),
],
),
const SizedBox(height: 15),
// Recovery indicator with unverified incidents
const RecoveryIndicator(),
],
),
const SizedBox(width: 10),
Expanded(
child: StatIndicatorCard(
progress: statsController.zoneMinProgress.value,
value: statsController.zoneMinValue.value,
label: 'Safety Score',
color: Colors.green,
),
),
const SizedBox(width: 10),
Expanded(
child: StatIndicatorCard(
progress: statsController.mindfulProgress.value,
value: statsController.mindfulValue.value,
label: 'Solved Rate',
color: Colors.indigo,
),
),
],
),
const SizedBox(height: 15),
// Recovery indicator with unverified incidents
const RecoveryIndicator(),
],
),
),
// Loading indicator
if (statsController.isLoading.value)
Center(child: TLoader.centeredLoader(color: TColors.primary),
),
// Error message
if (statsController.errorMessage.value.isNotEmpty)
Positioned(
@ -124,6 +123,168 @@ class StatisticsView extends StatelessWidget {
);
}
// Build shimmer loading placeholders
Widget _buildShimmerLoading(BuildContext context) {
return Column(
children: [
// Header shimmer
_buildHeaderShimmer(),
const SizedBox(height: 15),
// Main indicator shimmer
_buildMainIndicatorShimmer(),
const SizedBox(height: 15),
// Stats cards shimmer
Row(
children: [
Expanded(child: _buildStatCardShimmer()),
const SizedBox(width: 10),
Expanded(child: _buildStatCardShimmer()),
const SizedBox(width: 10),
Expanded(child: _buildStatCardShimmer()),
],
),
const SizedBox(height: 15),
// Recovery indicator shimmer
_buildRecoveryShimmer(),
],
);
}
// Header shimmer placeholder
Widget _buildHeaderShimmer() {
return Container(
width: double.infinity,
padding: const EdgeInsets.all(15),
decoration: BoxDecoration(
color: Colors.white,
borderRadius: BorderRadius.circular(10),
boxShadow: [
BoxShadow(
color: Colors.grey.withOpacity(0.1),
blurRadius: 5,
spreadRadius: 1,
),
],
),
child: Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
Row(
mainAxisAlignment: MainAxisAlignment.spaceBetween,
children: [
const TShimmerEffect(width: 150, height: 20, radius: 4),
Container(
width: 24,
height: 24,
decoration: BoxDecoration(
color: Colors.grey.withOpacity(0.2),
shape: BoxShape.circle,
),
),
],
),
const SizedBox(height: 15),
Row(
children: const [
TShimmerEffect(width: 120, height: 30, radius: 5),
SizedBox(width: 10),
TShimmerEffect(width: 80, height: 30, radius: 5),
],
),
],
),
);
}
// Main safety indicator shimmer
Widget _buildMainIndicatorShimmer() {
return Container(
width: double.infinity,
padding: const EdgeInsets.all(20),
decoration: BoxDecoration(
color: Colors.white,
borderRadius: BorderRadius.circular(10),
boxShadow: [
BoxShadow(
color: Colors.grey.withOpacity(0.1),
blurRadius: 5,
spreadRadius: 1,
),
],
),
child: Column(
children: [
const TShimmerEffect(width: 120, height: 120, radius: 60), // Circle
const SizedBox(height: 15),
const TShimmerEffect(width: 100, height: 20, radius: 4), // Text
],
),
);
}
// Stat card shimmer
Widget _buildStatCardShimmer() {
return Container(
padding: const EdgeInsets.all(15),
decoration: BoxDecoration(
color: Colors.white,
borderRadius: BorderRadius.circular(10),
boxShadow: [
BoxShadow(
color: Colors.grey.withOpacity(0.1),
blurRadius: 5,
spreadRadius: 1,
),
],
),
child: Column(
children: const [
TShimmerEffect(width: 60, height: 60, radius: 30), // Circle
SizedBox(height: 10),
TShimmerEffect(width: 40, height: 18, radius: 4), // Value
SizedBox(height: 5),
TShimmerEffect(width: 60, height: 14, radius: 4), // Label
],
),
);
}
// Recovery indicator shimmer
Widget _buildRecoveryShimmer() {
return Container(
width: double.infinity,
padding: const EdgeInsets.all(15),
decoration: BoxDecoration(
color: Colors.white,
borderRadius: BorderRadius.circular(10),
boxShadow: [
BoxShadow(
color: Colors.grey.withOpacity(0.1),
blurRadius: 5,
spreadRadius: 1,
),
],
),
child: Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: const [
TShimmerEffect(width: 140, height: 20, radius: 4), // Title
SizedBox(height: 15),
TShimmerEffect(
width: double.infinity,
height: 12,
radius: 6,
), // Progress bar
SizedBox(height: 10),
TShimmerEffect(width: 80, height: 16, radius: 4), // Status text
],
),
);
}
// Helper to get district name from district ID
String _getDistrictName(String districtId) {
final statsController = Get.find<StatisticsViewController>();