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:get/get.dart';
import 'package:sigap/src/features/panic/presentation/controllers/main_safety_indicator_controller.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/features/panic/presentation/controllers/statistics_view_controller.dart';
import 'package:sigap/src/shared/widgets/loaders/custom_circular_loader.dart'; import 'package:sigap/src/utils/loaders/shimmer.dart';
import 'package:sigap/src/utils/constants/colors.dart';
import 'crime_stats_header.dart'; import 'crime_stats_header.dart';
import 'main_safety_indicator.dart'; import 'main_safety_indicator.dart';
@ -24,81 +23,81 @@ class StatisticsView extends StatelessWidget {
// Main content // Main content
SingleChildScrollView( SingleChildScrollView(
padding: const EdgeInsets.symmetric(horizontal: 20, vertical: 10), padding: const EdgeInsets.symmetric(horizontal: 20, vertical: 10),
child: Column( child:
crossAxisAlignment: CrossAxisAlignment.start, statsController.isLoading.value
children: [ ? _buildShimmerLoading(context)
// District and date information : Column(
CrimeStatsHeader( crossAxisAlignment: CrossAxisAlignment.start,
district: children: [
statsController.currentDistrictId.value.isNotEmpty // District and date information
? _getDistrictName( CrimeStatsHeader(
statsController.currentDistrictId.value, district:
) statsController.currentDistrictId.value.isNotEmpty
: _getLocalityName(), ? _getDistrictName(
month: statsController.currentMonth.value, statsController.currentDistrictId.value,
year: statsController.currentYear.value, )
onMonthChanged: statsController.changeMonth, : _getLocalityName(),
onYearChanged: statsController.changeYear, month: statsController.currentMonth.value,
onRefresh: statsController.refreshStatistics, 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 // Main indicator - Area Safety Level
MainSafetyIndicator( MainSafetyIndicator(
progress: safetyController.progress.value, progress: safetyController.progress.value,
title: safetyController.title.value, title: safetyController.title.value,
label: safetyController.label.value, label: safetyController.label.value,
color: _getSafetyColor(safetyController.progress.value), color: _getSafetyColor(
), safetyController.progress.value,
),
),
const SizedBox(height: 15), const SizedBox(height: 15),
// Secondary indicators row with donut charts // Secondary indicators row with donut charts
Row( Row(
children: [ children: [
Expanded( Expanded(
child: StatIndicatorCard( child: StatIndicatorCard(
progress: statsController.reportsProgress.value, progress: statsController.reportsProgress.value,
value: statsController.reportsValue.value, value: statsController.reportsValue.value,
label: 'Reports', label: 'Reports',
color: Colors.teal, 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 // Error message
if (statsController.errorMessage.value.isNotEmpty) if (statsController.errorMessage.value.isNotEmpty)
Positioned( 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 // Helper to get district name from district ID
String _getDistrictName(String districtId) { String _getDistrictName(String districtId) {
final statsController = Get.find<StatisticsViewController>(); final statsController = Get.find<StatisticsViewController>();