711 lines
25 KiB
Dart
711 lines
25 KiB
Dart
import 'package:cloud_firestore/cloud_firestore.dart';
|
|
import 'package:firebase_auth/firebase_auth.dart';
|
|
import 'package:flutter/material.dart';
|
|
import 'dart:async';
|
|
import 'package:firebase_database/firebase_database.dart';
|
|
import 'package:flutter_background_service/flutter_background_service.dart';
|
|
import 'package:shared_preferences/shared_preferences.dart';
|
|
|
|
import 'jadwal_kotak_obat1_screen.dart';
|
|
import 'jadwal_kotak_obat2_screen.dart';
|
|
import 'login_screen.dart';
|
|
//import 'firebase_messaging_service.dart';
|
|
import 'notifikasi_service.dart';
|
|
import 'toast.dart';
|
|
|
|
class HomeScreen extends StatefulWidget {
|
|
const HomeScreen({
|
|
super.key,
|
|
required String username,
|
|
required void Function() onSignOut,
|
|
});
|
|
|
|
@override
|
|
State<HomeScreen> createState() => _HomeScreenState();
|
|
}
|
|
|
|
class _HomeScreenState extends State<HomeScreen> {
|
|
Timer? _syncTimer;
|
|
final bool _hasSentKotak1 = false;
|
|
final bool _hasSentKotak2 = false;
|
|
DateTime? _lastSentKotak1;
|
|
DateTime? _lastSentKotak2;
|
|
static const Duration minInterval = Duration(seconds: 10);
|
|
String username = '';
|
|
bool isLoading = true;
|
|
|
|
// Tambahan state untuk tombol dan status
|
|
bool button1 = false;
|
|
bool button2 = false;
|
|
bool buzzer = false;
|
|
bool lampu = false;
|
|
bool _canAccessKotak1 = false;
|
|
bool _canAccessKotak2 = false;
|
|
|
|
String infoText = "Tidak Ada Informasi";
|
|
|
|
@override
|
|
void initState() {
|
|
super.initState();
|
|
//NotifikasiService.listenToAllNotifikasi(); // Realtime listener
|
|
_initializeAsyncTasks(); // panggil fungsi async tanpa await
|
|
_checkUserAccessToKotak();
|
|
_syncTimer = Timer.periodic(const Duration(seconds: 1), (timer) {
|
|
_updateStatus(); // update tombol dan info tiap 2 detik
|
|
});
|
|
}
|
|
|
|
@override
|
|
void dispose() {
|
|
_syncTimer?.cancel();
|
|
super.dispose();
|
|
}
|
|
|
|
Future<void> _initializeAsyncTasks() async {
|
|
await fetchUsername();
|
|
}
|
|
|
|
Future<void> fetchUsername() async {
|
|
try {
|
|
final user = FirebaseAuth.instance.currentUser;
|
|
if (user != null) {
|
|
final uid = user.uid;
|
|
final doc =
|
|
await FirebaseFirestore.instance.collection('users').doc(uid).get();
|
|
if (doc.exists && doc.data() != null) {
|
|
if (!mounted) return;
|
|
setState(() {
|
|
username = doc['username'] ?? 'User';
|
|
isLoading = false;
|
|
});
|
|
}
|
|
}
|
|
} catch (e) {
|
|
print('Error fetching username: $e');
|
|
if (!mounted) return;
|
|
setState(() {
|
|
username = 'User';
|
|
isLoading = false;
|
|
});
|
|
}
|
|
}
|
|
|
|
Future<void> _showLogoutConfirmationDialog() async {
|
|
final shouldLogout = await showDialog<bool>(
|
|
context: context,
|
|
builder:
|
|
(context) => AlertDialog(
|
|
title: const Text('Konfirmasi Logout'),
|
|
content: const Text(
|
|
'Apakah Anda yakin ingin logout dari akun ini?',
|
|
),
|
|
actions: [
|
|
TextButton(
|
|
onPressed: () => Navigator.of(context).pop(false),
|
|
child: const Text('Tidak'),
|
|
),
|
|
TextButton(
|
|
onPressed: () => Navigator.of(context).pop(true),
|
|
child: const Text('Ya'),
|
|
),
|
|
],
|
|
),
|
|
);
|
|
|
|
if (shouldLogout == true) {
|
|
_logout();
|
|
}
|
|
}
|
|
|
|
// Fungsi baru: Update status tombol dan info
|
|
Future<void> _updateStatus() async {
|
|
final user = FirebaseAuth.instance.currentUser;
|
|
if (user == null) return;
|
|
final uid = user.uid;
|
|
|
|
try {
|
|
final button1Ref = FirebaseDatabase.instance.ref('status/button1');
|
|
final button2Ref = FirebaseDatabase.instance.ref('status/button2');
|
|
final buzzerRef = FirebaseDatabase.instance.ref('status/buzzer');
|
|
final lampuRef = FirebaseDatabase.instance.ref('status/led');
|
|
|
|
final kotak1UserIdRef = FirebaseDatabase.instance.ref(
|
|
'jadwal/kotak1/userId',
|
|
);
|
|
final kotak2UserIdRef = FirebaseDatabase.instance.ref(
|
|
'jadwal/kotak2/userId',
|
|
);
|
|
|
|
final button1Snap = await button1Ref.get();
|
|
final button2Snap = await button2Ref.get();
|
|
final buzzerSnap = await buzzerRef.get();
|
|
final lampuSnap = await lampuRef.get();
|
|
final kotak1UserIdSnap = await kotak1UserIdRef.get();
|
|
final kotak2UserIdSnap = await kotak2UserIdRef.get();
|
|
|
|
// Cek userId sesuai
|
|
final button1UserMatch =
|
|
kotak1UserIdSnap.exists && kotak1UserIdSnap.value == uid;
|
|
final button2UserMatch =
|
|
kotak2UserIdSnap.exists && kotak2UserIdSnap.value == uid;
|
|
|
|
if (!mounted) return;
|
|
setState(() {
|
|
button1 = button1Snap.exists && button1Snap.value == true;
|
|
button2 = button2Snap.exists && button2Snap.value == true;
|
|
buzzer = buzzerSnap.exists && buzzerSnap.value == true;
|
|
lampu = lampuSnap.exists && lampuSnap.value == true;
|
|
|
|
// Jika userId tidak cocok maka tombol tetap tampil tapi tidak bisa ditekan, tapi kita simpan status tombol sesuai DB
|
|
if (!button1UserMatch) button1 = button1; // tetap tampil status
|
|
if (!button2UserMatch) button2 = button2; // tetap tampil status
|
|
});
|
|
|
|
// Update infoText sesuai kondisi:
|
|
String info = "Tidak Ada Informasi";
|
|
|
|
// Cek alarm1 dan alarm2
|
|
final alarm1Snap =
|
|
await FirebaseDatabase.instance.ref('status/alarm1').get();
|
|
final alarm2Snap =
|
|
await FirebaseDatabase.instance.ref('status/alarm2').get();
|
|
|
|
// Cek jumlahObat kotak1 dan kotak2
|
|
final jumlahObat1Snap =
|
|
await FirebaseDatabase.instance.ref('jadwal/kotak1/jumlahObat').get();
|
|
final jumlahObat2Snap =
|
|
await FirebaseDatabase.instance.ref('jadwal/kotak2/jumlahObat').get();
|
|
|
|
final kotak1UserIdCheck =
|
|
kotak1UserIdSnap.exists && kotak1UserIdSnap.value == uid;
|
|
final kotak2UserIdCheck =
|
|
kotak2UserIdSnap.exists && kotak2UserIdSnap.value == uid;
|
|
|
|
if (kotak1UserIdCheck) {
|
|
if (alarm1Snap.exists && alarm1Snap.value == true) {
|
|
info = "Alarm Kotak 1 Aktif";
|
|
} else if (jumlahObat1Snap.exists) {
|
|
final val = jumlahObat1Snap.value;
|
|
int? jumlah = (val is int) ? val : int.tryParse(val.toString());
|
|
if (jumlah != null) {
|
|
if (jumlah == 0) {
|
|
info = "Obat Kotak 1 Habis, Segera isi ulang";
|
|
} else if (jumlah <= 3 && jumlah > 0) {
|
|
info = "Obat Kotak 1 Sisa $jumlah, Waktunya isi ulang";
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
if (kotak2UserIdCheck) {
|
|
if (alarm2Snap.exists && alarm2Snap.value == true) {
|
|
info = "Alarm Kotak 2 Aktif";
|
|
} else if (jumlahObat2Snap.exists) {
|
|
final val = jumlahObat2Snap.value;
|
|
int? jumlah = (val is int) ? val : int.tryParse(val.toString());
|
|
if (jumlah != null) {
|
|
if (jumlah == 0) {
|
|
info = "Obat Kotak 2 Habis, Segera isi ulang";
|
|
} else if (jumlah <= 3 && jumlah > 0) {
|
|
info = "Obat Kotak 2 Sisa $jumlah, Waktunya isi ulang";
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
if (!mounted) return;
|
|
setState(() {
|
|
infoText = info;
|
|
});
|
|
} catch (e) {
|
|
print('Gagal update status tombol dan info: $e');
|
|
}
|
|
}
|
|
|
|
Future<void> _logout() async {
|
|
await FirebaseAuth.instance.signOut();
|
|
SharedPreferences prefs = await SharedPreferences.getInstance();
|
|
await prefs.remove('userId');
|
|
FlutterBackgroundService().invoke("stopService");
|
|
|
|
print("✅ Logout berhasil dan background service dihentikan");
|
|
print('❌ userId dihapus dari SharedPreferences');
|
|
Navigator.pushReplacement(
|
|
context,
|
|
MaterialPageRoute(builder: (context) => const LoginScreen()),
|
|
);
|
|
showToast(message: "Successfully Logout");
|
|
}
|
|
|
|
// Fungsi untuk toggle button kotak 1
|
|
void _toggleButton1() async {
|
|
final user = FirebaseAuth.instance.currentUser;
|
|
if (user == null) return;
|
|
|
|
final userId = user.uid;
|
|
final kotak1UserIdSnap =
|
|
await FirebaseDatabase.instance.ref('jadwal/kotak1/userId').get();
|
|
if (!kotak1UserIdSnap.exists || kotak1UserIdSnap.value != userId) return;
|
|
|
|
await FirebaseDatabase.instance.ref('status/button1').set(!button1);
|
|
}
|
|
|
|
// Fungsi untuk toggle button kotak 2
|
|
void _toggleButton2() async {
|
|
final user = FirebaseAuth.instance.currentUser;
|
|
if (user == null) return;
|
|
|
|
final userId = user.uid;
|
|
final kotak2UserIdSnap =
|
|
await FirebaseDatabase.instance.ref('jadwal/kotak2/userId').get();
|
|
if (!kotak2UserIdSnap.exists || kotak2UserIdSnap.value != userId) return;
|
|
|
|
await FirebaseDatabase.instance.ref('status/button2').set(!button2);
|
|
}
|
|
|
|
Future<void> _checkUserAccessToKotak() async {
|
|
final user = FirebaseAuth.instance.currentUser;
|
|
if (user == null) return;
|
|
final uid = user.uid;
|
|
|
|
try {
|
|
final kotak1UserIdSnap =
|
|
await FirebaseDatabase.instance.ref('jadwal/kotak1/userId').get();
|
|
final kotak2UserIdSnap =
|
|
await FirebaseDatabase.instance.ref('jadwal/kotak2/userId').get();
|
|
|
|
bool canAccessKotak1 = false;
|
|
bool canAccessKotak2 = false;
|
|
|
|
// Jika userId kosong atau tidak ada, maka semua user boleh akses
|
|
if (!kotak1UserIdSnap.exists ||
|
|
kotak1UserIdSnap.value == null ||
|
|
kotak1UserIdSnap.value.toString().trim().isEmpty) {
|
|
canAccessKotak1 = true;
|
|
} else {
|
|
canAccessKotak1 = kotak1UserIdSnap.value == uid;
|
|
}
|
|
|
|
if (!kotak2UserIdSnap.exists ||
|
|
kotak2UserIdSnap.value == null ||
|
|
kotak2UserIdSnap.value.toString().trim().isEmpty) {
|
|
canAccessKotak2 = true;
|
|
} else {
|
|
canAccessKotak2 = kotak2UserIdSnap.value == uid;
|
|
}
|
|
|
|
if (!mounted) return;
|
|
setState(() {
|
|
_canAccessKotak1 = canAccessKotak1;
|
|
_canAccessKotak2 = canAccessKotak2;
|
|
});
|
|
} catch (e) {
|
|
print('Error checking access to kotak: $e');
|
|
}
|
|
}
|
|
|
|
void _showAccessDeniedDialog(String kotakName) {
|
|
showDialog(
|
|
context: context,
|
|
builder:
|
|
(_) => AlertDialog(
|
|
title: const Text('Akses Ditolak'),
|
|
content: Text('$kotakName Sudah diisi user lain'),
|
|
actions: [
|
|
TextButton(
|
|
onPressed: () => Navigator.of(context).pop(),
|
|
child: const Text('OK'),
|
|
),
|
|
],
|
|
),
|
|
);
|
|
}
|
|
|
|
Future<void> _refresh() async {
|
|
setState(() {
|
|
isLoading = true;
|
|
});
|
|
await Future.wait([
|
|
_initializeAsyncTasks(),
|
|
_checkUserAccessToKotak(),
|
|
_updateStatus(),
|
|
]);
|
|
if (mounted) {
|
|
setState(() {
|
|
isLoading = false;
|
|
});
|
|
}
|
|
}
|
|
|
|
@override
|
|
Widget build(BuildContext context) {
|
|
if (isLoading) {
|
|
return const Center(child: CircularProgressIndicator());
|
|
}
|
|
|
|
return RefreshIndicator(
|
|
onRefresh: _refresh,
|
|
child: SingleChildScrollView(
|
|
physics: const AlwaysScrollableScrollPhysics(),
|
|
child: Column(
|
|
children: [
|
|
Container(
|
|
width: double.infinity,
|
|
padding: const EdgeInsets.symmetric(vertical: 35, horizontal: 24),
|
|
decoration: const BoxDecoration(
|
|
color: Color(0xFF3FA535),
|
|
borderRadius: BorderRadius.only(
|
|
bottomLeft: Radius.circular(40),
|
|
bottomRight: Radius.circular(40),
|
|
),
|
|
),
|
|
child: Row(
|
|
mainAxisAlignment:
|
|
MainAxisAlignment
|
|
.center, // Menyusun elemen-elemen ke tengah horizontal
|
|
crossAxisAlignment:
|
|
CrossAxisAlignment
|
|
.center, // Menyusun elemen-elemen ke tengah vertikal
|
|
children: [
|
|
const Icon(
|
|
Icons.account_circle,
|
|
color: Colors.white,
|
|
size: 35,
|
|
),
|
|
const SizedBox(width: 10),
|
|
Text(
|
|
'Hello, $username',
|
|
style: const TextStyle(
|
|
fontSize: 20,
|
|
color: Colors.white,
|
|
fontWeight: FontWeight.bold,
|
|
),
|
|
),
|
|
const Spacer(),
|
|
IconButton(
|
|
onPressed: _showLogoutConfirmationDialog,
|
|
icon: const Icon(
|
|
Icons.logout,
|
|
color: Colors.white,
|
|
size: 30,
|
|
),
|
|
),
|
|
],
|
|
),
|
|
),
|
|
Container(
|
|
margin: const EdgeInsets.all(24),
|
|
padding: const EdgeInsets.all(24),
|
|
decoration: BoxDecoration(
|
|
color: Colors.white,
|
|
borderRadius: BorderRadius.circular(24),
|
|
boxShadow: [
|
|
BoxShadow(
|
|
color: Colors.grey.shade300,
|
|
blurRadius: 10,
|
|
offset: const Offset(0, 5),
|
|
),
|
|
],
|
|
),
|
|
child: Column(
|
|
children: [
|
|
Row(
|
|
mainAxisAlignment: MainAxisAlignment.center,
|
|
children: [
|
|
Image.asset(
|
|
'assets/images/logowarna.png',
|
|
width: 60,
|
|
height: 60,
|
|
),
|
|
SizedBox(width: 10), // jarak antar gambar
|
|
Image.asset(
|
|
'assets/images/logopolije.png',
|
|
width: 60,
|
|
height: 60,
|
|
),
|
|
],
|
|
),
|
|
const SizedBox(height: 10),
|
|
const Text(
|
|
'SmartMediBox',
|
|
style: TextStyle(fontSize: 20, fontWeight: FontWeight.bold),
|
|
),
|
|
const SizedBox(height: 10),
|
|
const Text(
|
|
'Pilih kotak obat untuk atur jadwal obat',
|
|
textAlign: TextAlign.center,
|
|
style: TextStyle(fontSize: 14, color: Colors.black54),
|
|
),
|
|
const SizedBox(height: 30),
|
|
|
|
// Tombol Jadwal Kotak Obat 1 dengan pengecekan akses
|
|
ElevatedButton(
|
|
onPressed:
|
|
_canAccessKotak1
|
|
? () {
|
|
Navigator.push(
|
|
context,
|
|
MaterialPageRoute(
|
|
builder:
|
|
(context) =>
|
|
const JadwalKotakObat1Screen(),
|
|
),
|
|
);
|
|
}
|
|
: () => _showAccessDeniedDialog('Kotak Obat 1'),
|
|
style: ElevatedButton.styleFrom(
|
|
backgroundColor:
|
|
_canAccessKotak1
|
|
? const Color(0xFF3FA535)
|
|
: Colors.grey,
|
|
padding: const EdgeInsets.symmetric(vertical: 16),
|
|
minimumSize: const Size.fromHeight(50),
|
|
shape: RoundedRectangleBorder(
|
|
borderRadius: BorderRadius.circular(12),
|
|
),
|
|
),
|
|
child: Text(
|
|
'Jadwal Kotak Obat 1',
|
|
style: TextStyle(
|
|
fontSize: 16,
|
|
color: _canAccessKotak1 ? Colors.white : Colors.white70,
|
|
),
|
|
),
|
|
),
|
|
|
|
const SizedBox(height: 20),
|
|
|
|
// Tombol Jadwal Kotak Obat 2 dengan pengecekan akses
|
|
ElevatedButton(
|
|
onPressed:
|
|
_canAccessKotak2
|
|
? () {
|
|
Navigator.push(
|
|
context,
|
|
MaterialPageRoute(
|
|
builder:
|
|
(context) =>
|
|
const JadwalKotakObat2Screen(),
|
|
),
|
|
);
|
|
}
|
|
: () => _showAccessDeniedDialog('Kotak Obat 2'),
|
|
style: ElevatedButton.styleFrom(
|
|
backgroundColor:
|
|
_canAccessKotak2
|
|
? const Color(0xFF3FA535)
|
|
: Colors.grey,
|
|
padding: const EdgeInsets.symmetric(vertical: 16),
|
|
minimumSize: const Size.fromHeight(50),
|
|
shape: RoundedRectangleBorder(
|
|
borderRadius: BorderRadius.circular(12),
|
|
),
|
|
),
|
|
child: Text(
|
|
'Jadwal Kotak Obat 2',
|
|
style: TextStyle(
|
|
fontSize: 16,
|
|
color: _canAccessKotak2 ? Colors.white : Colors.white70,
|
|
),
|
|
),
|
|
),
|
|
],
|
|
),
|
|
),
|
|
// ======== TAMBAHAN CONTAINER SESUAI DESAIN =========
|
|
Container(
|
|
margin: const EdgeInsets.symmetric(horizontal: 24, vertical: 0),
|
|
padding: const EdgeInsets.all(24),
|
|
decoration: BoxDecoration(
|
|
color: Colors.white,
|
|
borderRadius: BorderRadius.circular(24),
|
|
boxShadow: [
|
|
BoxShadow(
|
|
color: Colors.grey.shade300,
|
|
blurRadius: 10,
|
|
offset: const Offset(0, 5),
|
|
),
|
|
],
|
|
),
|
|
child: Column(
|
|
children: [
|
|
// Row Tombol Kotak 1 & Kotak 2
|
|
Row(
|
|
mainAxisAlignment: MainAxisAlignment.spaceBetween,
|
|
children: [
|
|
// Tombol Kotak 1
|
|
Expanded(
|
|
child: Column(
|
|
children: [
|
|
Text(
|
|
"Tombol Kotak 1",
|
|
style: const TextStyle(
|
|
fontWeight: FontWeight.bold,
|
|
fontSize: 16,
|
|
),
|
|
),
|
|
const SizedBox(height: 6),
|
|
ElevatedButton(
|
|
onPressed: _toggleButton1,
|
|
style: ElevatedButton.styleFrom(
|
|
backgroundColor:
|
|
button1 ? Colors.green : Colors.red,
|
|
minimumSize: const Size(80, 36),
|
|
padding: const EdgeInsets.symmetric(
|
|
horizontal: 8,
|
|
),
|
|
),
|
|
child: Text(
|
|
button1 ? 'ON' : 'OFF',
|
|
style: const TextStyle(
|
|
color: Colors.white,
|
|
fontWeight: FontWeight.bold,
|
|
),
|
|
),
|
|
),
|
|
],
|
|
),
|
|
),
|
|
const SizedBox(width: 16),
|
|
// Tombol Kotak 2
|
|
Expanded(
|
|
child: Column(
|
|
children: [
|
|
Text(
|
|
"Tombol Kotak 2",
|
|
style: const TextStyle(
|
|
fontWeight: FontWeight.bold,
|
|
fontSize: 16,
|
|
),
|
|
),
|
|
const SizedBox(height: 6),
|
|
ElevatedButton(
|
|
onPressed: _toggleButton2,
|
|
style: ElevatedButton.styleFrom(
|
|
backgroundColor:
|
|
button2 ? Colors.green : Colors.red,
|
|
minimumSize: const Size(80, 36),
|
|
padding: const EdgeInsets.symmetric(
|
|
horizontal: 8,
|
|
),
|
|
),
|
|
child: Text(
|
|
button2 ? 'ON' : 'OFF',
|
|
style: const TextStyle(
|
|
color: Colors.white,
|
|
fontWeight: FontWeight.bold,
|
|
),
|
|
),
|
|
),
|
|
],
|
|
),
|
|
),
|
|
],
|
|
),
|
|
const SizedBox(height: 16),
|
|
// Row Buzzer & Lampu (status read-only)
|
|
Row(
|
|
mainAxisAlignment: MainAxisAlignment.spaceBetween,
|
|
children: [
|
|
// Buzzer
|
|
Expanded(
|
|
child: Column(
|
|
children: [
|
|
Text(
|
|
"Buzzer",
|
|
style: const TextStyle(
|
|
fontWeight: FontWeight.bold,
|
|
fontSize: 16,
|
|
),
|
|
),
|
|
const SizedBox(height: 6),
|
|
Container(
|
|
alignment: Alignment.center,
|
|
decoration: BoxDecoration(
|
|
color: buzzer ? Colors.green : Colors.red,
|
|
borderRadius: BorderRadius.circular(8),
|
|
),
|
|
padding: const EdgeInsets.symmetric(vertical: 8),
|
|
child: Text(
|
|
buzzer ? 'ON' : 'OFF',
|
|
style: const TextStyle(
|
|
color: Colors.white,
|
|
fontWeight: FontWeight.bold,
|
|
),
|
|
),
|
|
),
|
|
],
|
|
),
|
|
),
|
|
const SizedBox(width: 16),
|
|
// Lampu
|
|
Expanded(
|
|
child: Column(
|
|
children: [
|
|
Text(
|
|
"Lampu",
|
|
style: const TextStyle(
|
|
fontWeight: FontWeight.bold,
|
|
fontSize: 16,
|
|
),
|
|
),
|
|
const SizedBox(height: 6),
|
|
Container(
|
|
alignment: Alignment.center,
|
|
decoration: BoxDecoration(
|
|
color: lampu ? Colors.green : Colors.red,
|
|
borderRadius: BorderRadius.circular(8),
|
|
),
|
|
padding: const EdgeInsets.symmetric(vertical: 8),
|
|
child: Text(
|
|
lampu ? 'ON' : 'OFF',
|
|
style: const TextStyle(
|
|
color: Colors.white,
|
|
fontWeight: FontWeight.bold,
|
|
),
|
|
),
|
|
),
|
|
],
|
|
),
|
|
),
|
|
],
|
|
),
|
|
const SizedBox(height: 20),
|
|
// Info Text
|
|
Container(
|
|
width: double.infinity,
|
|
padding: const EdgeInsets.symmetric(
|
|
vertical: 16,
|
|
horizontal: 20,
|
|
),
|
|
decoration: BoxDecoration(
|
|
color: Colors.white,
|
|
borderRadius: BorderRadius.circular(16),
|
|
boxShadow: [
|
|
BoxShadow(
|
|
color: Colors.black12,
|
|
blurRadius: 4,
|
|
offset: const Offset(0, 2),
|
|
),
|
|
],
|
|
),
|
|
child: Text(
|
|
"Info :\n$infoText",
|
|
textAlign: TextAlign.center,
|
|
style: const TextStyle(
|
|
fontWeight: FontWeight.bold,
|
|
fontSize: 16,
|
|
height: 1.4,
|
|
),
|
|
),
|
|
),
|
|
],
|
|
),
|
|
),
|
|
],
|
|
),
|
|
),
|
|
);
|
|
}
|
|
}
|