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: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>();
|
||||||
|
|
Loading…
Reference in New Issue