293 lines
10 KiB
Dart
293 lines
10 KiB
Dart
import 'package:flutter/material.dart';
|
|
import 'package:google_fonts/google_fonts.dart';
|
|
import 'package:image_picker/image_picker.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'; // Import ResultPage
|
|
|
|
class SelectImagePage extends StatefulWidget {
|
|
const SelectImagePage({super.key});
|
|
|
|
@override
|
|
State<SelectImagePage> createState() => _SelectImagePageState();
|
|
}
|
|
|
|
class _SelectImagePageState extends State<SelectImagePage> {
|
|
File? _selectedImage;
|
|
bool _isUploading = false;
|
|
|
|
final String apiUrl = "http://172.20.10.10:5000/predict";
|
|
|
|
Future<void> _pickImage() async {
|
|
final pickedFile =
|
|
await ImagePicker().pickImage(source: ImageSource.gallery);
|
|
|
|
if (pickedFile != null) {
|
|
File? croppedImage = await _cropImage(File(pickedFile.path));
|
|
|
|
if (croppedImage != null) {
|
|
setState(() {
|
|
_selectedImage = croppedImage;
|
|
_isUploading = true;
|
|
});
|
|
_uploadImage(croppedImage);
|
|
}
|
|
} else {
|
|
print("❌ Gambar tidak dipilih.");
|
|
}
|
|
}
|
|
|
|
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) async {
|
|
try {
|
|
var request = http.MultipartRequest('POST', Uri.parse(apiUrl));
|
|
request.files.add(await http.MultipartFile.fromPath('file', image.path));
|
|
|
|
var response = await request.send();
|
|
print("📡 Status Code: ${response.statusCode}");
|
|
|
|
if (response.statusCode == 200) {
|
|
var responseData = await response.stream.bytesToString();
|
|
var result = json.decode(responseData);
|
|
print("✅ Response dari API: $responseData");
|
|
|
|
setState(() =>
|
|
_isUploading = false); // Sembunyikan loading setelah upload selesai
|
|
|
|
// Navigasi ke ResultPage dengan hasil prediksi & gambar yang sudah di-crop
|
|
Navigator.push(
|
|
context,
|
|
MaterialPageRoute(
|
|
builder: (context) => ResultPage(
|
|
image: image, // Kirim gambar ke ResultPage
|
|
prediction: result['prediction'] ?? "-",
|
|
date: DateFormat('dd MMM yyyy, HH:mm').format(DateTime.now()),
|
|
),
|
|
),
|
|
);
|
|
} else {
|
|
print("❌ Error Response: ${await response.stream.bytesToString()}");
|
|
setState(() => _isUploading = false);
|
|
}
|
|
} catch (e) {
|
|
print("⚠️ Exception: $e");
|
|
setState(() => _isUploading = false);
|
|
}
|
|
}
|
|
|
|
@override
|
|
Widget build(BuildContext context) {
|
|
return Scaffold(
|
|
backgroundColor: Colors.white, // Latar belakang putih
|
|
appBar: AppBar(
|
|
backgroundColor: const Color(0xFF891A2D),
|
|
title: Text(
|
|
"Pilih Gambar",
|
|
style: GoogleFonts.poppins(color: Colors.white),
|
|
),
|
|
centerTitle: true,
|
|
iconTheme: const IconThemeData(color: Colors.white),
|
|
),
|
|
body: SafeArea(
|
|
child: SingleChildScrollView(
|
|
child: Padding(
|
|
padding: const EdgeInsets.all(20),
|
|
child: Column(
|
|
crossAxisAlignment: CrossAxisAlignment.center,
|
|
children: [
|
|
// Area Gambar dengan Efek Kartu
|
|
Card(
|
|
elevation: 5,
|
|
shape: RoundedRectangleBorder(
|
|
borderRadius: BorderRadius.circular(20),
|
|
),
|
|
child: AnimatedContainer(
|
|
duration: const Duration(milliseconds: 300),
|
|
width: double.infinity,
|
|
height: 280,
|
|
decoration: BoxDecoration(
|
|
borderRadius: BorderRadius.circular(20),
|
|
color: Colors.grey[200],
|
|
),
|
|
child: _selectedImage != null
|
|
? ClipRRect(
|
|
borderRadius: BorderRadius.circular(20),
|
|
child: Image.file(
|
|
_selectedImage!,
|
|
width: double.infinity,
|
|
height: 280,
|
|
fit: BoxFit.cover,
|
|
),
|
|
)
|
|
: const Center(
|
|
child: Icon(
|
|
Icons.image_outlined,
|
|
size: 100,
|
|
color: Colors.black26,
|
|
),
|
|
),
|
|
),
|
|
),
|
|
|
|
const SizedBox(height: 20),
|
|
|
|
// Tampilkan Loading dengan Animasi
|
|
if (_isUploading)
|
|
Column(
|
|
children: [
|
|
const CircularProgressIndicator(
|
|
color: const Color(0xFF891A2D),
|
|
strokeWidth: 5,
|
|
),
|
|
const SizedBox(height: 15),
|
|
Text(
|
|
"Mengunggah Gambar...",
|
|
style: GoogleFonts.poppins(
|
|
fontSize: 16,
|
|
color: const Color.fromARGB(255, 0, 0, 0),
|
|
fontWeight: FontWeight.w500,
|
|
),
|
|
),
|
|
],
|
|
),
|
|
|
|
const SizedBox(height: 25),
|
|
|
|
// Petunjuk Penggunaan dalam Kartu
|
|
Card(
|
|
elevation: 3,
|
|
shape: RoundedRectangleBorder(
|
|
borderRadius: BorderRadius.circular(15),
|
|
),
|
|
color: Colors.grey[
|
|
100], // Warna latar belakang seperti StrawberryInfoPage
|
|
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), // Warna merah tema
|
|
size: 24,
|
|
),
|
|
const SizedBox(width: 8),
|
|
Text(
|
|
"Petunjuk Penggunaan",
|
|
style: GoogleFonts.poppins(
|
|
fontSize: 18,
|
|
fontWeight: FontWeight.bold,
|
|
color:
|
|
const Color(0xFF891A2D), // Warna merah tema
|
|
),
|
|
),
|
|
],
|
|
),
|
|
const SizedBox(height: 10),
|
|
Text(
|
|
"• Klik tombol 'Pilih dari Galeri' untuk memilih gambar.\n"
|
|
"• Crop gambar pada bagian buah stroberi, hilangkan tangkainya, lalu tekan 'Simpan'.\n"
|
|
"• Tunggu proses upload hingga selesai.\n"
|
|
"• Hasil prediksi akan ditampilkan di halaman berikutnya.",
|
|
textAlign: TextAlign.left,
|
|
style: GoogleFonts.poppins(
|
|
fontSize: 14,
|
|
color: Colors.black87,
|
|
height: 1.6,
|
|
),
|
|
),
|
|
],
|
|
),
|
|
),
|
|
),
|
|
|
|
const SizedBox(height: 30),
|
|
|
|
// Tombol Pilih 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(
|
|
onPressed: _pickImage,
|
|
style: ElevatedButton.styleFrom(
|
|
backgroundColor: const Color(0xFF891A2D),
|
|
shape: RoundedRectangleBorder(
|
|
borderRadius: BorderRadius.circular(15),
|
|
),
|
|
padding: const EdgeInsets.symmetric(
|
|
vertical: 15, horizontal: 40),
|
|
elevation: 5,
|
|
),
|
|
child: Row(
|
|
mainAxisSize: MainAxisSize.min,
|
|
children: [
|
|
const Icon(
|
|
Icons.photo_library,
|
|
color: Colors.white,
|
|
size: 24,
|
|
),
|
|
const SizedBox(width: 10),
|
|
Text(
|
|
"Pilih dari Galeri",
|
|
style: GoogleFonts.poppins(
|
|
fontSize: 16,
|
|
color: Colors.white,
|
|
fontWeight: FontWeight.w600,
|
|
),
|
|
),
|
|
],
|
|
),
|
|
),
|
|
),
|
|
],
|
|
),
|
|
),
|
|
),
|
|
),
|
|
);
|
|
}
|
|
}
|