feat(statistics): implement shimmer loading placeholders in statistics view
This commit is contained in:
parent
b54c204963
commit
8bafa19e31
|
@ -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>();
|
||||
|
|
Loading…
Reference in New Issue