diff --git a/TODO.md b/TODO.md
index 5d401e8..238b31f 100644
--- a/TODO.md
+++ b/TODO.md
@@ -1,10 +1,16 @@
-## TODO - Evaporasi (Dual Axis + History Firebase)
+# TODO - Fix flutter analyze issues
-### Checklist
-- [x] Cek: List Evaporasi sudah mengambil `state.history` dari Firebase `getSensorHistory`.
-- [x] Cek: Filter custom date pada list menggunakan `state.history` (data terdahulu sudah tersedia).
-- [x] Ubah grafik evaporasi menjadi **dual-axis** (melalui mapping skala + label kiri/kanan).
-- [ ] Perbaiki mismatch chart & list dengan sumber history Firebase (sinkronisasi agregasi/label/period + timezone).
-- [ ] Jalankan `flutter analyze` dan minimal compile aplikasi.
+## Step 1: Fix evaporasi.dart parse conflict
+- Remove leftover git conflict markers in packages/monitoring_repository/lib/src/models/evaporasi.dart
+- Unify timestamp parsing logic into a single implementation
+- Ensure `factory Evaporasi.fromJson` always returns a non-null `Evaporasi`
+## Step 2: Fix Google Sign-In import errors
+- Investigate why `package:google_sign_in/google_sign_in.dart` is reported missing
+- Update user_repository dependency versions if needed
+- Run `flutter pub get` (from c:/flutter/klimatologi) and `flutter analyze` again
+
+## Step 3: Re-run analyze and address remaining warnings
+- Run `flutter analyze` and fix any remaining compile errors
+- Optionally clean up performance/deprecation warnings (const constructors, withOpacity deprecation, Share->SharePlus)
diff --git a/analyze_output.txt b/analyze_output.txt
new file mode 100644
index 0000000..2dead12
Binary files /dev/null and b/analyze_output.txt differ
diff --git a/android/build/reports/problems/problems-report.html b/android/build/reports/problems/problems-report.html
new file mode 100644
index 0000000..6bb5e2c
--- /dev/null
+++ b/android/build/reports/problems/problems-report.html
@@ -0,0 +1,663 @@
+
+
+
+
+
+
+
+
+
+
+
+
+ Gradle Configuration Cache
+
+
+
+
+
+
+ Loading...
+
+
+
+
+
+
+
diff --git a/blackboxai_plan.md b/blackboxai_plan.md
new file mode 100644
index 0000000..d52d913
--- /dev/null
+++ b/blackboxai_plan.md
@@ -0,0 +1,48 @@
+# Edit plan (approved before edits)
+
+## Information gathered
+- `flutter analyze` reports many issues, but the compile-stopping ones are:
+ 1) `packages/monitoring_repository/lib/src/models/evaporasi.dart` contains unresolved git merge conflict markers (`<<<<<<< HEAD`, `=======`, `>>>>>>> ...`) and thus causes syntax errors and incorrect factory return type/assignments.
+ 2) `packages/user_repository/lib/src/firebase_user_repo.dart` cannot resolve `package:google_sign_in/google_sign_in.dart` and related symbols (`GoogleSignIn`, `GoogleSignInAuthentication`).
+- Read `evaporasi.dart`: conflict markers exist inside `Evaporasi.fromJson`, causing broken code.
+- Read `firebase_user_repo.dart`: it imports `package:google_sign_in/google_sign_in.dart` and uses `GoogleSignIn`, `GoogleSignInAuthentication`.
+- Read `packages/monitoring_repository/pubspec.yaml`: no `google_sign_in` dependency there (likely only in user_repository).
+- Read `packages/user_repository/pubspec.yaml`: `google_sign_in: ^6.3.0` is present.
+- `search_files` for conflict markers returned none (likely because pattern needs exact marker search or repo search mismatch), but reading the file shows markers are present.
+
+## Plan
+### Step A — Fix evaporasi.dart merge conflict
+- Edit `packages/monitoring_repository/lib/src/models/evaporasi.dart`:
+ - Remove all `<<<<<<< HEAD`, `=======`, `>>>>>>> ...` markers.
+ - Consolidate timestamp parsing:
+ - Keep a robust `parseTimestamp(dynamic raw)` helper.
+ - Use one `rawTimestamp` priority order: `json['timestamp'] ?? json['time'] ?? json['datetime']`.
+ - Keep legacy fallback from `json['waktu']` (HH:mm:ss) if other timestamp fields are missing.
+ - Keep filtering for evaporasi (0..50) and tinggiAir (0..100) and sensor sanity for suhu (-50..100).
+ - Ensure `factory Evaporasi.fromJson(...)` always returns an `Evaporasi` with a non-null `timestamp`.
+
+### Step B — Fix google_sign_in missing import/class errors
+Because `google_sign_in` is declared but analyzer can't find the Dart library:
+- Check whether the dependency actually downloaded/available. The simplest fix path is:
+ - Run `flutter pub get` correctly (Windows shell issue is present: `&&` is not valid in the terminal context).
+ - Re-run `flutter analyze` to see if the missing import resolves.
+- If still failing:
+ - Inspect `packages/user_repository/pubspec.lock` / ensure plugin supports current platform (and that the package name/dart import matches).
+ - Update `google_sign_in` version to a compatible one if necessary.
+
+### Step C — Re-run analyze
+- After Step A and Step B, run `flutter analyze` again and fix any remaining compile errors.
+
+## Dependent files to edit
+- `packages/monitoring_repository/lib/src/models/evaporasi.dart`
+- Potentially `packages/user_repository/pubspec.yaml` (only if dependency resolution still fails)
+
+## Followup steps
+- Run `flutter pub get` (from `c:/flutter/klimatologi`).
+- Run `flutter analyze` again.
+- If compile succeeds, optionally address warnings (prefer_const_constructors, deprecated withOpacity, Share -> SharePlus, etc.).
+
+
+Approve Step A (remove merge conflict and fix timestamp parsing in evaporasi.dart). After that I will proceed to Step B (google_sign_in resolution) based on the next analyze output.
+
+
diff --git a/lib/app_view.dart b/lib/app_view.dart
index 0320ddb..f8b4763 100644
--- a/lib/app_view.dart
+++ b/lib/app_view.dart
@@ -1,7 +1,7 @@
import 'package:flutter/material.dart';
import 'package:flutter_bloc/flutter_bloc.dart';
import 'package:klimatologiot/blocs/authentication_bloc/authentication_bloc.dart';
-
+import 'package:flutter_localizations/flutter_localizations.dart';
import 'package:klimatologiot/screens/auth/views/welcome_screen.dart';
import 'package:klimatologiot/screens/home/views/home_screen.dart';
@@ -13,6 +13,15 @@ class MyAppView extends StatelessWidget {
return MaterialApp(
title: 'Klimatologi',
debugShowCheckedModeBanner: false,
+ localizationsDelegates: const [
+ GlobalMaterialLocalizations.delegate,
+ GlobalWidgetsLocalizations.delegate,
+ GlobalCupertinoLocalizations.delegate,
+ ],
+ supportedLocales: const [
+ Locale('id', 'ID'),
+ Locale('en'),
+ ],
theme: ThemeData(
colorScheme: ColorScheme.light(
surface: Colors.grey.shade100,
diff --git a/lib/core/utils/time_series_mapper.dart b/lib/core/utils/time_series_mapper.dart
index 12b1a59..3d61bf4 100644
--- a/lib/core/utils/time_series_mapper.dart
+++ b/lib/core/utils/time_series_mapper.dart
@@ -16,15 +16,17 @@ class TimeSeriesMapper {
final time = getTime(item);
if (_isSameDay(time, now)) {
- final hour = time.hour;
- sums[hour] += getValue(item);
- counts[hour]++;
+ final hour = time.toLocal().hour; // ✅ FIX: pastikan pakai local hour
+ if (hour >= 0 && hour < 24) {
+ sums[hour] += getValue(item);
+ counts[hour]++;
+ }
}
}
return List.generate(24, (i) {
if (counts[i] == 0) return 0;
- return sums[i] / counts[i]; // 🔥 average, bukan overwrite
+ return sums[i] / counts[i];
});
}
@@ -46,12 +48,14 @@ class TimeSeriesMapper {
DateTime(startOfWeek.year, startOfWeek.month, startOfWeek.day);
for (final item in data) {
- final time = getTime(item);
+ final time = getTime(item).toLocal(); // ✅ FIX: konversi ke local
- if (time.isAfter(startOfWeek)) {
+ if (!time.isBefore(startOfWeek)) {
final index = time.weekday - 1;
- sums[index] += getValue(item);
- counts[index]++;
+ if (index >= 0 && index < 7) {
+ sums[index] += getValue(item);
+ counts[index]++;
+ }
}
}
@@ -76,12 +80,14 @@ class TimeSeriesMapper {
final counts = List.filled(daysInMonth, 0);
for (final item in data) {
- final time = getTime(item);
+ final time = getTime(item).toLocal(); // ✅ FIX: konversi ke local
if (time.month == now.month && time.year == now.year) {
final index = time.day - 1;
- sums[index] += getValue(item);
- counts[index]++;
+ if (index >= 0 && index < daysInMonth) {
+ sums[index] += getValue(item);
+ counts[index]++;
+ }
}
}
@@ -91,7 +97,7 @@ class TimeSeriesMapper {
});
}
-/// =========================
+ /// =========================
/// 📅 SPECIFIC DATE (24 JAM - TANGGAL KHUSUS)
/// =========================
static List toSpecificDate({
@@ -107,9 +113,11 @@ class TimeSeriesMapper {
final time = getTime(item);
if (_isSameDay(time, targetDate)) {
- final hour = time.hour;
- sums[hour] += getValue(item);
- counts[hour]++;
+ final hour = time.toLocal().hour; // ✅ FIX: pastikan pakai local hour
+ if (hour >= 0 && hour < 24) {
+ sums[hour] += getValue(item);
+ counts[hour]++;
+ }
}
}
@@ -120,21 +128,23 @@ class TimeSeriesMapper {
}
/// =========================
- /// 🧠 HELPER
+ /// 🧠 HELPER — Bandingkan tanggal secara LOCAL (bukan UTC)
+ /// ✅ FIX: Firebase datetime "2026-05-14 01:25:25" di-parse sebagai local time,
+ /// jadi perbandingan harus pakai local time juga agar tidak mismatch timezone
/// =========================
- /// Sinkronisasi tanggal untuk menghindari mismatch akibat timezone (UTC vs local).
- /// Kita bandingkan berdasarkan UTC.
static bool _isSameDay(DateTime a, DateTime b) {
- final au = a.toUtc();
- final bu = b.toUtc();
- return au.year == bu.year && au.month == bu.month && au.day == bu.day;
+ final al = a.toLocal();
+ final bl = b.toLocal();
+ return al.year == bl.year && al.month == bl.month && al.day == bl.day;
}
-
+ /// =========================
+ /// 📈 SMOOTH (moving average 3 titik)
+ /// =========================
static List smooth(List data) {
if (data.length < 3) return data;
- List result = [];
+ final List result = [];
for (int i = 0; i < data.length; i++) {
if (i == 0 || i == data.length - 1) {
@@ -147,4 +157,4 @@ class TimeSeriesMapper {
return result;
}
-}
+}
\ No newline at end of file
diff --git a/lib/main.dart b/lib/main.dart
index f0093ed..f7a8f1f 100644
--- a/lib/main.dart
+++ b/lib/main.dart
@@ -1,7 +1,5 @@
import 'package:flutter/material.dart';
import 'package:firebase_core/firebase_core.dart';
-import 'package:flutter_bloc/flutter_bloc.dart';
-import 'package:klimatologiot/simple_bloc_observer.dart';
import 'package:klimatologiot/app.dart';
import 'package:user_repository/user_repository.dart';
import 'package:monitoring_repository/monitoring_repository.dart';
@@ -14,9 +12,9 @@ void main() async {
options: DefaultFirebaseOptions.currentPlatform,
);
await initializeDateFormatting('id_ID', null);
- Bloc.observer = SimpleBlocObserver();
runApp(MyApp(
FirebaseUserRepo(),
FirebaseMonitoringRepo(),
));
}
+// sadadsadsadsadasda
diff --git a/lib/screens/monitoring/evaporasi/blocs/evaporasi_bloc.dart b/lib/screens/monitoring/evaporasi/blocs/evaporasi_bloc.dart
index a3f1e56..fab84c6 100644
--- a/lib/screens/monitoring/evaporasi/blocs/evaporasi_bloc.dart
+++ b/lib/screens/monitoring/evaporasi/blocs/evaporasi_bloc.dart
@@ -1,10 +1,11 @@
import 'dart:async';
+
import 'package:bloc/bloc.dart';
import 'package:equatable/equatable.dart';
import 'package:monitoring_repository/monitoring_repository.dart';
-import '../../../../core/utils/time_series_mapper.dart';
import '../../../../blocs/notification_bloc/notification_bloc.dart';
+import '../../../../core/utils/time_series_mapper.dart';
part 'evaporasi_event.dart';
part 'evaporasi_state.dart';
@@ -33,19 +34,20 @@ class EvaporasiBloc extends Bloc {
) async {
emit(state.copyWith(isLoading: true));
- // Ambil history yang akan dipakai untuk list + agregasi grafik.
- final history = await _repository.getSensorHistory(
- 'Monitoring/History',
- (json) => Evaporasi.fromJson(json),
- );
-
- final listData = List.from(history)
+ final history = List.from(
+ await _repository.getSensorHistory(
+ 'Monitoring/History',
+ (json) => Evaporasi.fromJson(json),
+ ),
+ )
..sort((a, b) => a.timestamp.compareTo(b.timestamp));
+ final listData = List.from(history);
+
final dailyGraph = TimeSeriesMapper.toDaily(
data: history,
getTime: (e) => e.timestamp,
- getValue: (e) => e.evaporasi, // ⚠️ sesuaikan nama field
+ getValue: (e) => e.evaporasi,
);
final dailyTempGraph = TimeSeriesMapper.toDaily(
@@ -81,11 +83,13 @@ class EvaporasiBloc extends Bloc {
final lastValue = history.isNotEmpty ? history.last.evaporasi : 0.0;
final lastWaterLevel = history.isNotEmpty ? history.last.tinggiAir : 0.0;
final lastTemperature = history.isNotEmpty ? history.last.suhu : 0.0;
+
final (status, rain) = _computeWeatherStatus(lastValue);
_emitEvaporasiAlert(status, rain, lastValue);
emit(state.copyWith(
history: history,
+ listData: listData,
currentValue: lastValue,
waterLevel: lastWaterLevel,
temperature: lastTemperature,
@@ -98,35 +102,53 @@ class EvaporasiBloc extends Bloc {
chartLabels: _buildChartLabels(period: 'Hari Ini'),
weatherStatus: status,
willRain: rain,
- listData: listData,
+ currentData: history.isNotEmpty ? history.last : null,
+ viewMode: EvaporasiViewMode.period,
+ selectedDate: null,
isLoading: false,
));
await _subscription?.cancel();
_subscription = _repository
- .getSensorStream('Monitoring', (json) => Evaporasi.fromJson(json))
+ .getSensorStream('Monitoring/History', _latestHistoryEntry)
.listen((data) => add(_EvaporasiRealtimeUpdated(data)));
}
+ Evaporasi _latestHistoryEntry(Map json) {
+ if (json.isEmpty) return Evaporasi.empty;
+
+ final entries = json.values
+ .whereType