339 lines
12 KiB
Dart
339 lines
12 KiB
Dart
import 'package:flutter/material.dart';
|
|
import 'package:google_fonts/google_fonts.dart';
|
|
import 'package:camera/camera.dart';
|
|
import 'dart:io';
|
|
import 'package:http/http.dart' as http;
|
|
import 'dart:convert';
|
|
import 'package:intl/intl.dart';
|
|
import 'package:image_cropper/image_cropper.dart';
|
|
import 'result_page.dart';
|
|
|
|
class CameraPage extends StatefulWidget {
|
|
const CameraPage({super.key});
|
|
|
|
@override
|
|
_CameraPageState createState() => _CameraPageState();
|
|
}
|
|
|
|
class _CameraPageState extends State<CameraPage> {
|
|
CameraController? _controller;
|
|
late List<CameraDescription> _cameras;
|
|
bool _isCameraInitialized = false;
|
|
bool _isUploading = false;
|
|
|
|
@override
|
|
void initState() {
|
|
super.initState();
|
|
_initializeCamera();
|
|
}
|
|
|
|
Future<void> _initializeCamera() async {
|
|
_cameras = await availableCameras();
|
|
_controller = CameraController(_cameras[0], ResolutionPreset.medium);
|
|
await _controller!.initialize();
|
|
if (!mounted) return;
|
|
setState(() {
|
|
_isCameraInitialized = true;
|
|
});
|
|
}
|
|
|
|
Future<void> _captureImage() async {
|
|
if (!_controller!.value.isInitialized) return;
|
|
|
|
final XFile imageFile = await _controller!.takePicture();
|
|
File? croppedImage = await _cropImage(File(imageFile.path));
|
|
|
|
if (croppedImage != null) {
|
|
final String date =
|
|
DateFormat('dd-MM-yyyy HH:mm:ss').format(DateTime.now());
|
|
_uploadImage(croppedImage, date);
|
|
} else {
|
|
print("❌ Cropping dibatalkan.");
|
|
}
|
|
}
|
|
|
|
Future<File?> _cropImage(File imageFile) async {
|
|
try {
|
|
final CroppedFile? croppedFile = await ImageCropper().cropImage(
|
|
sourcePath: imageFile.path,
|
|
compressFormat: ImageCompressFormat.jpg,
|
|
compressQuality: 80,
|
|
aspectRatioPresets: [
|
|
CropAspectRatioPreset.square,
|
|
CropAspectRatioPreset.ratio3x2,
|
|
CropAspectRatioPreset.original,
|
|
CropAspectRatioPreset.ratio4x3,
|
|
CropAspectRatioPreset.ratio16x9
|
|
],
|
|
uiSettings: [
|
|
AndroidUiSettings(
|
|
toolbarTitle: 'Crop Gambar',
|
|
toolbarColor: const Color(0xFF891A2D),
|
|
toolbarWidgetColor: Colors.white,
|
|
initAspectRatio: CropAspectRatioPreset.original,
|
|
lockAspectRatio: false,
|
|
),
|
|
IOSUiSettings(title: 'Crop Gambar'),
|
|
],
|
|
);
|
|
|
|
return croppedFile != null ? File(croppedFile.path) : null;
|
|
} catch (e) {
|
|
print("❌ Error saat cropping gambar: $e");
|
|
return null;
|
|
}
|
|
}
|
|
|
|
Future<void> _uploadImage(File image, String date) async {
|
|
setState(() {
|
|
_isUploading = true;
|
|
});
|
|
|
|
var request = http.MultipartRequest(
|
|
'POST', Uri.parse('http://172.20.10.10:5000/predict'));
|
|
request.files.add(await http.MultipartFile.fromPath('file', image.path));
|
|
|
|
var response = await request.send();
|
|
|
|
if (response.statusCode == 200) {
|
|
var responseData = await response.stream.bytesToString();
|
|
var result = json.decode(responseData);
|
|
String prediction = result['prediction'];
|
|
|
|
setState(() {
|
|
_isUploading = false;
|
|
});
|
|
|
|
Navigator.push(
|
|
context,
|
|
MaterialPageRoute(
|
|
builder: (context) => ResultPage(
|
|
image: image,
|
|
date: date,
|
|
prediction: prediction,
|
|
),
|
|
),
|
|
);
|
|
} else {
|
|
setState(() {
|
|
_isUploading = false;
|
|
});
|
|
|
|
ScaffoldMessenger.of(context).showSnackBar(
|
|
const SnackBar(content: Text("Terjadi kesalahan. Coba lagi.")),
|
|
);
|
|
}
|
|
}
|
|
|
|
@override
|
|
void dispose() {
|
|
_controller?.dispose();
|
|
super.dispose();
|
|
}
|
|
|
|
@override
|
|
Widget build(BuildContext context) {
|
|
return Scaffold(
|
|
appBar: AppBar(
|
|
backgroundColor: Colors.transparent,
|
|
elevation: 0,
|
|
flexibleSpace: Container(
|
|
decoration: const BoxDecoration(
|
|
gradient: LinearGradient(
|
|
colors: [Color(0xFF891A2D), Color(0xFF891A2D)],
|
|
begin: Alignment.topLeft,
|
|
end: Alignment.bottomRight,
|
|
),
|
|
),
|
|
),
|
|
title: Text(
|
|
"Ambil Gambar",
|
|
style: GoogleFonts.poppins(
|
|
color: Colors.white,
|
|
// fontWeight: FontWeight.w600,
|
|
fontSize: 20,
|
|
),
|
|
),
|
|
centerTitle: true,
|
|
iconTheme: const IconThemeData(color: Colors.white),
|
|
),
|
|
body: Stack(
|
|
children: [
|
|
Column(
|
|
children: [
|
|
Expanded(
|
|
child: Stack(
|
|
children: [
|
|
// Tampilan Kamera
|
|
_isCameraInitialized
|
|
? CameraPreview(_controller!)
|
|
: const Center(child: CircularProgressIndicator()),
|
|
// Bingkai Panduan untuk Membantu Pengguna
|
|
// Center(
|
|
// child: Container(
|
|
// width: 300,
|
|
// height: 300,
|
|
// decoration: BoxDecoration(
|
|
// border: Border.all(
|
|
// color: Colors.white.withOpacity(0.7),
|
|
// width: 2,
|
|
// style: BorderStyle.solid,
|
|
// ),
|
|
// borderRadius: BorderRadius.circular(15),
|
|
// ),
|
|
// child: Center(
|
|
// child: Text(
|
|
// "Letakkan stroberi di sini",
|
|
// style: GoogleFonts.poppins(
|
|
// color: Colors.white.withOpacity(0.8),
|
|
// fontSize: 14,
|
|
// ),
|
|
// ),
|
|
// ),
|
|
// ),
|
|
// ),
|
|
// Petunjuk Penggunaan dan Tombol
|
|
Align(
|
|
alignment: Alignment.bottomCenter,
|
|
child: Container(
|
|
width: double.infinity,
|
|
decoration: BoxDecoration(
|
|
color: Colors.white,
|
|
borderRadius: const BorderRadius.vertical(
|
|
top: Radius.circular(20),
|
|
),
|
|
boxShadow: [
|
|
BoxShadow(
|
|
color: Colors.black.withOpacity(0.1),
|
|
blurRadius: 10,
|
|
spreadRadius: 5,
|
|
),
|
|
],
|
|
),
|
|
padding: const EdgeInsets.all(20),
|
|
child: Column(
|
|
mainAxisSize: MainAxisSize.min,
|
|
children: [
|
|
// Petunjuk Penggunaan dalam Kartu
|
|
Card(
|
|
elevation: 3,
|
|
shape: RoundedRectangleBorder(
|
|
borderRadius: BorderRadius.circular(15),
|
|
),
|
|
color: Colors.grey[100],
|
|
child: Padding(
|
|
padding: const EdgeInsets.all(15),
|
|
child: Column(
|
|
crossAxisAlignment: CrossAxisAlignment.start,
|
|
children: [
|
|
Row(
|
|
mainAxisAlignment:
|
|
MainAxisAlignment.center,
|
|
children: [
|
|
const Icon(
|
|
Icons.info_outline,
|
|
color: Color(0xFF891A2D),
|
|
size: 24,
|
|
),
|
|
const SizedBox(width: 8),
|
|
Text(
|
|
"Petunjuk Penggunaan",
|
|
style: GoogleFonts.poppins(
|
|
fontSize: 18,
|
|
fontWeight: FontWeight.bold,
|
|
color: const Color(0xFF891A2D),
|
|
),
|
|
),
|
|
],
|
|
),
|
|
const SizedBox(height: 10),
|
|
Text(
|
|
"Arahkan kamera ke buah stroberi tanpa tangkai dalam pencahayaan yang baik untuk mendapatkan hasil terbaik",
|
|
textAlign: TextAlign.center,
|
|
style: GoogleFonts.poppins(
|
|
fontSize: 14,
|
|
color: Colors.black87,
|
|
height: 1.6,
|
|
),
|
|
),
|
|
],
|
|
),
|
|
),
|
|
),
|
|
const SizedBox(height: 20),
|
|
// Tombol Ambil Gambar dengan Animasi
|
|
TweenAnimationBuilder(
|
|
tween: Tween<double>(begin: 1.0, end: 1.0),
|
|
duration: const Duration(milliseconds: 200),
|
|
builder: (context, scale, child) {
|
|
return Transform.scale(
|
|
scale: scale,
|
|
child: child,
|
|
);
|
|
},
|
|
child: ElevatedButton.icon(
|
|
onPressed: _isUploading ? null : _captureImage,
|
|
icon: const Icon(
|
|
Icons.camera_alt,
|
|
size: 28,
|
|
color: Colors.white,
|
|
),
|
|
label: Text(
|
|
"Ambil Gambar",
|
|
style: GoogleFonts.poppins(
|
|
fontSize: 16,
|
|
color: Colors.white,
|
|
fontWeight: FontWeight.w600,
|
|
),
|
|
),
|
|
style: ElevatedButton.styleFrom(
|
|
backgroundColor: const Color(0xFF891A2D),
|
|
padding: const EdgeInsets.symmetric(
|
|
vertical: 15, horizontal: 30),
|
|
shape: RoundedRectangleBorder(
|
|
borderRadius: BorderRadius.circular(15),
|
|
),
|
|
elevation: 5,
|
|
),
|
|
),
|
|
),
|
|
],
|
|
),
|
|
),
|
|
),
|
|
],
|
|
),
|
|
),
|
|
],
|
|
),
|
|
// Indikator Loading
|
|
if (_isUploading)
|
|
Container(
|
|
color: Colors.black.withOpacity(0.6),
|
|
child: Center(
|
|
child: Column(
|
|
mainAxisSize: MainAxisSize.min,
|
|
children: [
|
|
const CircularProgressIndicator(
|
|
color: Color(0xFF891A2D),
|
|
strokeWidth: 5,
|
|
),
|
|
const SizedBox(height: 15),
|
|
Text(
|
|
"Mengunggah Gambar...",
|
|
style: GoogleFonts.poppins(
|
|
color: Colors.white,
|
|
fontSize: 16,
|
|
fontWeight: FontWeight.w500,
|
|
),
|
|
),
|
|
],
|
|
),
|
|
),
|
|
),
|
|
],
|
|
),
|
|
);
|
|
}
|
|
}
|