MIF_E31222451/lib/animal_detail.dart

1630 lines
61 KiB
Dart

import 'dart:io';
import 'dart:typed_data';
import 'dart:ui';
import 'package:cloud_firestore/cloud_firestore.dart';
import 'package:firebase_auth/firebase_auth.dart' as auth;
import 'package:flutter/material.dart';
import 'package:flutter/rendering.dart';
import 'package:intl/intl.dart';
import 'package:qr_flutter/qr_flutter.dart';
import 'package:permission_handler/permission_handler.dart';
import 'package:image_gallery_saver/image_gallery_saver.dart';
import 'package:path_provider/path_provider.dart';
import 'package:supabase_flutter/supabase_flutter.dart';
import 'package:ternak/animal_edit.dart';
import 'package:ternak/components/riwayat_kesehatan.dart';
import 'package:ternak/components/riwayat_penimbangan.dart';
import 'package:font_awesome_flutter/font_awesome_flutter.dart';
class AnimalDetail extends StatefulWidget {
final auth.User user;
final DocumentSnapshot<Map<String, dynamic>> doc;
const AnimalDetail({super.key, required this.doc, required this.user});
@override
State<AnimalDetail> createState() => _AnimalDetailState();
}
class _AnimalDetailState extends State<AnimalDetail> {
late DocumentSnapshot<Map<String, dynamic>> doc;
final GlobalKey _globalKey = GlobalKey();
TextEditingController _textController = TextEditingController();
final TextEditingController _tanggalBobotController = TextEditingController();
final TextEditingController _textStatusController = TextEditingController();
final TextEditingController _textStatus_kesehatanController =
TextEditingController();
late bool status = true;
bool _readOnlyFinalWeight = true;
bool _readOnlyStatus = true;
bool _readOnlyKondisi = true;
IconData _icon = Icons.rebase_edit;
IconData _iconKondisi = Icons.rebase_edit;
IconData _iconStatus = Icons.rebase_edit;
final _formKey = GlobalKey<FormState>();
Future<void> requestStoragePermission({required String type}) 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.");
if (type == "image") {
_captureAndSave();
}
if (type == "qrcode") {
_captureAndSaveQrCode();
}
} 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.id}.jpg';
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')));
}
Future<void> _captureAndSaveQrCode() 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/${doc.id}.jpg';
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('QR Code saved to gallery')));
}
Future<QuerySnapshot<Map<String, dynamic>>> _getHealthyData() {
return FirebaseFirestore.instance
.collection("kesehatan")
.where("hewan_id", isEqualTo: doc.id)
.orderBy("tanggal", descending: true)
.get();
}
Future<QuerySnapshot<Map<String, dynamic>>> _getWeightData() {
return FirebaseFirestore.instance
.collection("bobot")
.where("hewan_id", isEqualTo: doc.id)
.orderBy("tanggal", descending: true)
.get();
}
Future<QuerySnapshot<Map<String, dynamic>>> _getEditHistory() {
return FirebaseFirestore.instance
.collection("riwayat_edit")
.where("hewan_id", isEqualTo: doc.id)
.orderBy("tanggal", descending: true)
.get();
}
void _showQrcode() {
showDialog(
context: context,
barrierDismissible: false,
builder: (BuildContext context) {
return AlertDialog(
shape: RoundedRectangleBorder(
borderRadius: BorderRadius.circular(16),
),
title: const Text(
"QR Code Hewan",
style: TextStyle(
fontWeight: FontWeight.bold,
color: Color(0xFF1D91AA),
),
),
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(
padding: const EdgeInsets.all(16),
decoration: BoxDecoration(
color: Colors.white,
borderRadius: BorderRadius.circular(12),
border:
Border.all(color: Colors.grey.withOpacity(0.2)),
),
child: QrImageView(
data: doc.id,
size: 300,
version: QrVersions.auto,
backgroundColor: Colors.white,
foregroundColor: Color.fromRGBO(0, 0, 0, 1),
),
),
),
),
)
],
),
),
actions: <Widget>[
TextButton(
child: const Text(
'Download',
style: TextStyle(
color: Color(0xFF1D91AA),
fontWeight: FontWeight.bold,
),
),
onPressed: () {
requestStoragePermission(type: "qrcode");
},
),
ElevatedButton(
style: ElevatedButton.styleFrom(
backgroundColor: const Color(0xFF1D91AA),
foregroundColor: Colors.white,
shape: RoundedRectangleBorder(
borderRadius: BorderRadius.circular(8),
),
),
child: const Text('Tutup'),
onPressed: () {
Navigator.of(context).pop();
},
),
],
);
},
);
}
@override
void initState() {
super.initState();
doc = widget.doc;
status = doc.data()?['status'] == "Hidup";
_textController =
TextEditingController(text: doc.data()?["bobot_akhir"] ?? "");
}
void _showDeleteConfirmation(BuildContext context) {
showDialog(
context: context,
builder: (BuildContext context) {
return AlertDialog(
shape: RoundedRectangleBorder(
borderRadius: BorderRadius.circular(16),
),
title: const Text(
"Konfirmasi Hapus",
style: TextStyle(
fontWeight: FontWeight.bold,
color: Color(0xFF1D91AA),
),
),
content: const Text(
"Apakah kamu yakin ingin menghapus data hewan ini?",
style: TextStyle(fontSize: 16),
),
actions: [
TextButton(
onPressed: () => Navigator.of(context).pop(),
child: const Text(
"Batal",
style: TextStyle(
color: Colors.grey,
fontWeight: FontWeight.bold,
),
),
),
ElevatedButton(
style: ElevatedButton.styleFrom(
backgroundColor: Colors.red,
foregroundColor: Colors.white,
shape: RoundedRectangleBorder(
borderRadius: BorderRadius.circular(8),
),
padding:
const EdgeInsets.symmetric(horizontal: 16, vertical: 10),
),
onPressed: () {
FirebaseFirestore.instance
.collection("hewan")
.doc(doc.id)
.delete()
.then((value) {
Navigator.of(context).pop();
Navigator.of(context).pop();
ScaffoldMessenger.of(context).showSnackBar(
const SnackBar(
backgroundColor: Colors.green,
content: Text("Berhasil dihapus"),
),
);
}).catchError((value) {
Navigator.of(context).pop();
ScaffoldMessenger.of(context).showSnackBar(
const SnackBar(
backgroundColor: Colors.red,
content: Text("Gagal menghapus"),
),
);
});
},
child: const Text("Hapus"),
),
],
);
},
);
}
void _showImage() async {
String imageUrl = Supabase.instance.client.storage
.from('terdom')
.getPublicUrl("hewan/${widget.doc.id}.jpg");
print("DEBUG:: $imageUrl");
showDialog(
context: context,
barrierDismissible: false,
builder: (BuildContext context) {
return AlertDialog(
shape: RoundedRectangleBorder(
borderRadius: BorderRadius.circular(16),
),
title: const Text(
"Foto Hewan",
style: TextStyle(
fontWeight: FontWeight.bold,
color: Color(0xFF1D91AA),
),
),
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(
decoration: BoxDecoration(
color: Colors.white,
borderRadius: BorderRadius.circular(12),
boxShadow: [
BoxShadow(
color: Colors.black.withOpacity(0.1),
blurRadius: 10,
spreadRadius: 0,
),
],
),
clipBehavior: Clip.hardEdge,
child: Image.network(
imageUrl,
fit: BoxFit.cover,
errorBuilder: (context, error, stackTrace) {
return Container(
width: double.infinity,
height: 250,
color: Colors.grey[200],
child: Column(
mainAxisAlignment: MainAxisAlignment.center,
children: [
Icon(
Icons.broken_image,
size: 50,
color: Colors.grey[400],
),
const SizedBox(height: 16),
Text(
"Gambar tidak tersedia",
style: TextStyle(
color: Colors.grey[600],
fontSize: 16,
),
),
],
),
);
},
loadingBuilder: (context, child, loadingProgress) {
if (loadingProgress == null) return child;
return Container(
width: double.infinity,
height: 250,
color: Colors.grey[100],
child: Center(
child: CircularProgressIndicator(
color: const Color(0xFF1D91AA),
value: loadingProgress.expectedTotalBytes !=
null
? loadingProgress.cumulativeBytesLoaded /
loadingProgress.expectedTotalBytes!
: null,
),
),
);
},
),
),
),
),
)
],
),
),
actions: <Widget>[
TextButton(
onPressed: () {
requestStoragePermission(type: "image");
},
child: const Text(
'Download',
style: TextStyle(
color: Color(0xFF1D91AA),
fontWeight: FontWeight.bold,
),
),
),
ElevatedButton(
style: ElevatedButton.styleFrom(
backgroundColor: const Color(0xFF1D91AA),
foregroundColor: Colors.white,
shape: RoundedRectangleBorder(
borderRadius: BorderRadius.circular(8),
),
),
child: const Text('Tutup'),
onPressed: () {
Navigator.of(context).pop();
},
),
],
);
},
);
}
@override
Widget build(BuildContext context) {
var dropdownStatus = <String>['Hidup', 'Mati', 'Terjual'];
var dropdownStatus_kesehatan = <String>['Sehat', 'Sakit'];
return Scaffold(
backgroundColor: Colors.grey[50],
appBar: AppBar(
backgroundColor: const Color(0xFF1D91AA),
elevation: 0,
title: const Text(
"Detail Hewan",
style: TextStyle(
fontSize: 18,
fontWeight: FontWeight.w700,
color: Colors.white,
),
),
leading: IconButton(
onPressed: () {
Navigator.pop(context);
},
icon: const Icon(Icons.arrow_back_ios_new_rounded),
color: Colors.white,
),
actions: [
IconButton(
icon: const Icon(
Icons.qr_code,
size: 24,
color: Colors.white,
),
onPressed: () {
_showQrcode();
},
),
],
),
body: ListView(
children: [
// Header with sheep info
Container(
decoration: BoxDecoration(
color: Colors.white,
borderRadius: const BorderRadius.only(
bottomLeft: Radius.circular(20),
bottomRight: Radius.circular(20),
),
boxShadow: [
BoxShadow(
color: Colors.black.withOpacity(0.05),
blurRadius: 5,
spreadRadius: 0,
offset: const Offset(0, 2),
),
],
),
padding: const EdgeInsets.fromLTRB(10, 10, 10, 15),
child: Column(
children: [
// Add sheep name as a centered header
const SizedBox(height: 10),
// Gender and health status with menu aligned
Row(
mainAxisAlignment: MainAxisAlignment.center,
children: [
// Gender indicator
Container(
padding: const EdgeInsets.symmetric(
horizontal: 10, vertical: 6),
decoration: BoxDecoration(
color: const Color(0xFF1D91AA).withOpacity(0.1),
borderRadius: BorderRadius.circular(20),
),
child: Row(
children: [
Icon(
(doc.data()?["jenis_kelamin"]) == "Jantan"
? Icons.male
: Icons.female,
color: (doc.data()?["jenis_kelamin"]) == "Jantan"
? Colors.lightBlue
: Colors.pink,
size: 18,
),
const SizedBox(width: 8),
Text(
doc.data()?["jenis_kelamin"] ?? "",
style: TextStyle(
fontSize: 12,
fontWeight: FontWeight.w600,
color: Colors.grey[800],
),
),
],
),
),
const SizedBox(width: 12),
// Health status
if (doc.data()?["status_kesehatan"] != null)
Container(
padding: const EdgeInsets.symmetric(
horizontal: 10, vertical: 6),
decoration: BoxDecoration(
color: doc.data()?["status_kesehatan"] == "Sehat"
? Colors.green.withOpacity(0.1)
: Colors.red.withOpacity(0.1),
borderRadius: BorderRadius.circular(20),
),
child: Row(
children: [
Icon(
doc.data()?["status_kesehatan"] == "Sehat"
? Icons.favorite
: Icons.healing,
color: doc.data()?["status_kesehatan"] == "Sehat"
? Colors.green
: Colors.red,
size: 18,
),
const SizedBox(width: 12),
Text(
doc.data()?["status_kesehatan"] ?? "",
style: TextStyle(
fontSize: 12,
fontWeight: FontWeight.w600,
color: Colors.grey[800],
),
),
],
),
),
const SizedBox(width: 12),
// Animal status (alive, sold, etc)
Container(
padding: const EdgeInsets.symmetric(
horizontal: 10, vertical: 6),
decoration: BoxDecoration(
color: _getStatusBackgroundColor(doc.data()?["status"]),
borderRadius: BorderRadius.circular(20),
),
child: Row(
children: [
Icon(
_getStatusIcon(doc.data()?["status"]),
color: _getStatusIconColor(doc.data()?["status"]),
size: 18,
),
const SizedBox(width: 12),
Text(
doc.data()?["status"] ?? "",
style: TextStyle(
fontSize: 12,
fontWeight: FontWeight.w600,
color: Colors.grey[800],
),
),
],
),
),
const SizedBox(width: 20),
// Action menu aligned with status indicators
Container(
padding: const EdgeInsets.all(4),
// decoration: BoxDecoration(
// color: const Color(0xFF1D91AA).withOpacity(0.1),
// borderRadius: BorderRadius.circular(20),
// ),
child: PopupMenuButton<String>(
padding: EdgeInsets.zero,
icon: const Icon(
Icons.more_vert,
color: Color(0xFF1D91AA),
size: 24,
),
onSelected: (value) {
if (value == "edit") {
Navigator.push<
DocumentSnapshot<Map<String, dynamic>>>(
context,
MaterialPageRoute(
builder: (context) => AnimalEdit(
doc: doc,
user: widget.user,
)),
).then((value) {
setState(() {
doc = value!;
});
});
} else if (value == "delete") {
_showDeleteConfirmation(context);
}
},
itemBuilder: (context) => [
PopupMenuItem(
enabled: status ? true : false,
value: "edit",
child: const Row(
children: [
Icon(Icons.edit, size: 18),
SizedBox(width: 8),
Text("Edit"),
],
),
),
const PopupMenuItem(
value: "delete",
child: Row(
children: [
Icon(Icons.delete, size: 18, color: Colors.red),
SizedBox(width: 8),
Text("Hapus",
style: TextStyle(color: Colors.red)),
],
),
),
],
),
),
],
),
const SizedBox(height: 8),
// Image view button
GestureDetector(
onTap: () => _showImage(),
child: Container(
width: double.infinity,
padding: const EdgeInsets.symmetric(vertical: 10),
margin: const EdgeInsets.symmetric(horizontal: 30),
decoration: BoxDecoration(
color: const Color(0xFF1D91AA),
borderRadius: BorderRadius.circular(12),
boxShadow: [
BoxShadow(
color: Colors.black.withOpacity(0.1),
blurRadius: 4,
offset: const Offset(0, 2),
),
],
),
child: const Row(
mainAxisAlignment: MainAxisAlignment.center,
children: [
Icon(
Icons.camera_alt,
size: 16,
color: Colors.white,
),
SizedBox(width: 8),
Text(
"Lihat Foto Hewan",
style: TextStyle(
color: Colors.white,
fontWeight: FontWeight.w500,
fontSize: 14,
),
),
],
),
),
),
],
),
),
const SizedBox(height: 5),
// Location info in a more compact card
Container(
margin: const EdgeInsets.symmetric(horizontal: 20),
padding: const EdgeInsets.all(15),
decoration: BoxDecoration(
color: Colors.white,
borderRadius: BorderRadius.circular(12),
boxShadow: [
BoxShadow(
color: Colors.black.withOpacity(0.05),
blurRadius: 5,
spreadRadius: 0,
offset: const Offset(0, 2),
),
],
),
child: Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
const Text(
"Lokasi Hewan",
style: TextStyle(
fontWeight: FontWeight.bold,
fontSize: 15,
),
),
const SizedBox(height: 10),
Row(
mainAxisAlignment: MainAxisAlignment.spaceEvenly,
children: [
// Making location info more compact
locationTile(
text: "Kandang",
icon: Icons.cabin,
value: doc.data()?["kandang"] ?? "",
),
const SizedBox(width: 20),
locationTile(
text: "Blok",
icon: Icons.account_tree_outlined,
value: doc.data()?["blok"] ?? "",
),
],
),
],
),
),
dash(),
Container(
padding: const EdgeInsets.symmetric(horizontal: 20),
child: Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
const Row(
children: [
Icon(
Icons.info_outline,
color: Color(0xFF1D91AA),
size: 22,
),
SizedBox(width: 8),
Text(
"Informasi Hewan",
style: TextStyle(
fontWeight: FontWeight.w700,
fontSize: 18,
color: Color(0xFF1D91AA),
),
),
],
),
const SizedBox(height: 10),
// Date
Container(
padding: const EdgeInsets.symmetric(vertical: 8),
child: Row(
children: [
const Icon(
Icons.calendar_today,
size: 16,
color: Colors.grey,
),
const SizedBox(width: 6),
Text(
"Tanggal Masuk: ${doc.data()?["tanggal_masuk"] ?? "-"}",
style: TextStyle(
fontSize: 14,
color: Colors.grey[600],
),
),
],
),
),
const SizedBox(height: 10),
// Information Cards
Container(
padding: const EdgeInsets.all(16),
decoration: BoxDecoration(
color: Colors.white,
borderRadius: BorderRadius.circular(12),
boxShadow: [
BoxShadow(
color: Colors.black.withOpacity(0.05),
blurRadius: 10,
spreadRadius: 0,
offset: const Offset(0, 2),
),
],
),
child: Column(
children: [
// Name
_buildInfoRow(
icon: FontAwesomeIcons.paw,
label: "Nama",
value: doc.data()?["nama"] ?? "-"),
const Divider(height: 20),
// Age
_buildInfoRow(
icon: Icons.access_time,
label: "Usia",
value: doc.data()?["usia"] ?? "-"),
const Divider(height: 20),
// Type
_buildInfoRow(
icon: Icons.category,
label: "Jenis Hewan",
value: doc.data()?["jenis"] ?? "-"),
const Divider(height: 20),
// Category
_buildInfoRow(
icon: Icons.layers,
label: "Kategori Hewan",
value: doc.data()?["kategori"] ?? "-"),
const Divider(height: 20),
// Health Condition Dropdown
Row(
children: [
Container(
height: 36,
width: 36,
decoration: BoxDecoration(
color: const Color(0xFF1D91AA).withOpacity(0.1),
borderRadius: BorderRadius.circular(8),
),
child: const Icon(
Icons.healing,
color: Color(0xFF1D91AA),
size: 20,
),
),
const SizedBox(width: 12),
Expanded(
child: formDropdown(
iconSuffix: GestureDetector(
onTap: () {
if (_iconKondisi == Icons.save_rounded) {
FirebaseFirestore.instance
.collection("hewan")
.doc(doc.id)
.update({
"status_kesehatan":
_textStatus_kesehatanController.text
});
}
setState(() {
_readOnlyKondisi = !_readOnlyKondisi;
_iconKondisi = _readOnlyKondisi
? Icons.rebase_edit
: Icons.save_rounded;
});
},
child: Icon(_iconKondisi),
),
text: "Kondisi Kesehatan",
dropdown: dropdownStatus_kesehatan,
textController: _textStatus_kesehatanController,
readOnly: _readOnlyKondisi,
value: doc.data()?["status_kesehatan"] ?? "",
),
),
],
),
const Divider(height: 20),
// Status Dropdown
Row(
children: [
Container(
height: 36,
width: 36,
decoration: BoxDecoration(
color: const Color(0xFF1D91AA).withOpacity(0.1),
borderRadius: BorderRadius.circular(8),
),
child: const Icon(
Icons.insights,
color: Color(0xFF1D91AA),
size: 20,
),
),
const SizedBox(width: 12),
Expanded(
child: formDropdown(
iconSuffix: GestureDetector(
onTap: () async {
if (_iconStatus == Icons.save_rounded) {
await FirebaseFirestore.instance
.collection("hewan")
.doc(doc.id)
.update({
"status": _textStatusController.text
});
doc.data()?["status"] =
_textStatusController.text;
status =
_textStatusController.text == "Hidup";
}
setState(() {
_readOnlyStatus = !_readOnlyStatus;
_iconStatus = _readOnlyStatus
? Icons.rebase_edit
: Icons.save_rounded;
});
},
child: Icon(_iconStatus),
),
text: "Status",
dropdown: dropdownStatus,
textController: _textStatusController,
readOnly: _readOnlyStatus,
value: doc.data()?["status"] ?? "",
),
),
],
),
],
),
),
const SizedBox(height: 20),
// Weight Information
Container(
padding: const EdgeInsets.all(16),
decoration: BoxDecoration(
color: Colors.white,
borderRadius: BorderRadius.circular(12),
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(
"Informasi Bobot",
style: TextStyle(
fontWeight: FontWeight.w700,
fontSize: 16,
color: Color(0xFF1D91AA),
),
),
const SizedBox(height: 16),
// Initial weight
Row(
children: [
Container(
height: 36,
width: 36,
decoration: BoxDecoration(
color: const Color(0xFF1D91AA).withOpacity(0.1),
borderRadius: BorderRadius.circular(8),
),
child: const Icon(
Icons.monitor_weight,
color: Color(0xFF1D91AA),
size: 20,
),
),
const SizedBox(width: 12),
Expanded(
child: Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
Text(
"Bobot Awal",
style: TextStyle(
fontSize: 13,
color: Colors.grey[600],
),
),
const SizedBox(height: 4),
Row(
children: [
Text(
"${doc.data()?["bobot"] ?? "-"} Kg",
style: const TextStyle(
fontWeight: FontWeight.w700,
fontSize: 16,
),
),
const SizedBox(width: 8),
Text(
doc.data()?["tanggal_update"] ?? "",
style: TextStyle(
fontSize: 13,
color: Colors.grey[600],
),
),
],
),
],
),
),
],
),
const SizedBox(height: 16),
// Current weight with edit function
Form(
key: _formKey,
child: Row(
children: [
Container(
height: 36,
width: 36,
decoration: BoxDecoration(
color: const Color(0xFF1D91AA).withOpacity(0.1),
borderRadius: BorderRadius.circular(8),
),
child: const Icon(
Icons.scale,
color: Color(0xFF1D91AA),
size: 20,
),
),
const SizedBox(width: 12),
Expanded(
child: Row(
children: [
Expanded(
flex: 2,
child: Column(
crossAxisAlignment:
CrossAxisAlignment.start,
children: [
Text(
"Bobot Terkini",
style: TextStyle(
fontSize: 13,
color: Colors.grey[600],
),
),
const SizedBox(height: 4),
TextFormField(
autofocus: !_readOnlyFinalWeight,
keyboardType: TextInputType.number,
controller: _textController,
decoration: InputDecoration(
hintText: "Masukkan bobot",
suffixText: "Kg",
contentPadding:
const EdgeInsets.symmetric(
horizontal: 12,
vertical: 12),
border: OutlineInputBorder(
borderRadius:
BorderRadius.circular(8),
borderSide: BorderSide(
color: Colors.grey[300]!),
),
enabledBorder: OutlineInputBorder(
borderRadius:
BorderRadius.circular(8),
borderSide: BorderSide(
color: Colors.grey[300]!),
),
focusedBorder: OutlineInputBorder(
borderRadius:
BorderRadius.circular(8),
borderSide: const BorderSide(
color: Color(0xFF1D91AA)),
),
),
readOnly: _readOnlyFinalWeight,
validator: (value) {
if (value == null ||
value.isEmpty) {
return "Bobot Kosong";
}
return null;
},
),
],
),
),
if (_readOnlyFinalWeight && status)
IconButton(
icon: const Icon(Icons.edit,
color: Color(0xFF1D91AA)),
onPressed: () {
setState(() {
_readOnlyFinalWeight = false;
_icon = Icons.save_rounded;
});
},
)
else if (!_readOnlyFinalWeight)
IconButton(
icon: const Icon(Icons.save_rounded,
color: Color(0xFF1D91AA)),
onPressed: () {
if (_formKey.currentState?.validate() ==
true) {
FirebaseFirestore.instance
.collection("hewan")
.doc(doc.id)
.update({
"bobot_akhir": _textController.text
});
FirebaseFirestore.instance
.collection("bobot")
.add({
"hewan_id": doc.id,
"bobot_akhir": _textController.text,
"tanggal": DateFormat('yyyy-MM-dd')
.format(DateTime.now()),
});
setState(() {
_readOnlyFinalWeight = true;
_icon = Icons.rebase_edit;
});
}
},
),
],
),
),
],
),
),
],
),
),
],
),
),
dash(),
Container(
padding: const EdgeInsets.all(20),
child: Column(
children: [
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.monitor_weight,
color: Color(0xFF1D91AA),
size: 20,
),
),
const SizedBox(width: 12),
const Text(
"Riwayat Penimbangan",
style: TextStyle(
fontWeight: FontWeight.w700,
fontSize: 18,
color: Color(0xFF1D91AA),
),
),
],
),
const SizedBox(height: 20),
FutureBuilder<QuerySnapshot<Map<String, dynamic>>>(
future: _getWeightData(),
builder: (context, snapshot) {
if (snapshot.connectionState == ConnectionState.waiting) {
return const Center(
child: CircularProgressIndicator(
color: Color(0xFF1D91AA),
strokeWidth: 3,
),
);
}
if (snapshot.data != null) {
if (snapshot.data!.docs.isEmpty) {
return Container(
padding: const EdgeInsets.all(24),
decoration: BoxDecoration(
color: Colors.grey[100],
borderRadius: BorderRadius.circular(12),
),
child: Column(
children: [
Icon(
Icons.history,
size: 40,
color: Colors.grey[400],
),
const SizedBox(height: 16),
Text(
"Tidak Terdapat Riwayat Penimbangan",
style: TextStyle(
color: Colors.grey[600],
fontSize: 16,
),
),
],
),
);
}
return Column(
children: snapshot.data!.docs
.map((e) => RiwayatPenimbangan(doc: e))
.toList(),
);
}
return Container(
padding: const EdgeInsets.all(24),
decoration: BoxDecoration(
color: Colors.grey[100],
borderRadius: BorderRadius.circular(12),
),
child: Column(
children: [
Icon(
Icons.history,
size: 40,
color: Colors.grey[400],
),
const SizedBox(height: 16),
Text(
"Tidak Terdapat Riwayat Penimbangan",
style: TextStyle(
color: Colors.grey[600],
fontSize: 16,
),
),
],
),
);
},
),
],
),
),
dash(),
Container(
padding: const EdgeInsets.all(20),
child: Column(
children: [
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.health_and_safety,
color: Color(0xFF1D91AA),
size: 20,
),
),
const SizedBox(width: 12),
const Text(
"Riwayat Keterangan",
style: TextStyle(
fontWeight: FontWeight.w700,
fontSize: 18,
color: Color(0xFF1D91AA),
),
),
],
),
const SizedBox(height: 20),
FutureBuilder<QuerySnapshot<Map<String, dynamic>>>(
future: _getHealthyData(),
builder: (context, snapshot) {
if (snapshot.connectionState == ConnectionState.waiting) {
return const Center(
child: CircularProgressIndicator(
color: Color(0xFF1D91AA),
strokeWidth: 3,
),
);
}
if (snapshot.data != null) {
if (snapshot.data!.docs.isEmpty) {
return Container(
padding: const EdgeInsets.all(24),
decoration: BoxDecoration(
color: Colors.grey[100],
borderRadius: BorderRadius.circular(12),
),
child: Column(
children: [
Icon(
Icons.healing,
size: 40,
color: Colors.grey[400],
),
const SizedBox(height: 16),
Text(
"Tidak Terdapat Riwayat Keterangan",
style: TextStyle(
color: Colors.grey[600],
fontSize: 16,
),
),
],
),
);
}
return Column(
children: snapshot.data!.docs
.map((e) =>
RiwayatKesehatan(doc: e, globalKey: _globalKey))
.toList(),
);
}
return Container(
padding: const EdgeInsets.all(24),
decoration: BoxDecoration(
color: Colors.grey[100],
borderRadius: BorderRadius.circular(12),
),
child: Column(
children: [
Icon(
Icons.healing,
size: 40,
color: Colors.grey[400],
),
const SizedBox(height: 16),
Text(
"Tidak Terdapat Riwayat Keterangan",
style: TextStyle(
color: Colors.grey[600],
fontSize: 16,
),
),
],
),
);
},
),
],
),
)
],
),
);
}
TextFormField form(
{required String text,
String? value,
Widget? iconSuffix,
bool readOnly = true,
bool autoFocus = false,
String? validatorMessage,
void Function()? onTap,
TextEditingController? textController}) {
return TextFormField(
autofocus: autoFocus,
keyboardType: TextInputType.number,
controller:
value != null ? TextEditingController(text: value) : textController,
decoration: InputDecoration(
labelText: text,
suffixIcon: iconSuffix,
filled: true,
fillColor: readOnly ? Colors.grey[50] : Colors.white,
contentPadding:
const EdgeInsets.symmetric(horizontal: 16, vertical: 16),
border: OutlineInputBorder(
borderRadius: BorderRadius.circular(8),
borderSide: BorderSide(color: Colors.grey[300]!),
),
enabledBorder: OutlineInputBorder(
borderRadius: BorderRadius.circular(8),
borderSide: BorderSide(color: Colors.grey[300]!),
),
focusedBorder: OutlineInputBorder(
borderRadius: BorderRadius.circular(8),
borderSide: const BorderSide(color: Color(0xFF1D91AA)),
),
),
readOnly: readOnly,
onTap: onTap,
validator: (value) {
if (value == null || value.isEmpty) {
if (validatorMessage != null) {
return validatorMessage;
}
}
return null;
},
);
}
DropdownButtonFormField formDropdown(
{required String text,
required List<String> dropdown,
String? value,
Widget? iconSuffix,
bool readOnly = true,
TextEditingController? textController}) {
return DropdownButtonFormField(
decoration: InputDecoration(
labelText: text,
suffixIcon: status ? iconSuffix : null,
isDense: true,
filled: true,
fillColor: readOnly ? Colors.grey[50] : Colors.white,
contentPadding:
const EdgeInsets.symmetric(horizontal: 16, vertical: 16),
border: OutlineInputBorder(
borderRadius: BorderRadius.circular(8),
borderSide: BorderSide(color: Colors.grey[300]!),
),
enabledBorder: OutlineInputBorder(
borderRadius: BorderRadius.circular(8),
borderSide: BorderSide(color: Colors.grey[300]!),
),
focusedBorder: OutlineInputBorder(
borderRadius: BorderRadius.circular(8),
borderSide: const BorderSide(color: Color(0xFF1D91AA)),
),
),
value: dropdown.contains(value) ? (value) : null,
dropdownColor: Colors.white,
icon: const Icon(Icons.arrow_drop_down, color: Color(0xFF1D91AA)),
items: dropdown.map<DropdownMenuItem<String>>((String value) {
return DropdownMenuItem<String>(
value: value,
child: Text(value),
);
}).toList(),
onChanged: readOnly
? null
: (value) {
setState(() {
if (value != null) {
textController?.text = value;
}
});
},
);
}
Container dash() {
return Container(
margin: const EdgeInsets.symmetric(horizontal: 20),
height: 1,
decoration: BoxDecoration(
color: Colors.grey.withOpacity(0.2),
),
);
}
// Helper method for medium-sized location tile display
Widget locationTile({
required IconData icon,
required String text,
required String value,
}) {
return Column(
children: [
Container(
height: 32,
width: 32,
decoration: BoxDecoration(
color: const Color(0xFF1D91AA).withOpacity(0.1),
borderRadius: BorderRadius.circular(8),
),
child: Icon(icon, size: 18, color: const Color(0xFF1D91AA)),
),
const SizedBox(height: 4),
Text(
text,
style: TextStyle(
fontSize: 11,
color: Colors.grey[600],
),
),
const SizedBox(height: 2),
Text(
value,
style: const TextStyle(
fontWeight: FontWeight.bold,
fontSize: 13,
),
),
],
);
}
// Helper method to get status color
Color _getStatusColor(String? status) {
switch (status) {
case "Hidup":
return Colors.green.withOpacity(0.3);
case "Mati":
return Colors.red.withOpacity(0.3);
case "Terjual":
return Colors.blue.withOpacity(0.3);
default:
return Colors.grey.withOpacity(0.3);
}
}
// Helper method to get status icon
IconData _getStatusIcon(String? status) {
switch (status) {
case "Hidup":
return Icons.check_circle;
case "Mati":
return Icons.cancel;
case "Terjual":
return Icons.monetization_on;
default:
return Icons.help;
}
}
// Helper method to build information rows
Widget _buildInfoRow(
{required IconData icon, required String label, required String value}) {
return Row(
children: [
Container(
height: 36,
width: 36,
decoration: BoxDecoration(
color: const Color(0xFF1D91AA).withOpacity(0.1),
borderRadius: BorderRadius.circular(8),
),
child: Icon(
icon,
color: const Color(0xFF1D91AA),
size: 20,
),
),
const SizedBox(width: 12),
Expanded(
child: Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
Text(
label,
style: TextStyle(
fontSize: 13,
color: Colors.grey[600],
),
),
const SizedBox(height: 4),
Text(
value,
style: const TextStyle(
fontWeight: FontWeight.w700,
fontSize: 16,
),
),
],
),
),
],
);
}
// New helper method for status background colors (for white theme)
Color _getStatusBackgroundColor(String? status) {
switch (status) {
case "Hidup":
return Colors.green.withOpacity(0.1);
case "Mati":
return Colors.red.withOpacity(0.1);
case "Terjual":
return Colors.blue.withOpacity(0.1);
default:
return Colors.grey.withOpacity(0.1);
}
}
// New helper method for status icon colors (for white theme)
Color _getStatusIconColor(String? status) {
switch (status) {
case "Hidup":
return Colors.green;
case "Mati":
return Colors.red;
case "Terjual":
return Colors.blue;
default:
return Colors.grey;
}
}
}