1135 lines
45 KiB
Dart
1135 lines
45 KiB
Dart
import 'dart:io';
|
|
import 'dart:math';
|
|
import 'dart:ui';
|
|
|
|
import 'package:cloud_firestore/cloud_firestore.dart';
|
|
import 'package:flutter/material.dart';
|
|
import 'package:flutter/rendering.dart';
|
|
import 'package:flutter/services.dart';
|
|
import 'package:image_gallery_saver/image_gallery_saver.dart';
|
|
import 'package:path_provider/path_provider.dart';
|
|
import 'package:permission_handler/permission_handler.dart';
|
|
import 'package:supabase_flutter/supabase_flutter.dart';
|
|
import 'package:ternak/block_detail.dart';
|
|
import 'package:ternak/cage_edit.dart';
|
|
import 'package:firebase_auth/firebase_auth.dart' as fire;
|
|
|
|
class CageDetail extends StatefulWidget {
|
|
final QueryDocumentSnapshot<Map<String, dynamic>> doc;
|
|
final String total;
|
|
final fire.User user;
|
|
const CageDetail(
|
|
{super.key, required this.doc, required this.total, required this.user});
|
|
|
|
@override
|
|
State<CageDetail> createState() => _CageDetailState();
|
|
}
|
|
|
|
class _CageDetailState extends State<CageDetail> {
|
|
var canUse = 0;
|
|
var totalBlok = 0;
|
|
|
|
final GlobalKey _globalKey = GlobalKey();
|
|
|
|
late DocumentSnapshot<Map<String, dynamic>> doc;
|
|
|
|
void getTotal() {
|
|
FirebaseFirestore.instance
|
|
.collection("blok")
|
|
.where("kandang_id", isEqualTo: doc.id)
|
|
.count()
|
|
.get()
|
|
.then((value) => setState(() {
|
|
var kapasitasInt = int.tryParse(doc.data()?["kapasitas"] ?? "");
|
|
var totalInt = int.tryParse(widget.total);
|
|
|
|
if (kapasitasInt != null && totalInt != null) {
|
|
canUse = max(0, kapasitasInt - totalInt);
|
|
}
|
|
totalBlok = value.count ?? 0;
|
|
}));
|
|
}
|
|
|
|
@override
|
|
void initState() {
|
|
super.initState();
|
|
doc = widget.doc;
|
|
getTotal();
|
|
|
|
var kapasitasInt = int.tryParse(doc.data()?["kapasitas"] ?? "");
|
|
var totalInt = int.tryParse(widget.total);
|
|
|
|
if (kapasitasInt != null && totalInt != null) {
|
|
canUse = max(0, kapasitasInt - totalInt);
|
|
}
|
|
}
|
|
|
|
Future<Uint8List?> _getImage() async {
|
|
String imageFile = doc.data()?["image"];
|
|
var value = await Supabase.instance.client.storage
|
|
.from('terdom')
|
|
.download("kandang/$imageFile");
|
|
|
|
return value;
|
|
}
|
|
|
|
void _showDeleteConfirmation(BuildContext context) {
|
|
showDialog(
|
|
context: context,
|
|
builder: (BuildContext context) {
|
|
return AlertDialog(
|
|
shape: RoundedRectangleBorder(
|
|
borderRadius: BorderRadius.circular(16),
|
|
),
|
|
title: Row(
|
|
children: [
|
|
Container(
|
|
padding: const EdgeInsets.all(8),
|
|
decoration: BoxDecoration(
|
|
color: Colors.red.withOpacity(0.1),
|
|
shape: BoxShape.circle,
|
|
),
|
|
child: const Icon(Icons.delete_outline, color: Colors.red),
|
|
),
|
|
const SizedBox(width: 16),
|
|
const Text("Konfirmasi Hapus"),
|
|
],
|
|
),
|
|
content: const Text(
|
|
"Apakah kamu yakin ingin menghapus kandang ini? Tindakan ini tidak dapat dibatalkan."),
|
|
actions: [
|
|
TextButton(
|
|
onPressed: () => Navigator.of(context).pop(),
|
|
style: TextButton.styleFrom(
|
|
foregroundColor: Colors.grey[700],
|
|
),
|
|
child: const Text("Batal"),
|
|
),
|
|
ElevatedButton(
|
|
onPressed: () {
|
|
FirebaseFirestore.instance
|
|
.collection("kandang")
|
|
.doc(doc.id)
|
|
.delete()
|
|
.then((value) {
|
|
Navigator.of(context).pop(); // Tutup dialog
|
|
Navigator.of(context).pop(); // penumpuk
|
|
ScaffoldMessenger.of(context).showSnackBar(
|
|
const SnackBar(
|
|
content: Text("Berhasil dihapus"),
|
|
backgroundColor: Colors.green,
|
|
behavior: SnackBarBehavior.floating,
|
|
),
|
|
);
|
|
}).catchError((value) {
|
|
Navigator.of(context).pop(); // Tutup dialog
|
|
ScaffoldMessenger.of(context).showSnackBar(
|
|
const SnackBar(
|
|
content: Text("Gagal menghapus"),
|
|
backgroundColor: Colors.red,
|
|
behavior: SnackBarBehavior.floating,
|
|
),
|
|
);
|
|
});
|
|
},
|
|
style: ElevatedButton.styleFrom(
|
|
backgroundColor: Colors.red,
|
|
foregroundColor: Colors.white,
|
|
elevation: 0,
|
|
shape: RoundedRectangleBorder(
|
|
borderRadius: BorderRadius.circular(8),
|
|
),
|
|
),
|
|
child: const Text("Hapus"),
|
|
),
|
|
],
|
|
);
|
|
},
|
|
);
|
|
}
|
|
|
|
Future<void> requestStoragePermission(imageUrl) async {
|
|
var status = await Permission.storage.status;
|
|
var status2 = await Permission.photos.status;
|
|
|
|
if (!status.isGranted || !status2.isGranted) {
|
|
status = await Permission.storage.request();
|
|
|
|
if (status.isGranted || status2.isGranted) {
|
|
print("Izin storage diberikan.");
|
|
_captureAndSave();
|
|
} else if (status.isDenied || status2.isDenied) {
|
|
print("Izin storage ditolak.");
|
|
if (!mounted) return;
|
|
|
|
ScaffoldMessenger.of(context)
|
|
.showSnackBar(const SnackBar(content: Text('Permission denied')));
|
|
} else if (status.isPermanentlyDenied || status2.isPermanentlyDenied) {
|
|
print(
|
|
"Izin storage ditolak permanen, buka pengaturan untuk mengaktifkan.");
|
|
openAppSettings();
|
|
}
|
|
}
|
|
}
|
|
|
|
Future<void> _captureAndSave() async {
|
|
// Meminta izin penyimpanan
|
|
RenderRepaintBoundary boundary =
|
|
_globalKey.currentContext!.findRenderObject()! as RenderRepaintBoundary;
|
|
var image = await boundary.toImage(pixelRatio: 5.0);
|
|
ByteData? byteData = await image.toByteData(format: ImageByteFormat.png);
|
|
Uint8List pngBytes = byteData!.buffer.asUint8List();
|
|
|
|
final directory = (await getTemporaryDirectory()).path;
|
|
final filePath = '$directory/${widget.doc.data()['image']}';
|
|
final file = File(filePath);
|
|
await file.writeAsBytes(pngBytes);
|
|
|
|
// Simpan ke galeri
|
|
await ImageGallerySaver.saveFile(filePath);
|
|
|
|
if (!mounted) return;
|
|
|
|
ScaffoldMessenger.of(context)
|
|
.showSnackBar(const SnackBar(content: Text('Image saved to gallery')));
|
|
}
|
|
|
|
void _showImage() async {
|
|
String imageUrl = Supabase.instance.client.storage
|
|
.from('terdom')
|
|
.getPublicUrl("kandang/${doc.data()?['image']}");
|
|
|
|
showDialog(
|
|
context: context,
|
|
barrierDismissible: false,
|
|
builder: (BuildContext context) {
|
|
return AlertDialog(
|
|
shape: RoundedRectangleBorder(
|
|
borderRadius: BorderRadius.circular(20),
|
|
),
|
|
contentPadding: const EdgeInsets.all(16),
|
|
content: SingleChildScrollView(
|
|
child: Column(
|
|
mainAxisSize: MainAxisSize.min,
|
|
children: <Widget>[
|
|
SizedBox(
|
|
height: MediaQuery.of(context).size.width * 0.7,
|
|
width: MediaQuery.of(context).size.height * 0.4,
|
|
child: Center(
|
|
child: RepaintBoundary(
|
|
key: _globalKey,
|
|
child: Container(
|
|
color: Colors.white,
|
|
child: ClipRRect(
|
|
borderRadius: BorderRadius.circular(12),
|
|
child: Image.network(
|
|
imageUrl,
|
|
errorBuilder: (context, error, stackTrace) {
|
|
return Container(
|
|
padding: const EdgeInsets.all(20),
|
|
decoration: BoxDecoration(
|
|
color: Colors.grey[100],
|
|
borderRadius: BorderRadius.circular(12),
|
|
),
|
|
child: const Column(
|
|
mainAxisSize: MainAxisSize.min,
|
|
children: [
|
|
Icon(Icons.broken_image,
|
|
size: 50, color: Colors.grey),
|
|
SizedBox(height: 10),
|
|
Text("Gagal memuat gambar",
|
|
style: TextStyle(color: Colors.grey))
|
|
],
|
|
),
|
|
);
|
|
},
|
|
loadingBuilder: (context, child, loadingProgress) {
|
|
if (loadingProgress == null) return child;
|
|
return Center(
|
|
child: CircularProgressIndicator(
|
|
color: const Color(0xFF1D91AA),
|
|
value: loadingProgress.expectedTotalBytes !=
|
|
null
|
|
? loadingProgress.cumulativeBytesLoaded /
|
|
loadingProgress.expectedTotalBytes!
|
|
: null,
|
|
),
|
|
);
|
|
},
|
|
),
|
|
),
|
|
),
|
|
),
|
|
),
|
|
)
|
|
],
|
|
),
|
|
),
|
|
actions: <Widget>[
|
|
TextButton.icon(
|
|
icon: const Icon(Icons.download, size: 18),
|
|
label: const Text('Download'),
|
|
onPressed: () {
|
|
requestStoragePermission(imageUrl);
|
|
},
|
|
style: TextButton.styleFrom(
|
|
foregroundColor: const Color(0xFF1D91AA),
|
|
),
|
|
),
|
|
TextButton.icon(
|
|
icon: const Icon(Icons.close, size: 18),
|
|
label: const Text('Tutup'),
|
|
onPressed: () {
|
|
Navigator.of(context).pop();
|
|
},
|
|
style: TextButton.styleFrom(
|
|
foregroundColor: Colors.grey[700],
|
|
),
|
|
),
|
|
],
|
|
);
|
|
},
|
|
);
|
|
}
|
|
|
|
Future<List<QueryDocumentSnapshot<Map<String, dynamic>>>>
|
|
_getListLog() async {
|
|
Query<Map<String, dynamic>> query = FirebaseFirestore.instance
|
|
.collection("kandang_log")
|
|
.where("kandang_id", isEqualTo: widget.doc.id);
|
|
|
|
final snapshot = await query.get();
|
|
return snapshot.docs;
|
|
}
|
|
|
|
@override
|
|
Widget build(BuildContext context) {
|
|
return Scaffold(
|
|
backgroundColor: Colors.grey[50],
|
|
extendBodyBehindAppBar: true,
|
|
appBar: AppBar(
|
|
elevation: 0,
|
|
backgroundColor: Colors.transparent,
|
|
leading: Container(
|
|
margin: const EdgeInsets.all(8),
|
|
decoration: BoxDecoration(
|
|
color: Colors.white.withOpacity(0.9),
|
|
shape: BoxShape.circle,
|
|
boxShadow: [
|
|
BoxShadow(
|
|
color: Colors.black.withOpacity(0.1),
|
|
blurRadius: 8,
|
|
offset: const Offset(0, 2),
|
|
),
|
|
],
|
|
),
|
|
child: IconButton(
|
|
icon: const Icon(Icons.arrow_back, color: Color(0xFF1D91AA)),
|
|
onPressed: () {
|
|
Navigator.pop(context, 'refresh');
|
|
},
|
|
),
|
|
),
|
|
actions: [
|
|
Container(
|
|
margin: const EdgeInsets.all(8),
|
|
decoration: BoxDecoration(
|
|
color: Colors.white.withOpacity(0.9),
|
|
shape: BoxShape.circle,
|
|
boxShadow: [
|
|
BoxShadow(
|
|
color: Colors.black.withOpacity(0.1),
|
|
blurRadius: 8,
|
|
offset: const Offset(0, 2),
|
|
),
|
|
],
|
|
),
|
|
child: IconButton(
|
|
icon: const Icon(Icons.image, color: Color(0xFF1D91AA)),
|
|
onPressed: () {
|
|
_showImage();
|
|
},
|
|
),
|
|
),
|
|
Container(
|
|
margin: const EdgeInsets.all(8),
|
|
decoration: BoxDecoration(
|
|
color: Colors.white.withOpacity(0.9),
|
|
shape: BoxShape.circle,
|
|
boxShadow: [
|
|
BoxShadow(
|
|
color: Colors.black.withOpacity(0.1),
|
|
blurRadius: 8,
|
|
offset: const Offset(0, 2),
|
|
),
|
|
],
|
|
),
|
|
child: PopupMenuButton<String>(
|
|
icon: const Icon(Icons.more_vert, color: Color(0xFF1D91AA)),
|
|
shape: RoundedRectangleBorder(
|
|
borderRadius: BorderRadius.circular(12),
|
|
),
|
|
offset: const Offset(0, 40),
|
|
onSelected: (value) {
|
|
if (value == "delete") {
|
|
_showDeleteConfirmation(context);
|
|
}
|
|
|
|
if (value == "edit") {
|
|
Navigator.push<DocumentSnapshot<Map<String, dynamic>>>(
|
|
context,
|
|
MaterialPageRoute(
|
|
builder: (context) => CageEdit(
|
|
doc: doc,
|
|
user: widget.user,
|
|
),
|
|
),
|
|
).then((value) {
|
|
setState(() {
|
|
doc = value!;
|
|
getTotal();
|
|
});
|
|
});
|
|
}
|
|
},
|
|
itemBuilder: (context) => [
|
|
const PopupMenuItem(
|
|
value: "edit",
|
|
child: Row(
|
|
children: [
|
|
Icon(Icons.edit, color: Color(0xFF1D91AA), size: 20),
|
|
SizedBox(width: 12),
|
|
Text("Edit",
|
|
style: TextStyle(fontWeight: FontWeight.w500)),
|
|
],
|
|
),
|
|
),
|
|
const PopupMenuItem(
|
|
value: "delete",
|
|
child: Row(
|
|
children: [
|
|
Icon(Icons.delete, color: Colors.redAccent, size: 20),
|
|
SizedBox(width: 12),
|
|
Text("Hapus",
|
|
style: TextStyle(
|
|
fontWeight: FontWeight.w500,
|
|
color: Colors.redAccent)),
|
|
],
|
|
),
|
|
),
|
|
],
|
|
),
|
|
),
|
|
],
|
|
),
|
|
body: SingleChildScrollView(
|
|
physics: const BouncingScrollPhysics(),
|
|
child: Column(
|
|
children: [
|
|
// Header image with overlay
|
|
Stack(
|
|
children: [
|
|
SizedBox(
|
|
height: 280,
|
|
width: double.infinity,
|
|
child: FutureBuilder<Uint8List?>(
|
|
future: _getImage(),
|
|
builder: (context, snapshot) {
|
|
if (snapshot.connectionState == ConnectionState.waiting) {
|
|
return const Center(
|
|
child: CircularProgressIndicator(
|
|
color: Colors.white,
|
|
),
|
|
);
|
|
}
|
|
var image = snapshot.data;
|
|
if (image != null) {
|
|
return Image.memory(
|
|
image,
|
|
fit: BoxFit.cover,
|
|
height: double.infinity,
|
|
width: double.infinity,
|
|
);
|
|
}
|
|
return Image.asset(
|
|
"assets/images/Kandangr.png",
|
|
fit: BoxFit.cover,
|
|
height: double.infinity,
|
|
width: double.infinity,
|
|
);
|
|
},
|
|
),
|
|
),
|
|
// Gradient overlay for better text contrast
|
|
Positioned.fill(
|
|
child: Container(
|
|
decoration: BoxDecoration(
|
|
gradient: LinearGradient(
|
|
begin: Alignment.topCenter,
|
|
end: Alignment.bottomCenter,
|
|
colors: [
|
|
Colors.transparent,
|
|
Colors.black.withOpacity(0.7),
|
|
],
|
|
stops: const [0.5, 1.0],
|
|
),
|
|
),
|
|
),
|
|
),
|
|
// Title overlay at bottom of image
|
|
Positioned(
|
|
bottom: 20,
|
|
left: 20,
|
|
right: 20,
|
|
child: Column(
|
|
crossAxisAlignment: CrossAxisAlignment.start,
|
|
children: [
|
|
Text(
|
|
doc.data()?["nama"] ?? "",
|
|
style: const TextStyle(
|
|
fontWeight: FontWeight.bold,
|
|
fontSize: 28,
|
|
color: Colors.white,
|
|
shadows: [
|
|
Shadow(
|
|
offset: Offset(0, 1),
|
|
blurRadius: 3.0,
|
|
color: Color.fromARGB(150, 0, 0, 0),
|
|
),
|
|
],
|
|
),
|
|
),
|
|
const SizedBox(height: 8),
|
|
Row(
|
|
children: [
|
|
Container(
|
|
padding: const EdgeInsets.symmetric(
|
|
horizontal: 12, vertical: 6),
|
|
decoration: BoxDecoration(
|
|
color: const Color(0xFF1D91AA),
|
|
borderRadius: BorderRadius.circular(20),
|
|
),
|
|
child: Text(
|
|
doc.data()?["kategori"] ?? "",
|
|
style: const TextStyle(
|
|
color: Colors.white,
|
|
fontWeight: FontWeight.w600,
|
|
fontSize: 14,
|
|
),
|
|
),
|
|
),
|
|
const SizedBox(width: 10),
|
|
// Container(
|
|
// padding: const EdgeInsets.symmetric(
|
|
// horizontal: 12, vertical: 6),
|
|
// decoration: BoxDecoration(
|
|
// color: Colors.white.withOpacity(0.9),
|
|
// borderRadius: BorderRadius.circular(20),
|
|
// ),
|
|
// child: Row(
|
|
// children: [
|
|
// const Icon(
|
|
// Icons.calendar_today_rounded,
|
|
// size: 14,
|
|
// color: Color(0xFF1D91AA),
|
|
// ),
|
|
// const SizedBox(width: 6),
|
|
// Text(
|
|
// doc.data()?["tanggal_dibuat"] ?? "",
|
|
// style: const TextStyle(
|
|
// color: Color(0xFF1D91AA),
|
|
// fontWeight: FontWeight.w600,
|
|
// fontSize: 14,
|
|
// ),
|
|
// ),
|
|
// ],
|
|
// ),
|
|
// ),
|
|
],
|
|
),
|
|
],
|
|
),
|
|
),
|
|
],
|
|
),
|
|
|
|
//Date section
|
|
Container(
|
|
padding: const EdgeInsets.symmetric(vertical: 14, horizontal: 20),
|
|
decoration: BoxDecoration(
|
|
color: const Color(0xFF1D91AA).withOpacity(0.05),
|
|
border: Border(
|
|
bottom: BorderSide(
|
|
color: Colors.grey.withOpacity(0.15),
|
|
width: 1,
|
|
),
|
|
),
|
|
),
|
|
child: Row(
|
|
children: [
|
|
const Icon(
|
|
Icons.today_rounded,
|
|
size: 18,
|
|
color: Color(0xFF1D91AA),
|
|
),
|
|
const SizedBox(width: 8),
|
|
const Text(
|
|
"Tanggal Dibuat:",
|
|
style: TextStyle(
|
|
fontWeight: FontWeight.w500,
|
|
fontSize: 14,
|
|
color: Colors.grey,
|
|
),
|
|
),
|
|
const SizedBox(width: 8),
|
|
Text(
|
|
doc.data()?["tanggal_dibuat"] ?? "-",
|
|
style: const TextStyle(
|
|
fontWeight: FontWeight.w600,
|
|
fontSize: 14,
|
|
),
|
|
),
|
|
],
|
|
),
|
|
),
|
|
|
|
// Capacity info cards
|
|
Padding(
|
|
padding: const EdgeInsets.fromLTRB(20, 20, 20, 0),
|
|
child: Row(
|
|
children: [
|
|
// Left card
|
|
Expanded(
|
|
child: Container(
|
|
padding: const EdgeInsets.all(16),
|
|
decoration: BoxDecoration(
|
|
color: Colors.white,
|
|
borderRadius: BorderRadius.circular(16),
|
|
boxShadow: [
|
|
BoxShadow(
|
|
color: Colors.black.withOpacity(0.05),
|
|
blurRadius: 10,
|
|
spreadRadius: 0,
|
|
offset: const Offset(0, 2),
|
|
),
|
|
],
|
|
),
|
|
child: Column(
|
|
crossAxisAlignment: CrossAxisAlignment.start,
|
|
children: [
|
|
const Text(
|
|
"Jumlah Ternak",
|
|
style: TextStyle(
|
|
color: Colors.grey,
|
|
fontSize: 14,
|
|
fontWeight: FontWeight.w500,
|
|
),
|
|
),
|
|
const SizedBox(height: 8),
|
|
Row(
|
|
children: [
|
|
// Container(
|
|
// padding: const EdgeInsets.all(8),
|
|
// decoration: BoxDecoration(
|
|
// color:
|
|
// const Color(0xFF1D91AA).withOpacity(0.1),
|
|
// borderRadius: BorderRadius.circular(8),
|
|
// ),
|
|
// // child: const Icon(
|
|
// Icons.pets,
|
|
// // size: 20,
|
|
// // color: Color(0xFF1D91AA),
|
|
// // ),
|
|
// ),
|
|
const SizedBox(width: 12),
|
|
Text(
|
|
"${widget.total}/${doc.data()?["kapasitas"] ?? ""} Ekor",
|
|
style: const TextStyle(
|
|
fontWeight: FontWeight.bold,
|
|
fontSize: 18,
|
|
color: Color(0xFF1D91AA),
|
|
),
|
|
),
|
|
],
|
|
),
|
|
],
|
|
),
|
|
),
|
|
),
|
|
const SizedBox(width: 16),
|
|
// Right card
|
|
Expanded(
|
|
child: Container(
|
|
padding: const EdgeInsets.all(16),
|
|
decoration: BoxDecoration(
|
|
color: Colors.white,
|
|
borderRadius: BorderRadius.circular(16),
|
|
boxShadow: [
|
|
BoxShadow(
|
|
color: Colors.black.withOpacity(0.05),
|
|
blurRadius: 10,
|
|
spreadRadius: 0,
|
|
offset: const Offset(0, 2),
|
|
),
|
|
],
|
|
),
|
|
child: Column(
|
|
crossAxisAlignment: CrossAxisAlignment.start,
|
|
children: [
|
|
const Text(
|
|
"Kapasitas Tersisa",
|
|
style: TextStyle(
|
|
color: Colors.grey,
|
|
fontSize: 14,
|
|
fontWeight: FontWeight.w500,
|
|
),
|
|
),
|
|
const SizedBox(height: 8),
|
|
Row(
|
|
children: [
|
|
// Container(
|
|
// padding: const EdgeInsets.all(8),
|
|
// decoration: BoxDecoration(
|
|
// color:
|
|
// const Color.fromRGBO(189, 148, 26, 0.1),
|
|
// borderRadius: BorderRadius.circular(8),
|
|
// ),
|
|
// child: const Icon(
|
|
// Icons.add_circle_outline_rounded,
|
|
// size: 20,
|
|
// color: Color.fromRGBO(189, 148, 26, 1),
|
|
// ),
|
|
// ),
|
|
const SizedBox(width: 12),
|
|
Text(
|
|
"${canUse.toString()} Ekor",
|
|
style: const TextStyle(
|
|
fontWeight: FontWeight.bold,
|
|
fontSize: 18,
|
|
color: Color.fromRGBO(189, 148, 26, 1),
|
|
),
|
|
),
|
|
],
|
|
),
|
|
],
|
|
),
|
|
),
|
|
),
|
|
],
|
|
),
|
|
),
|
|
|
|
// Block details card
|
|
Container(
|
|
margin: const EdgeInsets.all(20),
|
|
decoration: BoxDecoration(
|
|
color: Colors.white,
|
|
borderRadius: BorderRadius.circular(16),
|
|
boxShadow: [
|
|
BoxShadow(
|
|
color: Colors.black.withOpacity(0.05),
|
|
blurRadius: 10,
|
|
spreadRadius: 0,
|
|
offset: const Offset(0, 2),
|
|
),
|
|
],
|
|
),
|
|
child: Column(
|
|
children: [
|
|
Padding(
|
|
padding: const EdgeInsets.all(20),
|
|
child: Row(
|
|
children: [
|
|
Expanded(
|
|
child: Row(
|
|
children: [
|
|
Container(
|
|
padding: const EdgeInsets.all(10),
|
|
decoration: BoxDecoration(
|
|
color: const Color(0xFF1D91AA).withOpacity(0.1),
|
|
borderRadius: BorderRadius.circular(12),
|
|
),
|
|
child: const Icon(
|
|
Icons.dashboard_rounded,
|
|
color: Color(0xFF1D91AA),
|
|
size: 22,
|
|
),
|
|
),
|
|
const SizedBox(width: 16),
|
|
Column(
|
|
crossAxisAlignment: CrossAxisAlignment.start,
|
|
children: [
|
|
// const Text(
|
|
// "Lihat detail blok",
|
|
// style: TextStyle(
|
|
// fontSize: 14,
|
|
// fontWeight: FontWeight.w500,
|
|
// ),
|
|
//),
|
|
const Text(
|
|
"Detail Blok",
|
|
style: TextStyle(
|
|
fontSize: 18,
|
|
fontWeight: FontWeight.bold,
|
|
color: Color(0xFF1D91AA),
|
|
),
|
|
),
|
|
const SizedBox(height: 4),
|
|
Text(
|
|
"Terdapat $totalBlok blok ",
|
|
style: TextStyle(
|
|
fontSize: 12,
|
|
color: Colors.grey[600],
|
|
),
|
|
),
|
|
],
|
|
),
|
|
],
|
|
),
|
|
),
|
|
ElevatedButton(
|
|
onPressed: () {
|
|
Navigator.push(
|
|
context,
|
|
MaterialPageRoute(
|
|
builder: (context) => BlockDetail(
|
|
kandangId: doc.id,
|
|
total: widget.total,
|
|
totalBlok: totalBlok.toString(),
|
|
),
|
|
),
|
|
).then((value) => setState(() {
|
|
doc = value!;
|
|
getTotal();
|
|
}));
|
|
},
|
|
style: ElevatedButton.styleFrom(
|
|
backgroundColor: const Color(0xFF1D91AA),
|
|
foregroundColor: Colors.white,
|
|
elevation: 2,
|
|
shadowColor:
|
|
const Color(0xFF1D91AA).withOpacity(0.3),
|
|
shape: RoundedRectangleBorder(
|
|
borderRadius: BorderRadius.circular(12),
|
|
),
|
|
padding: const EdgeInsets.symmetric(
|
|
horizontal: 18, vertical: 10),
|
|
),
|
|
child: Row(
|
|
mainAxisSize: MainAxisSize.min,
|
|
children: const [
|
|
Text(
|
|
"Lihat Blok",
|
|
style: TextStyle(
|
|
fontWeight: FontWeight.bold,
|
|
),
|
|
),
|
|
SizedBox(width: 6),
|
|
Icon(
|
|
Icons.arrow_forward,
|
|
size: 16,
|
|
),
|
|
],
|
|
),
|
|
),
|
|
],
|
|
),
|
|
),
|
|
],
|
|
),
|
|
),
|
|
|
|
// Edit history card
|
|
Container(
|
|
margin: const EdgeInsets.fromLTRB(20, 0, 20, 20),
|
|
decoration: BoxDecoration(
|
|
color: Colors.white,
|
|
borderRadius: BorderRadius.circular(16),
|
|
boxShadow: [
|
|
BoxShadow(
|
|
color: Colors.black.withOpacity(0.05),
|
|
blurRadius: 10,
|
|
spreadRadius: 0,
|
|
offset: const Offset(0, 2),
|
|
),
|
|
],
|
|
),
|
|
child: Column(
|
|
crossAxisAlignment: CrossAxisAlignment.start,
|
|
children: [
|
|
// Header
|
|
Padding(
|
|
padding: const EdgeInsets.all(20),
|
|
child: Row(
|
|
children: [
|
|
Container(
|
|
padding: const EdgeInsets.all(12),
|
|
decoration: BoxDecoration(
|
|
color: const Color(0xFF1D91AA).withOpacity(0.1),
|
|
borderRadius: BorderRadius.circular(14),
|
|
),
|
|
child: const Icon(
|
|
Icons.history_rounded,
|
|
color: Color(0xFF1D91AA),
|
|
size: 24,
|
|
),
|
|
),
|
|
const SizedBox(width: 16),
|
|
const Expanded(
|
|
child: Column(
|
|
crossAxisAlignment: CrossAxisAlignment.start,
|
|
children: [
|
|
Text(
|
|
"Riwayat Edit",
|
|
style: TextStyle(
|
|
fontSize: 18,
|
|
fontWeight: FontWeight.bold,
|
|
color: Color(0xFF1D91AA),
|
|
),
|
|
),
|
|
SizedBox(height: 4),
|
|
Text(
|
|
"Daftar perubahan pada kandang",
|
|
style: TextStyle(
|
|
fontSize: 14,
|
|
color: Colors.grey,
|
|
),
|
|
),
|
|
],
|
|
),
|
|
),
|
|
],
|
|
),
|
|
),
|
|
|
|
// Divider
|
|
Divider(
|
|
height: 1,
|
|
thickness: 1,
|
|
color: Colors.grey.withOpacity(0.1),
|
|
),
|
|
|
|
// Content
|
|
FutureBuilder(
|
|
future: _getListLog(),
|
|
builder: (context, snapshot) {
|
|
if (snapshot.connectionState == ConnectionState.waiting) {
|
|
return Container(
|
|
height: 150,
|
|
alignment: Alignment.center,
|
|
child: const CircularProgressIndicator(
|
|
color: Color(0xFF1D91AA),
|
|
strokeWidth: 3,
|
|
),
|
|
);
|
|
}
|
|
|
|
if (snapshot.data == null || snapshot.data!.isEmpty) {
|
|
return Container(
|
|
height: 180,
|
|
alignment: Alignment.center,
|
|
child: Column(
|
|
mainAxisAlignment: MainAxisAlignment.center,
|
|
children: [
|
|
Container(
|
|
padding: const EdgeInsets.all(16),
|
|
decoration: BoxDecoration(
|
|
color: Colors.grey.withOpacity(0.1),
|
|
shape: BoxShape.circle,
|
|
),
|
|
child: Icon(
|
|
Icons.history_toggle_off_rounded,
|
|
size: 40,
|
|
color: Colors.grey[500],
|
|
),
|
|
),
|
|
const SizedBox(height: 16),
|
|
Text(
|
|
"Belum ada riwayat perubahan",
|
|
style: TextStyle(
|
|
fontSize: 16,
|
|
fontWeight: FontWeight.w500,
|
|
color: Colors.grey[600],
|
|
),
|
|
),
|
|
const SizedBox(height: 8),
|
|
Text(
|
|
"Perubahan pada kandang akan muncul di sini",
|
|
style: TextStyle(
|
|
fontSize: 14,
|
|
color: Colors.grey[500],
|
|
),
|
|
),
|
|
],
|
|
),
|
|
);
|
|
}
|
|
|
|
// Reverse the list to show newest edits first
|
|
var dataList = snapshot.data?.toList() ?? [];
|
|
// Sort the list by date, most recent first dataList.sort((a, b) { // Parse date strings to ensure proper date comparison var aDateStr = a.data()["created_at"] ?? ''; var bDateStr = b.data()["created_at"] ?? ''; // Try to parse dates if possible (format: dd/MM/yyyy) DateTime? aDate; DateTime? bDate; try { var aParts = aDateStr.split('/'); if (aParts.length == 3) { aDate = DateTime( int.parse(aParts[2]), // year int.parse(aParts[1]), // month int.parse(aParts[0]), // day ); } } catch (e) { // Fallback to string comparison if parsing fails } try { var bParts = bDateStr.split('/'); if (bParts.length == 3) { bDate = DateTime( int.parse(bParts[2]), // year int.parse(bParts[1]), // month int.parse(bParts[0]), // day ); } } catch (e) { // Fallback to string comparison if parsing fails } // Compare dates if both were parsed successfully if (aDate != null && bDate != null) { return bDate.compareTo(aDate); // Newest first } // Fallback to string comparison return bDateStr.compareTo(aDateStr); // Reverse order: newest first });
|
|
|
|
var data = dataList.map(
|
|
(el) {
|
|
var e = el.data();
|
|
return _buildHistoryItem(
|
|
iconName: e["icon"] ?? '',
|
|
title: e["title"] ?? '',
|
|
description: e["text"] ?? '',
|
|
date: e["created_at"] ?? '',
|
|
iconColor: const Color(0xFF1D91AA),
|
|
showDivider: el != dataList.last,
|
|
);
|
|
},
|
|
).toList();
|
|
|
|
return Column(
|
|
children: [
|
|
Container(
|
|
height: 300,
|
|
padding: const EdgeInsets.fromLTRB(20, 5, 20, 0),
|
|
decoration: BoxDecoration(
|
|
borderRadius: BorderRadius.circular(12),
|
|
),
|
|
child: RawScrollbar(
|
|
thumbColor: const Color(0xFF1D91AA).withOpacity(0.2),
|
|
radius: const Radius.circular(20),
|
|
thickness: 5,
|
|
thumbVisibility: true,
|
|
child: ListView(
|
|
padding: EdgeInsets.zero,
|
|
shrinkWrap: true,
|
|
physics: const BouncingScrollPhysics(),
|
|
children: data ?? [],
|
|
),
|
|
),
|
|
),
|
|
// Scroll indicator
|
|
Padding(
|
|
padding: const EdgeInsets.only(top: 8, bottom: 12),
|
|
child: Row(
|
|
mainAxisAlignment: MainAxisAlignment.center,
|
|
children: [
|
|
Icon(
|
|
Icons.keyboard_arrow_up,
|
|
size: 16,
|
|
color: Colors.grey[400],
|
|
),
|
|
const SizedBox(width: 4),
|
|
Text(
|
|
"Scroll untuk melihat lebih banyak",
|
|
style: TextStyle(
|
|
fontSize: 12,
|
|
color: Colors.grey[500],
|
|
),
|
|
),
|
|
const SizedBox(width: 4),
|
|
Icon(
|
|
Icons.keyboard_arrow_down,
|
|
size: 16,
|
|
color: Colors.grey[400],
|
|
),
|
|
],
|
|
),
|
|
),
|
|
],
|
|
);
|
|
},
|
|
),
|
|
],
|
|
),
|
|
),
|
|
],
|
|
),
|
|
),
|
|
);
|
|
}
|
|
|
|
Widget _buildHistoryItem({
|
|
required String iconName,
|
|
required String title,
|
|
required String description,
|
|
required String date,
|
|
required Color iconColor,
|
|
bool showDivider = true,
|
|
}) {
|
|
var icon = Icons.pets_rounded;
|
|
switch (iconName) {
|
|
case 'edit_rounded':
|
|
icon = Icons.edit_rounded;
|
|
case 'category_rounded':
|
|
icon = Icons.edit_rounded;
|
|
}
|
|
return Column(
|
|
children: [
|
|
Padding(
|
|
padding: const EdgeInsets.symmetric(vertical: 12),
|
|
child: Row(
|
|
crossAxisAlignment: CrossAxisAlignment.start,
|
|
children: [
|
|
Container(
|
|
padding: const EdgeInsets.all(10),
|
|
decoration: BoxDecoration(
|
|
color: iconColor.withOpacity(0.1),
|
|
borderRadius: BorderRadius.circular(12),
|
|
),
|
|
child: Icon(
|
|
icon,
|
|
size: 20,
|
|
color: iconColor,
|
|
),
|
|
),
|
|
const SizedBox(width: 16),
|
|
Expanded(
|
|
child: Column(
|
|
crossAxisAlignment: CrossAxisAlignment.start,
|
|
children: [
|
|
Row(
|
|
mainAxisAlignment: MainAxisAlignment.spaceBetween,
|
|
children: [
|
|
Text(
|
|
title,
|
|
style: const TextStyle(
|
|
fontWeight: FontWeight.bold,
|
|
fontSize: 16,
|
|
),
|
|
),
|
|
Container(
|
|
padding: const EdgeInsets.symmetric(horizontal: 10, vertical: 4),
|
|
decoration: BoxDecoration(
|
|
color: Colors.grey[100],
|
|
borderRadius: BorderRadius.circular(12),
|
|
),
|
|
child: Text(
|
|
date,
|
|
style: TextStyle(
|
|
fontSize: 12,
|
|
fontWeight: FontWeight.w500,
|
|
color: Colors.grey[700],
|
|
),
|
|
),
|
|
),
|
|
],
|
|
),
|
|
const SizedBox(height: 8),
|
|
Text(
|
|
description,
|
|
style: TextStyle(
|
|
fontSize: 14,
|
|
height: 1.4,
|
|
color: Colors.grey[800],
|
|
),
|
|
),
|
|
],
|
|
),
|
|
),
|
|
],
|
|
),
|
|
),
|
|
if (showDivider)
|
|
const Divider(),
|
|
],
|
|
);
|
|
}
|
|
}
|