This commit is contained in:
kleponijo 2026-04-12 21:11:40 +07:00
parent 60b2047eae
commit 1239b39e30
11 changed files with 151 additions and 124 deletions

BIN
images/atmosfer.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 14 KiB

BIN
images/google-icon-logo.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 15 KiB

View File

@ -1,50 +1 @@
import 'dart:async';
import 'package:bloc/bloc.dart';
import 'package:equatable/equatable.dart';
import 'package:firebase_database/firebase_database.dart';
part 'monitoring_event.dart';
part 'monitoring_state.dart';
class MonitoringBloc extends Bloc<MonitoringEvent, MonitoringState> {
final DatabaseReference _realtimeRef =
FirebaseDatabase.instance.ref('anemometer/realtime');
StreamSubscription<DatabaseEvent>? _realtimeSub;
MonitoringBloc() : super(const MonitoringState.initial()) {
on<LoadMonitoringData>(_onLoadMonitoringData);
on<MonitoringDataUpdated>(_onMonitoringDataUpdated);
}
void _onLoadMonitoringData(
LoadMonitoringData event, Emitter<MonitoringState> emit) {
emit(const MonitoringState.loading());
// Start listening to realtime data
_realtimeSub = _realtimeRef.onValue.listen((event) {
final data = event.snapshot.value;
if (data is Map) {
final speed = (data['kecepatan'] ?? 0).toDouble();
final timestamp = data['timestamp'] ?? 0;
final waktu = DateTime.fromMillisecondsSinceEpoch(timestamp * 1000);
add(MonitoringDataUpdated(speed: speed, timestamp: waktu));
}
});
}
void _onMonitoringDataUpdated(
MonitoringDataUpdated event, Emitter<MonitoringState> emit) {
emit(MonitoringState.loaded(
currentSpeed: event.speed,
lastUpdateTime: event.timestamp,
));
}
@override
Future<void> close() {
_realtimeSub?.cancel();
return super.close();
}
}

View File

@ -1,25 +1 @@
part of 'monitoring_bloc.dart';
sealed class MonitoringEvent extends Equatable {
const MonitoringEvent();
@override
List<Object> get props => [];
}
class LoadMonitoringData extends MonitoringEvent {
const LoadMonitoringData();
}
class MonitoringDataUpdated extends MonitoringEvent {
final double speed;
final DateTime timestamp;
const MonitoringDataUpdated({
required this.speed,
required this.timestamp,
});
@override
List<Object> get props => [speed, timestamp];
}

View File

@ -1,34 +1 @@
part of 'monitoring_bloc.dart';
class MonitoringState extends Equatable {
final MonitoringStatus status;
final double? currentSpeed;
final DateTime? lastUpdateTime;
const MonitoringState._({
this.status = MonitoringStatus.initial,
this.currentSpeed,
this.lastUpdateTime,
});
const MonitoringState.initial() : this._();
const MonitoringState.loading() : this._(status: MonitoringStatus.loading);
const MonitoringState.loaded({
required double currentSpeed,
required DateTime lastUpdateTime,
}) : this._(
status: MonitoringStatus.loaded,
currentSpeed: currentSpeed,
lastUpdateTime: lastUpdateTime,
);
const MonitoringState.error(String message)
: this._(status: MonitoringStatus.error);
@override
List<Object?> get props => [status, currentSpeed, lastUpdateTime];
}
enum MonitoringStatus { initial, loading, loaded, error }

View File

