feat(panic): enhance incident logs model and repository, add recent incidents fetching, and improve statistics view UI
This commit is contained in:
parent
b62c64d36d
commit
c116e5d229
|
@ -46,12 +46,12 @@ class IncidentLogModel {
|
|||
json['updated_at'] != null
|
||||
? DateTime.parse(json['updated_at'])
|
||||
: null,
|
||||
evidence:
|
||||
json['evidence'] != null
|
||||
? (json['evidence'] as List)
|
||||
.map((e) => EvidenceModel.fromJson(e as Map<String, dynamic>))
|
||||
.toList()
|
||||
: null,
|
||||
// evidence:
|
||||
// json['evidence'] != null
|
||||
// ? (json['evidence'] as List)
|
||||
// .map((e) => EvidenceModel.fromJson(e as Map<String, dynamic>))
|
||||
// .toList()
|
||||
// : null,
|
||||
);
|
||||
}
|
||||
|
||||
|
|
|
@ -1,5 +1,6 @@
|
|||
import 'package:get/get.dart';
|
||||
import 'package:sigap/src/cores/services/supabase_service.dart';
|
||||
import 'package:sigap/src/features/panic-button/data/models/models/incident_logs_model.dart';
|
||||
import 'package:sigap/src/utils/exceptions/exceptions.dart';
|
||||
|
||||
class IncidentLogsRepository extends GetxController {
|
||||
|
@ -20,6 +21,23 @@ class IncidentLogsRepository extends GetxController {
|
|||
}
|
||||
}
|
||||
|
||||
// Get recent incidents
|
||||
Future<List<IncidentLogModel>> getRecentIncidents() async {
|
||||
try {
|
||||
final response = await _supabase
|
||||
.from('incident_logs')
|
||||
.select('*, location_id(*), category_id(*)')
|
||||
.order('time', ascending: false)
|
||||
.limit(10);
|
||||
|
||||
return List<Map<String, dynamic>>.from(
|
||||
response,
|
||||
).map((e) => IncidentLogModel.fromJson(e)).toList();
|
||||
} catch (e) {
|
||||
throw TExceptions('Failed to fetch recent incidents: ${e.toString()}');
|
||||
}
|
||||
}
|
||||
|
||||
Future<Map<String, dynamic>> getIncidentById(String id) async {
|
||||
try {
|
||||
final response =
|
||||
|
|
|
@ -17,13 +17,17 @@ class PanicButtonController extends GetxController {
|
|||
// Location service
|
||||
final LocationService _locationService = Get.find<LocationService>();
|
||||
|
||||
final RxString currentDistrict = ''.obs;
|
||||
final RxString currentCity = ''.obs;
|
||||
|
||||
Timer? _timer;
|
||||
|
||||
@override
|
||||
void onInit() {
|
||||
super.onInit();
|
||||
// Fetch real location
|
||||
// _fetchLocation();
|
||||
|
||||
// Get current district and city
|
||||
getCurrentDistrict();
|
||||
|
||||
// Setup location updates listener
|
||||
ever(_locationService.currentPosition, (_) => _updateLocationString());
|
||||
|
@ -249,8 +253,23 @@ class PanicButtonController extends GetxController {
|
|||
);
|
||||
}
|
||||
|
||||
// Get user's current district
|
||||
String getCurrentDistrict() {
|
||||
// Fetch real location
|
||||
currentCity.value = _locationService.currentCity.value.replaceAll(
|
||||
RegExp(r'(Kabupaten|Kecamatan)\s*'),
|
||||
'',
|
||||
);
|
||||
currentDistrict.value = _locationService.currentDistrict.value.replaceAll(
|
||||
RegExp(r'(Kabupaten|Kecamatan)\s*'),
|
||||
'',
|
||||
);
|
||||
|
||||
return locationString.value =
|
||||
"${currentCity.value}, ${currentDistrict.value}";
|
||||
}
|
||||
|
||||
// Get user's current district
|
||||
String updateCurrentDistrict() {
|
||||
_fetchLocation(); // Refresh location data
|
||||
|
||||
if (_locationService.currentDistrict.value.isEmpty) {
|
||||
|
|
|
@ -56,6 +56,9 @@ class StatisticsViewController extends GetxController {
|
|||
void onInit() {
|
||||
super.onInit();
|
||||
|
||||
// init district and city names
|
||||
getDistrictName();
|
||||
|
||||
// Load statistics data
|
||||
loadStatisticsData();
|
||||
|
||||
|
@ -86,9 +89,6 @@ class StatisticsViewController extends GetxController {
|
|||
// Load crime statistics
|
||||
await _loadCrimeStatistics();
|
||||
|
||||
// Load recent incidents
|
||||
await _loadRecentIncidents();
|
||||
|
||||
// Update UI components with fetched data
|
||||
_updateStatisticsUI();
|
||||
} catch (e) {
|
||||
|
@ -172,33 +172,6 @@ class StatisticsViewController extends GetxController {
|
|||
}
|
||||
}
|
||||
|
||||
Future<void> _loadRecentIncidents() async {
|
||||
try {
|
||||
final incidents = await _crimeIncidentsRepo.getRecentIncidents();
|
||||
|
||||
// Filter to show only incidents from current district if district ID is available
|
||||
final filteredIncidents =
|
||||
currentDistrictId.value.isNotEmpty
|
||||
? incidents.where((incident) {
|
||||
final locationId =
|
||||
incident['location_id'] is String
|
||||
? incident['location_id']
|
||||
: incident['location_id']?['id'];
|
||||
// Logic to filter by district would go here, but we'd need to join with locations table
|
||||
// For now, just return all incidents
|
||||
return true;
|
||||
}).toList()
|
||||
: incidents;
|
||||
|
||||
recentIncidents.value =
|
||||
filteredIncidents
|
||||
.map((incident) => CrimeIncidentModel.fromJson(incident))
|
||||
.toList();
|
||||
} catch (e) {
|
||||
_logger.e('Error loading recent incidents: $e');
|
||||
}
|
||||
}
|
||||
|
||||
void _updateStatisticsUI() {
|
||||
if (districtCrimes.isEmpty) return;
|
||||
|
||||
|
|
|
@ -2,6 +2,7 @@ import 'package:flutter/material.dart';
|
|||
import 'package:get/get.dart';
|
||||
import 'package:intl/intl.dart';
|
||||
import 'package:sigap/src/features/panic/presentation/controllers/statistics_view_controller.dart';
|
||||
import 'package:sigap/src/utils/constants/sizes.dart';
|
||||
|
||||
class CrimeStatsHeader extends StatelessWidget {
|
||||
final String district;
|
||||
|
@ -28,15 +29,16 @@ class CrimeStatsHeader extends StatelessWidget {
|
|||
|
||||
return Container(
|
||||
width: double.infinity,
|
||||
padding: const EdgeInsets.symmetric(horizontal: 15, vertical: 10),
|
||||
padding: const EdgeInsets.symmetric(horizontal: 16, vertical: 12),
|
||||
decoration: BoxDecoration(
|
||||
color: Colors.white,
|
||||
borderRadius: BorderRadius.circular(10),
|
||||
borderRadius: BorderRadius.circular(12),
|
||||
boxShadow: [
|
||||
BoxShadow(
|
||||
color: Colors.grey.withOpacity(0.1),
|
||||
blurRadius: 5,
|
||||
spreadRadius: 1,
|
||||
color: Colors.grey.withOpacity(0.08),
|
||||
blurRadius: 8,
|
||||
spreadRadius: 0,
|
||||
offset: const Offset(0, 2),
|
||||
),
|
||||
],
|
||||
),
|
||||
|
@ -50,33 +52,46 @@ class CrimeStatsHeader extends StatelessWidget {
|
|||
child: Text(
|
||||
district,
|
||||
style: const TextStyle(
|
||||
fontSize: 16,
|
||||
fontWeight: FontWeight.bold,
|
||||
fontSize: 18,
|
||||
fontWeight: FontWeight.w600,
|
||||
color: Colors.black87,
|
||||
),
|
||||
overflow: TextOverflow.ellipsis,
|
||||
),
|
||||
),
|
||||
IconButton(
|
||||
icon: const Icon(Icons.refresh, size: 20),
|
||||
onPressed: onRefresh,
|
||||
tooltip: 'Refresh statistics',
|
||||
Material(
|
||||
color: Colors.transparent,
|
||||
child: InkWell(
|
||||
borderRadius: BorderRadius.circular(20),
|
||||
onTap: onRefresh,
|
||||
child: Container(
|
||||
padding: const EdgeInsets.all(8),
|
||||
child: const Icon(
|
||||
Icons.refresh_rounded,
|
||||
size: 20,
|
||||
color: Colors.blue,
|
||||
),
|
||||
),
|
||||
),
|
||||
),
|
||||
],
|
||||
),
|
||||
|
||||
const SizedBox(height: 5),
|
||||
const SizedBox(height: 12),
|
||||
|
||||
// Date picker row
|
||||
Row(
|
||||
// Date picker section
|
||||
Container(
|
||||
padding: const EdgeInsets.symmetric(horizontal: 0, vertical: 0),
|
||||
child: Row(
|
||||
children: [
|
||||
const Icon(Icons.calendar_month, size: 16, color: Colors.blue),
|
||||
const SizedBox(width: 5),
|
||||
_buildMonthDropdown(),
|
||||
const SizedBox(width: 5),
|
||||
_buildYearDropdown(statsController.availableYears),
|
||||
Expanded(child: _buildMonthDropdown()),
|
||||
const SizedBox(width: TSizes.spaceBtwInputFields / 2),
|
||||
Expanded(
|
||||
child: _buildYearDropdown(statsController.availableYears),
|
||||
),
|
||||
],
|
||||
),
|
||||
),
|
||||
],
|
||||
),
|
||||
);
|
||||
|
@ -88,48 +103,421 @@ class CrimeStatsHeader extends StatelessWidget {
|
|||
'MMMM',
|
||||
).format(DateTime(int.parse(year), currentMonth));
|
||||
|
||||
return DropdownButton<int>(
|
||||
value: currentMonth,
|
||||
underline: Container(), // Remove underline
|
||||
onChanged: (value) {
|
||||
if (value != null) onMonthChanged(value);
|
||||
return CustomPopupMenuButton<int>(
|
||||
initialValue: currentMonth,
|
||||
buttonBuilder: (context, openMenu, isOpen) {
|
||||
return Container(
|
||||
padding: const EdgeInsets.symmetric(horizontal: 12, vertical: 10),
|
||||
decoration: BoxDecoration(
|
||||
color: Colors.white,
|
||||
borderRadius: BorderRadius.circular(8),
|
||||
border: Border.all(
|
||||
color: isOpen ? Colors.blue.shade300 : Colors.grey.shade300,
|
||||
width: isOpen ? 2 : 1,
|
||||
),
|
||||
boxShadow:
|
||||
isOpen
|
||||
? [
|
||||
BoxShadow(
|
||||
color: Colors.blue.withOpacity(0.1),
|
||||
blurRadius: 4,
|
||||
spreadRadius: 0,
|
||||
),
|
||||
]
|
||||
: null,
|
||||
),
|
||||
child: InkWell(
|
||||
onTap: openMenu,
|
||||
borderRadius: BorderRadius.circular(8),
|
||||
child: Row(
|
||||
mainAxisAlignment: MainAxisAlignment.spaceBetween,
|
||||
children: [
|
||||
Expanded(
|
||||
child: Text(
|
||||
monthName,
|
||||
style: const TextStyle(
|
||||
fontSize: 14,
|
||||
fontWeight: FontWeight.w500,
|
||||
color: Colors.black87,
|
||||
),
|
||||
overflow: TextOverflow.ellipsis,
|
||||
),
|
||||
),
|
||||
AnimatedRotation(
|
||||
turns: isOpen ? 0.5 : 0,
|
||||
duration: const Duration(milliseconds: 200),
|
||||
child: Icon(
|
||||
Icons.keyboard_arrow_down_rounded,
|
||||
color: isOpen ? Colors.blue.shade600 : Colors.grey.shade600,
|
||||
size: 20,
|
||||
),
|
||||
),
|
||||
],
|
||||
),
|
||||
),
|
||||
);
|
||||
},
|
||||
items: List.generate(12, (index) {
|
||||
itemBuilder: (BuildContext context) {
|
||||
return List.generate(12, (index) {
|
||||
final monthNum = index + 1;
|
||||
final monthName = DateFormat(
|
||||
'MMMM',
|
||||
).format(DateTime(int.parse(year), monthNum));
|
||||
final isSelected = monthNum == currentMonth;
|
||||
|
||||
return DropdownMenuItem(
|
||||
return CustomPopupMenuItem<int>(
|
||||
value: monthNum,
|
||||
child: Text(monthName, style: const TextStyle(fontSize: 14)),
|
||||
isSelected: isSelected,
|
||||
child: Row(
|
||||
children: [
|
||||
Container(
|
||||
width: 6,
|
||||
height: 6,
|
||||
decoration: BoxDecoration(
|
||||
color:
|
||||
isSelected ? Colors.blue.shade600 : Colors.transparent,
|
||||
shape: BoxShape.circle,
|
||||
),
|
||||
),
|
||||
const SizedBox(width: 12),
|
||||
Expanded(
|
||||
child: Text(
|
||||
monthName,
|
||||
style: TextStyle(
|
||||
fontSize: 14,
|
||||
fontWeight:
|
||||
isSelected ? FontWeight.w600 : FontWeight.w400,
|
||||
color: isSelected ? Colors.blue.shade700 : Colors.black87,
|
||||
),
|
||||
),
|
||||
),
|
||||
if (isSelected)
|
||||
Icon(
|
||||
Icons.check_rounded,
|
||||
size: 16,
|
||||
color: Colors.blue.shade600,
|
||||
),
|
||||
],
|
||||
),
|
||||
);
|
||||
}),
|
||||
});
|
||||
},
|
||||
onSelected: (value) {
|
||||
onMonthChanged(value);
|
||||
},
|
||||
);
|
||||
}
|
||||
|
||||
Widget _buildYearDropdown(List<int> availableYears) {
|
||||
final int currentYear = int.parse(year);
|
||||
|
||||
return DropdownButton<int>(
|
||||
value:
|
||||
// Use the available years from stats controller
|
||||
final selectedYear =
|
||||
availableYears.contains(currentYear)
|
||||
? currentYear
|
||||
: availableYears.last,
|
||||
underline: Container(), // Remove underline
|
||||
onChanged: (value) {
|
||||
if (value != null) onYearChanged(value);
|
||||
: availableYears.isNotEmpty
|
||||
? availableYears.last
|
||||
: DateTime.now().year;
|
||||
|
||||
return CustomPopupMenuButton<int>(
|
||||
initialValue: selectedYear,
|
||||
buttonBuilder: (context, openMenu, isOpen) {
|
||||
return Container(
|
||||
padding: const EdgeInsets.symmetric(horizontal: 12, vertical: 10),
|
||||
decoration: BoxDecoration(
|
||||
color: Colors.white,
|
||||
borderRadius: BorderRadius.circular(8),
|
||||
border: Border.all(
|
||||
color: isOpen ? Colors.blue.shade300 : Colors.grey.shade300,
|
||||
width: isOpen ? 2 : 1,
|
||||
),
|
||||
boxShadow:
|
||||
isOpen
|
||||
? [
|
||||
BoxShadow(
|
||||
color: Colors.blue.withOpacity(0.1),
|
||||
blurRadius: 4,
|
||||
spreadRadius: 0,
|
||||
),
|
||||
]
|
||||
: null,
|
||||
),
|
||||
child: InkWell(
|
||||
onTap: openMenu,
|
||||
borderRadius: BorderRadius.circular(8),
|
||||
child: Row(
|
||||
mainAxisAlignment: MainAxisAlignment.spaceBetween,
|
||||
children: [
|
||||
Expanded(
|
||||
child: Text(
|
||||
selectedYear.toString(),
|
||||
style: const TextStyle(
|
||||
fontSize: 14,
|
||||
fontWeight: FontWeight.w500,
|
||||
color: Colors.black87,
|
||||
),
|
||||
),
|
||||
),
|
||||
AnimatedRotation(
|
||||
turns: isOpen ? 0.5 : 0,
|
||||
duration: const Duration(milliseconds: 200),
|
||||
child: Icon(
|
||||
Icons.keyboard_arrow_down_rounded,
|
||||
color: isOpen ? Colors.blue.shade600 : Colors.grey.shade600,
|
||||
size: 20,
|
||||
),
|
||||
),
|
||||
],
|
||||
),
|
||||
),
|
||||
);
|
||||
},
|
||||
items:
|
||||
availableYears.map((year) {
|
||||
return DropdownMenuItem(
|
||||
itemBuilder: (BuildContext context) {
|
||||
// Sort the years in descending order (newest first)
|
||||
final sortedYears = List<int>.from(availableYears)
|
||||
..sort((a, b) => b.compareTo(a));
|
||||
|
||||
return sortedYears.map((year) {
|
||||
final isSelected = year == selectedYear;
|
||||
return CustomPopupMenuItem<int>(
|
||||
value: year,
|
||||
isSelected: isSelected,
|
||||
child: Row(
|
||||
children: [
|
||||
Container(
|
||||
width: 6,
|
||||
height: 6,
|
||||
decoration: BoxDecoration(
|
||||
color:
|
||||
isSelected ? Colors.blue.shade600 : Colors.transparent,
|
||||
shape: BoxShape.circle,
|
||||
),
|
||||
),
|
||||
const SizedBox(width: 12),
|
||||
Expanded(
|
||||
child: Text(
|
||||
year.toString(),
|
||||
style: const TextStyle(fontSize: 14),
|
||||
style: TextStyle(
|
||||
fontSize: 14,
|
||||
fontWeight:
|
||||
isSelected ? FontWeight.w600 : FontWeight.w400,
|
||||
color: isSelected ? Colors.blue.shade700 : Colors.black87,
|
||||
),
|
||||
),
|
||||
),
|
||||
if (isSelected)
|
||||
Icon(
|
||||
Icons.check_rounded,
|
||||
size: 16,
|
||||
color: Colors.blue.shade600,
|
||||
),
|
||||
],
|
||||
),
|
||||
);
|
||||
}).toList();
|
||||
},
|
||||
onSelected: (value) {
|
||||
onYearChanged(value);
|
||||
},
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
// Custom popup menu item with selection state
|
||||
class CustomPopupMenuItem<T> {
|
||||
final T value;
|
||||
final Widget child;
|
||||
final bool isSelected;
|
||||
|
||||
const CustomPopupMenuItem({
|
||||
required this.value,
|
||||
required this.child,
|
||||
this.isSelected = false,
|
||||
});
|
||||
}
|
||||
|
||||
// Enhanced custom popup menu button
|
||||
class CustomPopupMenuButton<T> extends StatefulWidget {
|
||||
final T initialValue;
|
||||
final List<CustomPopupMenuItem<T>> Function(BuildContext) itemBuilder;
|
||||
final Widget Function(BuildContext, VoidCallback, bool) buttonBuilder;
|
||||
final void Function(T) onSelected;
|
||||
|
||||
const CustomPopupMenuButton({
|
||||
super.key,
|
||||
required this.initialValue,
|
||||
required this.itemBuilder,
|
||||
required this.buttonBuilder,
|
||||
required this.onSelected,
|
||||
});
|
||||
|
||||
@override
|
||||
State<CustomPopupMenuButton<T>> createState() =>
|
||||
_CustomPopupMenuButtonState<T>();
|
||||
}
|
||||
|
||||
class _CustomPopupMenuButtonState<T> extends State<CustomPopupMenuButton<T>>
|
||||
with TickerProviderStateMixin {
|
||||
late T selectedValue;
|
||||
final LayerLink _layerLink = LayerLink();
|
||||
OverlayEntry? _overlayEntry;
|
||||
bool _isOpen = false;
|
||||
late AnimationController _animationController;
|
||||
late Animation<double> _scaleAnimation;
|
||||
late Animation<double> _opacityAnimation;
|
||||
|
||||
@override
|
||||
void initState() {
|
||||
super.initState();
|
||||
selectedValue = widget.initialValue;
|
||||
_animationController = AnimationController(
|
||||
duration: const Duration(milliseconds: 200),
|
||||
vsync: this,
|
||||
);
|
||||
_scaleAnimation = Tween<double>(begin: 0.8, end: 1.0).animate(
|
||||
CurvedAnimation(parent: _animationController, curve: Curves.easeOutBack),
|
||||
);
|
||||
_opacityAnimation = Tween<double>(begin: 0.0, end: 1.0).animate(
|
||||
CurvedAnimation(parent: _animationController, curve: Curves.easeOut),
|
||||
);
|
||||
}
|
||||
|
||||
void _showOverlay() {
|
||||
if (_isOpen) return;
|
||||
|
||||
setState(() {
|
||||
_isOpen = true;
|
||||
});
|
||||
|
||||
_overlayEntry = _createOverlayEntry();
|
||||
Overlay.of(context).insert(_overlayEntry!);
|
||||
_animationController.forward();
|
||||
}
|
||||
|
||||
void _hideOverlay() {
|
||||
if (!_isOpen) return;
|
||||
|
||||
setState(() {
|
||||
_isOpen = false;
|
||||
});
|
||||
|
||||
_animationController.reverse().then((_) {
|
||||
_overlayEntry?.remove();
|
||||
_overlayEntry = null;
|
||||
});
|
||||
}
|
||||
|
||||
OverlayEntry _createOverlayEntry() {
|
||||
final RenderBox renderBox = context.findRenderObject() as RenderBox;
|
||||
final size = renderBox.size;
|
||||
final offset = renderBox.localToGlobal(Offset.zero);
|
||||
|
||||
return OverlayEntry(
|
||||
builder:
|
||||
(context) => GestureDetector(
|
||||
onTap: _hideOverlay,
|
||||
behavior: HitTestBehavior.translucent,
|
||||
child: Stack(
|
||||
children: [
|
||||
// Invisible overlay to detect taps outside
|
||||
Positioned.fill(child: Container(color: Colors.transparent)),
|
||||
// Dropdown menu
|
||||
Positioned(
|
||||
left: offset.dx,
|
||||
top: offset.dy + size.height + 4,
|
||||
width: size.width,
|
||||
child: AnimatedBuilder(
|
||||
animation: _animationController,
|
||||
builder: (context, child) {
|
||||
return Transform.scale(
|
||||
scale: _scaleAnimation.value,
|
||||
alignment: Alignment.topCenter,
|
||||
child: Opacity(
|
||||
opacity: _opacityAnimation.value,
|
||||
child: Material(
|
||||
elevation: 8,
|
||||
borderRadius: BorderRadius.circular(12),
|
||||
color: Colors.white,
|
||||
shadowColor: Colors.black.withOpacity(0.1),
|
||||
child: Container(
|
||||
decoration: BoxDecoration(
|
||||
color: Colors.white,
|
||||
border: Border.all(color: Colors.grey.shade200),
|
||||
borderRadius: BorderRadius.circular(12),
|
||||
),
|
||||
constraints: const BoxConstraints(maxHeight: 250),
|
||||
child: ClipRRect(
|
||||
borderRadius: BorderRadius.circular(12),
|
||||
child: SingleChildScrollView(
|
||||
padding: const EdgeInsets.symmetric(
|
||||
vertical: 8,
|
||||
),
|
||||
child: Column(
|
||||
mainAxisSize: MainAxisSize.min,
|
||||
children:
|
||||
widget.itemBuilder(context).map((item) {
|
||||
return Material(
|
||||
color: Colors.transparent,
|
||||
child: InkWell(
|
||||
onTap: () {
|
||||
_hideOverlay();
|
||||
widget.onSelected(item.value);
|
||||
setState(() {
|
||||
selectedValue = item.value;
|
||||
});
|
||||
},
|
||||
child: Container(
|
||||
width: double.infinity,
|
||||
padding:
|
||||
const EdgeInsets.symmetric(
|
||||
vertical: 12,
|
||||
horizontal: 16,
|
||||
),
|
||||
decoration: BoxDecoration(
|
||||
color:
|
||||
item.isSelected
|
||||
? Colors.blue
|
||||
.withOpacity(0.05)
|
||||
: Colors.transparent,
|
||||
),
|
||||
child: item.child,
|
||||
),
|
||||
),
|
||||
);
|
||||
}).toList(),
|
||||
),
|
||||
),
|
||||
),
|
||||
),
|
||||
),
|
||||
),
|
||||
);
|
||||
},
|
||||
),
|
||||
),
|
||||
],
|
||||
),
|
||||
),
|
||||
);
|
||||
}
|
||||
|
||||
@override
|
||||
void dispose() {
|
||||
_hideOverlay();
|
||||
_animationController.dispose();
|
||||
super.dispose();
|
||||
}
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
return CompositedTransformTarget(
|
||||
link: _layerLink,
|
||||
child: widget.buttonBuilder(context, () {
|
||||
if (_isOpen) {
|
||||
_hideOverlay();
|
||||
} else {
|
||||
_showOverlay();
|
||||
}
|
||||
}, _isOpen),
|
||||
);
|
||||
}
|
||||
}
|
||||
|
|
Loading…
Reference in New Issue