Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
20 changes: 14 additions & 6 deletions lib/app.dart
Original file line number Diff line number Diff line change
Expand Up @@ -40,21 +40,23 @@ import 'package:geopod/services/places_service.dart';
/// The root application widget.
///
/// Uses SolidLogin from solidui for authentication UI.

class App extends StatelessWidget {
const App({super.key});

@override
Widget build(BuildContext context) {
// Register global callback for clearing caches during logout
// This is called once at app startup to ensure caches are cleared
// BEFORE any blocking network operations during logout
// BEFORE any blocking network operations during logout.
registerLogoutCacheCallback(() async {
await PlacesService.clearCache();

// Note: MapSettings are user preferences, not user data,
// so we don't clear them during logout
// so we don't clear them during logout.
});

// Wrap appScaffold to ensure preload happens on navigation
// Wrap appScaffold to ensure preload happens on navigation.
final appWithPreload = _AppScaffoldWrapper(child: appScaffold);

final loginWidget = SolidLogin(
Expand All @@ -76,6 +78,7 @@ class App extends StatelessWidget {

/// Preloads data on app startup for instant map page access.
/// All platforms use this for initial data preloading.

class _StartupPreloader extends StatefulWidget {
const _StartupPreloader({required this.child});

Expand All @@ -91,7 +94,8 @@ class _StartupPreloaderState extends State<_StartupPreloader> {
super.initState();

// Preload map settings on app startup (both guests and logged-in users)
// Places data is now loaded on-demand by the map page
// Places data is now loaded on-demand by the map page.

WidgetsBinding.instance.addPostFrameCallback((_) {
unawaited(preloadMapSettings());
});
Expand All @@ -106,6 +110,7 @@ class _StartupPreloaderState extends State<_StartupPreloader> {
/// Wrapper that triggers preload when navigated to (for Continue button).
/// This ensures data is preloaded even when user navigates via Continue button
/// after the initial app startup preload.

class _AppScaffoldWrapper extends StatefulWidget {
const _AppScaffoldWrapper({required this.child});

Expand All @@ -121,10 +126,13 @@ class _AppScaffoldWrapperState extends State<_AppScaffoldWrapper> {
super.initState();

// Trigger preload when this widget is mounted (after login)
// Only preload local settings, network data will be loaded by pages themselves
// Only preload local settings, network data will be loaded by pages themselves.

WidgetsBinding.instance.addPostFrameCallback((_) {
unawaited(preloadMapSettings());
// Sync settings from POD with delay to avoid network congestion

// Sync settings from POD with delay to avoid network congestion.

unawaited(syncSettingsFromPod());
});
}
Expand Down
22 changes: 13 additions & 9 deletions lib/app_scaffold.dart
Original file line number Diff line number Diff line change
Expand Up @@ -37,10 +37,12 @@ import 'widgets/geomap.dart';
import 'widgets/locations_page.dart';

/// App scaffold widget that responds to fullscreen mode changes.

class AppScaffoldWidget extends StatelessWidget {
const AppScaffoldWidget({super.key});

/// Global key to access the GeoMap state for settings dialog.

static final GlobalKey<GeoMapWidgetState> geoMapKey =
GlobalKey<GeoMapWidgetState>();

Expand All @@ -50,18 +52,19 @@ class AppScaffoldWidget extends StatelessWidget {
valueListenable: fullscreenModeNotifier,
builder: (context, isFullscreen, child) {
if (isFullscreen) {
// Fullscreen mode: show only the Home content without navigation
// Fullscreen mode: show only the Home content without navigation.
return Home(title: appTitle, geoMapKey: geoMapKey);
}
// Normal mode: show full scaffold with navigation

// Normal mode: show full scaffold with navigation.
return _buildFullScaffold();
},
);
}

Widget _buildFullScaffold() {
return SolidScaffold(
// MENU
// MENU.
menu: [
SolidMenuItem(
icon: Icons.home,
Expand Down Expand Up @@ -130,11 +133,11 @@ class AppScaffoldWidget extends StatelessWidget {
),
],

// APP BAR
// APP BAR.
appBar: SolidAppBarConfig(
title: appTitle.split('-')[0],

// VERSION
// VERSION.
versionConfig: const SolidVersionConfig(
changelogUrl:
'https://github.com/gjwgit/geopod/blob/dev/'
Expand All @@ -146,7 +149,7 @@ class AppScaffoldWidget extends StatelessWidget {
SolidAppBarAction(
icon: Icons.settings,
onPressed: () {
// Call the GeoMap's settings dialog
// Call the GeoMap's settings dialog.
AppScaffoldWidget.geoMapKey.currentState?.showSettingsDialog();
},
tooltip: 'Settings',
Expand All @@ -155,7 +158,7 @@ class AppScaffoldWidget extends StatelessWidget {
overflowItems: [],
),

// STATUS BAR
// STATUS BAR.
statusBar: const SolidStatusBarConfig(
serverInfo: SolidServerInfo(
serverUri: 'https://pods.solidcommunity.au',
Expand All @@ -165,7 +168,7 @@ class AppScaffoldWidget extends StatelessWidget {
showOnNarrowScreens: true, // Show status bar on Android/mobile
),

// ABOUT
// ABOUT.
aboutConfig: SolidAboutConfig(
applicationName: appTitle.split(' - ')[0],
applicationIcon: Image.asset(
Expand All @@ -185,7 +188,7 @@ class AppScaffoldWidget extends StatelessWidget {
''',
),

// THEME DARK/LIGHT Mode
// THEME DARK/LIGHT Mode.
themeToggle: const SolidThemeToggleConfig(
enabled: true,
showInAppBarActions: true,
Expand All @@ -197,4 +200,5 @@ class AppScaffoldWidget extends StatelessWidget {
}

/// Convenience variable for backward compatibility.

final appScaffold = const AppScaffoldWidget();
1 change: 1 addition & 0 deletions lib/constants/example_places_data.dart
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,7 @@ library;

/// Raw example places data as a constant list.
/// This data is compiled into the binary, ensuring zero-latency access.

const List<Map<String, dynamic>> kExamplePlacesData = [
{
'id': 'local_001',
Expand Down
10 changes: 6 additions & 4 deletions lib/main.dart
Original file line number Diff line number Diff line change
Expand Up @@ -56,15 +56,16 @@ void main() async {

// CRITICAL: Set app directory name BEFORE any Pod operations
// This must be called early to prevent double-slash bug in file paths
// Without this, paths become //data/places.json instead of geopod/data/places.json
// Without this, paths become //data/places.json instead of geopod/data/places.json.
await setAppDirName('geopod');

// Initialize encrypted places service to load persistent flags
// This improves performance by avoiding repeated checks
// This improves performance by avoiding repeated checks.
await EncryptedPlacesService.initialize();

// Configure SolidAuthHandler with app-specific settings
// This ensures proper login page navigation when guest users want to authenticate
// This ensures proper login page navigation when guest users want to authenticate.

SolidAuthHandler.instance.configure(
SolidAuthConfig(
appTitle: appTitle,
Expand All @@ -73,7 +74,8 @@ void main() async {
appImage: const AssetImage('assets/images/app_image.png'),
appLogo: const AssetImage('assets/images/app_icon.png'),
loginSuccessWidget: appScaffold,
// Clear security key on logout to ensure clean state

// Clear security key on logout to ensure clean state.
onSecurityKeyReset: () async {
await KeyManager.clear();
debugPrint('GeoPod: Security key cleared on logout');
Expand Down
27 changes: 22 additions & 5 deletions lib/models/hourly_weather_data.dart
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,7 @@
library;

/// Hourly weather data point.

class HourlyWeatherPoint {
HourlyWeatherPoint({
required this.time,
Expand All @@ -28,6 +29,7 @@ class HourlyWeatherPoint {
}

/// Hourly weather data series.

class HourlyWeatherData {
HourlyWeatherData({
required this.data,
Expand Down Expand Up @@ -74,6 +76,7 @@ class HourlyWeatherData {
final DateTime endDate;

/// Get daily average temperatures.

Map<DateTime, double> getDailyAverages() {
final dailyTemps = <DateTime, List<double>>{};

Expand All @@ -89,6 +92,7 @@ class HourlyWeatherData {
}

/// Get daily average humidity.

Map<DateTime, double> getDailyAverageHumidity() {
final dailyHumidity = <DateTime, List<double>>{};

Expand All @@ -107,6 +111,7 @@ class HourlyWeatherData {
}

/// Get daily average wind speed.

Map<DateTime, double> getDailyAverageWindSpeed() {
final dailyWindSpeed = <DateTime, List<double>>{};

Expand All @@ -124,6 +129,7 @@ class HourlyWeatherData {

/// Get daily min/max values for a specific data type.
/// Returns a map where each date maps to (min, max) tuple.

Map<DateTime, (double, double)> getDailyMinMax(String dataType) {
final dailyValues = <DateTime, List<double>>{};

Expand Down Expand Up @@ -159,6 +165,7 @@ class HourlyWeatherData {
}

/// Get temperature range (min, max).

(double min, double max) getTemperatureRange() {
var min = data.first.temperature;
var max = data.first.temperature;
Expand All @@ -172,6 +179,7 @@ class HourlyWeatherData {
}

/// Get humidity range (min, max).

(double min, double max) getHumidityRange() {
final validPoints = data.where((p) => p.humidity != null).toList();
if (validPoints.isEmpty) return (0, 100);
Expand All @@ -189,6 +197,7 @@ class HourlyWeatherData {
}

/// Get wind speed range (min, max).

(double min, double max) getWindSpeedRange() {
final validPoints = data.where((p) => p.windSpeed != null).toList();
if (validPoints.isEmpty) return (0, 30);
Expand All @@ -206,18 +215,20 @@ class HourlyWeatherData {

/// Get daily total precipitation.
/// Sums all hourly precipitation values for each day.

Map<DateTime, double> getDailyTotalPrecipitation() {
final dailyPrecipitation = <DateTime, List<double>>{};

for (final point in data) {
// Include 0 values, skip only null
// Include 0 values, skip only null.
if (point.precipitation == null) continue;
final date = DateTime(point.time.year, point.time.month, point.time.day);
dailyPrecipitation.putIfAbsent(date, () => []).add(point.precipitation!);
}

// Return empty map ONLY if no precipitation data exists at all
// If all values are 0, we still return the data (not empty map)

if (dailyPrecipitation.isEmpty) return {};

return dailyPrecipitation.map(
Expand All @@ -230,6 +241,7 @@ class HourlyWeatherData {

/// Get count of hours with precipitation for each day.
/// Counts hours where precipitation > 0.

Map<DateTime, int> getDailyPrecipitationHours() {
final dailyHours = <DateTime, int>{};

Expand All @@ -238,10 +250,11 @@ class HourlyWeatherData {
final date = DateTime(point.time.year, point.time.month, point.time.day);

// Count hours with measurable precipitation (> 0)

if (point.precipitation! > 0) {
dailyHours[date] = (dailyHours[date] ?? 0) + 1;
} else {
// Ensure date exists in map even if no precipitation
// Ensure date exists in map even if no precipitation.
dailyHours.putIfAbsent(date, () => 0);
}
}
Expand All @@ -250,6 +263,7 @@ class HourlyWeatherData {
}

/// Get precipitation range (min, max) for hourly data.

(double min, double max) getPrecipitationRange() {
final validPoints = data.where((p) => p.precipitation != null).toList();
if (validPoints.isEmpty) return (0, 2); // Smaller default range
Expand All @@ -262,7 +276,8 @@ class HourlyWeatherData {
if (point.precipitation! > max) max = point.precipitation!;
}

// If all values are 0 or very close, set a small visible range
// If all values are 0 or very close, set a small visible range.

if (max < 0.1) {
return (0, 1.0); // Show 0-1mm range for very small/zero precipitation
}
Expand All @@ -277,6 +292,7 @@ class HourlyWeatherData {
/// Get daily total precipitation range (min, max).
/// Used for chart axis scaling when displaying daily totals.
/// Min is always 0 (precipitation cannot be negative).

(double min, double max) getDailyTotalPrecipitationRange() {
final dailyTotals = getDailyTotalPrecipitation();
if (dailyTotals.isEmpty) return (0, 10); // Default range for no data
Expand All @@ -288,7 +304,8 @@ class HourlyWeatherData {
if (value > maxValue) maxValue = value;
}

// If all values are 0 or very close, set a visible range
// If all values are 0 or very close, set a visible range.

if (maxValue < 0.5) {
return (0, 5.0); // Show 0-5mm range for very small/zero precipitation
}
Expand All @@ -297,7 +314,7 @@ class HourlyWeatherData {
return (0, maxValue + 5.0);
}

// Add some padding to the max for better visualization
// Add some padding to the max for better visualization.
return (0, maxValue * 1.1);
}
}
Loading