@ -124,19 +124,22 @@ class _SignInScreenState extends State<SignInScreen> {
)),
)
: const CircularProgressIndicator(),
/// ======= Tombol Login Google ======= ///
const SizedBox(height: 20),
SizedBox(
width: MediaQuery.of(context).size.width * 0.5,
child: TextButton.icon(
onPressed: () {
// == Mengirim event login Google ke Bloc == //
context.read<SignInBloc>().add(GoogleSignInRequired());
},
icon: const Text(
'G',
style: TextStyle(
fontSize: 20,
fontWeight: FontWeight.bold,
color: Colors.blue,
icon: SizedBox(
height: 20,
width: 20,
child: Image.asset(
'images/google-icon-logo.png',
fit: BoxFit.contain,
),
),
label: const Text(

View File

@ -234,16 +234,16 @@ class _SignUpScreenState extends State<SignUpScreen> {
.read<SignUpBloc>()
.add(GoogleSignInRequired());
},
icon: const Text(
'G',
style: TextStyle(
fontSize: 20,
fontWeight: FontWeight.bold,
color: Colors.blue,
icon: SizedBox(
height: 20,
width: 20,
child: Image.asset(
'images/google-icon-logo.png',
fit: BoxFit.contain,
),
),
label: const Text(
'Daftar dengan Google',
'Sign In With Google',
style: TextStyle(
color: Colors.black,
fontSize: 16,

View File

@ -1,10 +1,66 @@
import 'package:flutter/material.dart';
import 'package:google_fonts/google_fonts.dart';
import 'package:flutter_bloc/flutter_bloc.dart';
import '../../../blocs/authentication_bloc/authentication_bloc.dart';
import 'main_drawer.dart';
class HomeScreen extends StatelessWidget {
const HomeScreen({super.key});
@override
Widget build(BuildContext context) {
return Scaffold();
return Scaffold(
drawer: const MainDrawer(),
appBar: AppBar(
backgroundColor: Colors.white,
elevation: 0.5, // Kasih sedikit bayangan tipis agar elegan
centerTitle: false,
leading: Builder(
builder: (context) => IconButton(
icon: const Icon(Icons.menu,
color: Colors.black), // Ikon garis 3 horizontal
onPressed: () {
// ini kodenya untuk membuka:
Scaffold.of(context).openDrawer();
},
)),
title: Row(
children: [
// Ganti dengan Image.asset jika sudah ada logo
const Icon(Icons.cloud_queue, color: Colors.blue, size: 28),
const SizedBox(width: 10),
Text(
"Klimatologi",
style: GoogleFonts.rubik(
color: Colors.black,
fontWeight: FontWeight.bold,
fontSize: 20,
),
),
],
),
actions: [
IconButton(
icon: const Icon(Icons.notifications_none, color: Colors.black),
onPressed: () {
// Aksi notifikasi
},
),
IconButton(
icon: const Icon(Icons.logout, color: Colors.black),
onPressed: () {
// Trigger Logout di AuthenticationBloc kamu
context
.read<AuthenticationBloc>()
.add(AuthenticationLogoutRequested());
},
),
const SizedBox(width: 8),
],
),
body: const Center(
child: Text("body home page"),
),
);
}
}

View File

@ -0,0 +1,74 @@
import 'package:flutter/material.dart';
import 'package:flutter_bloc/flutter_bloc.dart';
import '../../../blocs/authentication_bloc/authentication_bloc.dart';
class MainDrawer extends StatelessWidget {
const MainDrawer({super.key});
@override
Widget build(BuildContext context) {
return Drawer(
child: Column(
children: [
// Bagian Header (Tempat Nama/Email)
BlocBuilder<AuthenticationBloc, AuthenticationState>(
builder: (context, state) {
// Mengambil email user dari Bloc Global
String userEmail = state.user?.email ?? "Guest";
String userName =
userEmail.split('@')[0]; // Ambil depan email saja
return UserAccountsDrawerHeader(
decoration: const BoxDecoration(color: Colors.blue),
currentAccountPicture: const CircleAvatar(
backgroundColor: Colors.white,
child: Icon(Icons.person, size: 40, color: Colors.blue),
),
accountName: Text(
userName,
style: const TextStyle(fontWeight: FontWeight.bold),
),
accountEmail: Text(userEmail),
);
},
),
// Daftar Menu
ListTile(
leading: const Icon(Icons.speed),
title: const Text("Wind Speed"),
onTap: () {
// Navigasi ke Wind Speed
},
),
ListTile(
leading: const Icon(Icons.water_drop),
title: const Text("Evaporasi"),
onTap: () {},
),
ListTile(
leading: Image.asset(
'images/atmosfer.png',
height: 20,
width: 20,
),
title: const Text("Udara"),
onTap: () {},
),
const Spacer(), // Dorong menu logout ke paling bawah
const Divider(),
ListTile(
leading: const Icon(Icons.logout, color: Colors.red),
title: const Text("Logout", style: TextStyle(color: Colors.red)),
onTap: () {
context
.read<AuthenticationBloc>()
.add(AuthenticationLogoutRequested());
},
),
const SizedBox(height: 20),
],
),
);
}
}

View File

@ -1,6 +1,6 @@
import 'dart:developer';
// import 'dart:developer';
import 'dart:async';
import 'package:rxdart/rxdart.dart';
// import 'package:rxdart/rxdart.dart';
import 'package:firebase_database/firebase_database.dart';
import 'package:monitoring_repository/monitoring_repository.dart';

View File

@ -31,5 +31,5 @@ dev_dependencies:
sdk: flutter
flutter:
uses-material-design: true
# assets:
# - assets/images/
assets:
- images/