Upload files to "Source Code Mobile"

This commit is contained in:
Dwikyyy 2026-07-30 12:52:37 +07:00
parent 97e78213c7
commit 7fc2e49904
5 changed files with 1622 additions and 0 deletions

View File

@ -0,0 +1,92 @@
import 'package:flutter/material.dart';
import 'package:provider/provider.dart';
import '../providers/history_provider.dart';
class ControlHistoryPage extends StatefulWidget {
const ControlHistoryPage({super.key});
@override
State<ControlHistoryPage> createState() => _ControlHistoryPageState();
}
class _ControlHistoryPageState extends State<ControlHistoryPage> {
@override
void initState() {
super.initState();
context.read<HistoryProvider>().startControlListener();
}
@override
Widget build(BuildContext context) {
final provider = context.watch<HistoryProvider>();
return Scaffold(
appBar: AppBar(
title: const Text('Histori Kontrol'),
actions: [
IconButton(
icon: const Icon(Icons.delete_outline, color: Colors.redAccent),
tooltip: 'Hapus Histori',
onPressed: () {
showDialog(
context: context,
builder: (context) => AlertDialog(
title: const Text('Hapus Histori'),
content: const Text('Apakah Anda yakin ingin menghapus semua data histori kontrol? Tindakan ini tidak dapat dibatalkan.'),
actions: [
TextButton(
onPressed: () => Navigator.pop(context),
child: const Text('Batal'),
),
TextButton(
onPressed: () async {
Navigator.pop(context);
try {
await context.read<HistoryProvider>().clearControlHistory();
if (context.mounted) {
ScaffoldMessenger.of(context).showSnackBar(
const SnackBar(content: Text('Histori kontrol berhasil dihapus')),
);
}
} catch (e) {
if (context.mounted) {
ScaffoldMessenger.of(context).showSnackBar(
SnackBar(content: Text('Gagal menghapus histori: $e')),
);
}
}
},
child: const Text('Hapus', style: TextStyle(color: Colors.red)),
),
],
),
);
},
),
],
),
body: provider.controlHistory.isEmpty
? const Center(
child: Text(
'Belum ada data histori kontrol.',
style: TextStyle(color: Colors.grey),
),
)
: ListView.builder(
itemCount: provider.controlHistory.length,
itemBuilder: (context, index) {
final item = provider.controlHistory[index];
return Card(
child: ListTile(
title: Text(item.action),
subtitle: Text('${item.source}\n${item.timestamp}'),
),
);
},
),
);
}
}

File diff suppressed because it is too large Load Diff

View File

@ -0,0 +1,135 @@
import 'package:flutter/material.dart';
import 'package:provider/provider.dart';
import '../providers/auth_provider.dart';
import 'dashboard_page.dart';
import 'register_page.dart';
class LoginPage extends StatefulWidget {
const LoginPage({super.key});
@override
State<LoginPage> createState() => _LoginPageState();
}
class _LoginPageState extends State<LoginPage> {
final _emailController = TextEditingController();
final _passwordController = TextEditingController();
final _formKey = GlobalKey<FormState>();
Future<void> login() async {
if (!_formKey.currentState!.validate()) {
return;
}
final auth = context.read<AuthProvider>();
final result = await auth.login(
email: _emailController.text.trim(),
password: _passwordController.text,
);
if (!mounted) return;
if (result == null) {
Navigator.pushReplacement(
context,
MaterialPageRoute(builder: (_) => const DashboardPage()),
);
} else {
ScaffoldMessenger.of(
context,
).showSnackBar(SnackBar(content: Text(result)));
}
}
@override
Widget build(BuildContext context) {
final auth = context.watch<AuthProvider>();
return Scaffold(
body: SafeArea(
child: Center(
child: SingleChildScrollView(
padding: const EdgeInsets.all(24),
child: Form(
key: _formKey,
child: Column(
children: [
const Icon(Icons.air, size: 100, color: Colors.green),
const SizedBox(height: 20),
const Text(
'Smart Air Quality',
style: TextStyle(fontSize: 28, fontWeight: FontWeight.bold),
),
const SizedBox(height: 50),
TextFormField(
controller: _emailController,
decoration: const InputDecoration(
labelText: 'Email',
border: OutlineInputBorder(),
),
validator: (value) {
if (value == null || value.isEmpty) {
return 'Email wajib diisi';
}
return null;
},
),
const SizedBox(height: 20),
TextFormField(
controller: _passwordController,
obscureText: true,
decoration: const InputDecoration(
labelText: 'Password',
border: OutlineInputBorder(),
),
validator: (value) {
if (value == null || value.isEmpty) {
return 'Password wajib diisi';
}
return null;
},
),
const SizedBox(height: 30),
SizedBox(
width: double.infinity,
height: 50,
child: ElevatedButton(
onPressed: auth.isLoading ? null : login,
child: auth.isLoading
? const CircularProgressIndicator()
: const Text('Login'),
),
),
const SizedBox(height: 20),
TextButton(
onPressed: () {
Navigator.push(
context,
MaterialPageRoute(builder: (_) => const RegisterPage()),
);
},
child: const Text('Belum punya akun? Register'),
),
],
),
),
),
),
),
);
}
}

