This commit is contained in:
parent
60b2047eae
commit
1239b39e30
Binary file not shown.
|
After Width: | Height: | Size: 14 KiB |
Binary file not shown.
|
After Width: | Height: | Size: 15 KiB |
|
|
@ -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();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
|
||||||
|
|
@ -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];
|
|
||||||
}
|
|
||||||
|
|
|
||||||
|
|
@ -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 }
|
|
||||||
|
|
|
||||||
|
|
@ -124,19 +124,22 @@ class _SignInScreenState extends State<SignInScreen> {
|
||||||
)),
|
)),
|
||||||
)
|
)
|
||||||
: const CircularProgressIndicator(),
|
: const CircularProgressIndicator(),
|
||||||
|
|
||||||
|
/// ======= Tombol Login Google ======= ///
|
||||||
const SizedBox(height: 20),
|
const SizedBox(height: 20),
|
||||||
SizedBox(
|
SizedBox(
|
||||||
width: MediaQuery.of(context).size.width * 0.5,
|
width: MediaQuery.of(context).size.width * 0.5,
|
||||||
child: TextButton.icon(
|
child: TextButton.icon(
|
||||||
onPressed: () {
|
onPressed: () {
|
||||||
|
// == Mengirim event login Google ke Bloc == //
|
||||||
context.read<SignInBloc>().add(GoogleSignInRequired());
|
context.read<SignInBloc>().add(GoogleSignInRequired());
|
||||||
},
|
},
|
||||||
icon: const Text(
|
icon: SizedBox(
|
||||||
'G',
|
height: 20,
|
||||||
style: TextStyle(
|
width: 20,
|
||||||
fontSize: 20,
|
child: Image.asset(
|
||||||
fontWeight: FontWeight.bold,
|
'images/google-icon-logo.png',
|
||||||
color: Colors.blue,
|
fit: BoxFit.contain,
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
label: const Text(
|
label: const Text(
|
||||||
|
|
|
||||||
|
|
@ -234,16 +234,16 @@ class _SignUpScreenState extends State<SignUpScreen> {
|
||||||
.read<SignUpBloc>()
|
.read<SignUpBloc>()
|
||||||
.add(GoogleSignInRequired());
|
.add(GoogleSignInRequired());
|
||||||
},
|
},
|
||||||
icon: const Text(
|
icon: SizedBox(
|
||||||
'G',
|
height: 20,
|
||||||
style: TextStyle(
|
width: 20,
|
||||||
fontSize: 20,
|
child: Image.asset(
|
||||||
fontWeight: FontWeight.bold,
|
'images/google-icon-logo.png',
|
||||||
color: Colors.blue,
|
fit: BoxFit.contain,
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
label: const Text(
|
label: const Text(
|
||||||
'Daftar dengan Google',
|
'Sign In With Google',
|
||||||
style: TextStyle(
|
style: TextStyle(
|
||||||
color: Colors.black,
|
color: Colors.black,
|
||||||
fontSize: 16,
|
fontSize: 16,
|
||||||
|
|
|
||||||
|
|
@ -1,10 +1,66 @@
|
||||||
import 'package:flutter/material.dart';
|
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 {
|
class HomeScreen extends StatelessWidget {
|
||||||
const HomeScreen({super.key});
|
const HomeScreen({super.key});
|
||||||
|
|
||||||
@override
|
@override
|
||||||
Widget build(BuildContext context) {
|
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"),
|
||||||
|
),
|
||||||
|
);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -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),
|
||||||
|
],
|
||||||
|
),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
@ -1,6 +1,6 @@
|
||||||
import 'dart:developer';
|
// import 'dart:developer';
|
||||||
import 'dart:async';
|
import 'dart:async';
|
||||||
import 'package:rxdart/rxdart.dart';
|
// import 'package:rxdart/rxdart.dart';
|
||||||
import 'package:firebase_database/firebase_database.dart';
|
import 'package:firebase_database/firebase_database.dart';
|
||||||
import 'package:monitoring_repository/monitoring_repository.dart';
|
import 'package:monitoring_repository/monitoring_repository.dart';
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -31,5 +31,5 @@ dev_dependencies:
|
||||||
sdk: flutter
|
sdk: flutter
|
||||||
flutter:
|
flutter:
|
||||||
uses-material-design: true
|
uses-material-design: true
|
||||||
# assets:
|
assets:
|
||||||
# - assets/images/
|
- images/
|
||||||
|
|
|
||||||
Loading…
Reference in New Issue