View File

@ -0,0 +1,257 @@
import 'package:flutter/material.dart';
import 'package:provider/provider.dart';
import '../providers/history_provider.dart';
import '../core/app_color.dart';
import 'control_history_page.dart';
class MonitoringHistoryPage extends StatefulWidget {
const MonitoringHistoryPage({super.key});
@override
State<MonitoringHistoryPage> createState() => _MonitoringHistoryPageState();
}
class _MonitoringHistoryPageState extends State<MonitoringHistoryPage> {
@override
void initState() {
super.initState();
context.read<HistoryProvider>().startMonitoringListener();
}
@override
Widget build(BuildContext context) {
final provider = context.watch<HistoryProvider>();
final history = provider.monitoringHistory;
return Scaffold(
backgroundColor: AppColor.background,
appBar: AppBar(
title: const Text('Histori Monitoring'),
actions: [
IconButton(
icon: const Icon(Icons.delete_outline, color: Colors.redAccent),
tooltip: 'Hapus Histori',
onPressed: () {
showDialog(
context: context,
builder: (context) => AlertDialog(
title: const Text('Hapus Histori'),
content: const Text('Apakah Anda yakin ingin menghapus semua data histori monitoring? Tindakan ini tidak dapat dibatalkan.'),
actions: [
TextButton(
onPressed: () => Navigator.pop(context),
child: const Text('Batal'),
),
TextButton(
onPressed: () async {
Navigator.pop(context);
try {
await context.read<HistoryProvider>().clearMonitoringHistory();
if (context.mounted) {
ScaffoldMessenger.of(context).showSnackBar(
const SnackBar(content: Text('Histori monitoring berhasil dihapus')),
);
}
} catch (e) {
if (context.mounted) {
ScaffoldMessenger.of(context).showSnackBar(
SnackBar(content: Text('Gagal menghapus histori: $e')),
);
}
}
},
child: const Text('Hapus', style: TextStyle(color: Colors.red)),
),
],
),
);
},
),
],
),
body: Padding(
padding: const EdgeInsets.all(16.0),
child: Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
// Table Header Title + Button to Control History
Row(
mainAxisAlignment: MainAxisAlignment.spaceBetween,
children: [
const Text(
'Riwayat (setiap 5 menit)',
style: TextStyle(
fontSize: 15,
fontWeight: FontWeight.bold,
color: Colors.white70,
),
),
TextButton.icon(
onPressed: () {
Navigator.push(
context,
MaterialPageRoute(builder: (_) => const ControlHistoryPage()),
);
},
icon: const Icon(Icons.history, size: 16, color: Colors.blueAccent),
label: const Text(
'Histori Kontrol',
style: TextStyle(color: Colors.blueAccent, fontSize: 13, fontWeight: FontWeight.bold),
),
style: TextButton.styleFrom(
padding: const EdgeInsets.symmetric(horizontal: 8, vertical: 4),
),
),
],
),
const SizedBox(height: 12),
// Main Table Card
Expanded(
child: Card(
color: AppColor.cardBg,
child: Padding(
padding: const EdgeInsets.all(12.0),
child: Column(
children: [
// Column Headers
Row(
children: const [
Expanded(
flex: 4,
child: Text(
'Waktu',
style: TextStyle(
fontWeight: FontWeight.bold,
color: Colors.grey,
fontSize: 12,
),
),
),
Expanded(
flex: 2,
child: Text(
'PM2.5',
textAlign: TextAlign.center,
style: TextStyle(
fontWeight: FontWeight.bold,
color: Colors.grey,
fontSize: 12,
),
),
),
Expanded(
flex: 2,
child: Text(
'CO₂',
textAlign: TextAlign.center,
style: TextStyle(
fontWeight: FontWeight.bold,
color: Colors.grey,
fontSize: 12,
),
),
),
Expanded(
flex: 3,
child: Text(
'Suhu / Lembap',
textAlign: TextAlign.center,
style: TextStyle(
fontWeight: FontWeight.bold,
color: Colors.grey,
fontSize: 12,
),
),
),
],
),
const SizedBox(height: 10),
const Divider(color: Colors.white24, height: 1),
const SizedBox(height: 10),
// Rows List
Expanded(
child: history.isEmpty
? const Center(
child: Text(
'Belum ada data histori.',
style: TextStyle(color: Colors.grey),
),
)
: ListView.separated(
itemCount: history.length,
separatorBuilder: (context, i) => const Divider(
color: Colors.white12,
height: 16,
),
itemBuilder: (context, index) {
final item = history[index];
// Format timestamp for display (shorten year/seconds if needed)
String displayTime = item.timestamp;
if (displayTime.length > 16) {
displayTime = displayTime.substring(0, 16);
}
return Row(
children: [
Expanded(
flex: 4,
child: Text(
displayTime,
style: const TextStyle(
color: Colors.white,
fontSize: 11,
),
),
),
Expanded(
flex: 2,
child: Text(
'${item.pm25}',
textAlign: TextAlign.center,
style: const TextStyle(
color: Colors.white,
fontSize: 11,
),
),
),
Expanded(
flex: 2,
child: Text(
'${item.co2.round()}',
textAlign: TextAlign.center,
style: const TextStyle(
color: Colors.white,
fontSize: 11,
),
),
),
Expanded(
flex: 3,
child: Text(
'${item.temperature.round()}°C / ${item.humidity.round()}%',
textAlign: TextAlign.center,
style: const TextStyle(
color: Colors.white,
fontSize: 11,
),
),
),
],
);
},
),
),
],
),
),
),
),
],
),
),
);
}
}

View File

@ -0,0 +1,46 @@
import 'package:flutter/material.dart';
import 'package:provider/provider.dart';
import '../providers/auth_provider.dart';
import 'login_page.dart';
class ProfilePage extends StatelessWidget {
const ProfilePage({super.key});
@override
Widget build(BuildContext context) {
final auth = context.watch<AuthProvider>();
return Scaffold(
appBar: AppBar(title: const Text('Profil')),
body: Padding(
padding: const EdgeInsets.all(16),
child: Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
Text('Nama : ${auth.userData?['name'] ?? '-'}'),
const SizedBox(height: 10),
Text('Email : ${auth.userData?['email'] ?? '-'}'),
const SizedBox(height: 30),
ElevatedButton(
onPressed: () async {
await auth.logout();
if (!context.mounted) {
return;
}
Navigator.pushAndRemoveUntil(
context,
MaterialPageRoute(builder: (_) => const LoginPage()),
(route) => false,
);
},
child: const Text('Logout'),
),
],
),
),
);
}
}