diff --git a/lib/app.dart b/lib/app.dart index 135d78f..2164a41 100644 --- a/lib/app.dart +++ b/lib/app.dart @@ -2,6 +2,8 @@ import 'package:flutter/material.dart'; import 'package:flutter_riverpod/flutter_riverpod.dart'; import 'package:go_router/go_router.dart'; +import 'package:flutter_timezone/flutter_timezone.dart'; + import 'core/constants.dart'; import 'core/exceptions.dart'; import 'core/theme.dart'; @@ -159,19 +161,29 @@ class _ShellState extends ConsumerState<_Shell> { @override void initState() { super.initState(); - // Silent update check — only if we haven't already checked this session. - // Skipping when status is not idle/error prevents the dialog from - // re-appearing every time the shell re-mounts (e.g. after visiting settings). WidgetsBinding.instance.addPostFrameCallback((_) { + // Silent update check — only if we haven't already checked this session. final repoUrl = ref.read(forgejoRepoUrlProvider); - if (repoUrl == null || repoUrl.isEmpty) return; - final status = ref.read(updateProvider).status; - if (status == UpdateStatus.idle || status == UpdateStatus.error) { - ref.read(updateProvider.notifier).check(repoUrl); + if (repoUrl != null && repoUrl.isNotEmpty) { + final status = ref.read(updateProvider).status; + if (status == UpdateStatus.idle || status == UpdateStatus.error) { + ref.read(updateProvider.notifier).check(repoUrl); + } } + // Sync device timezone to backend so briefing and chat use local time. + _syncTimezone(); }); } + Future _syncTimezone() async { + try { + final tzInfo = await FlutterTimezone.getLocalTimezone(); + await ref.read(settingsApiProvider).syncTimezone(tzInfo.identifier); + } catch (_) { + // Best-effort — failure is non-critical. + } + } + int _tabIndex(String location) { for (var i = 0; i < _tabs.length; i++) { if (location.startsWith(_tabs[i])) return i; diff --git a/lib/data/api/settings_api.dart b/lib/data/api/settings_api.dart new file mode 100644 index 0000000..8d30c5f --- /dev/null +++ b/lib/data/api/settings_api.dart @@ -0,0 +1,13 @@ +import 'package:dio/dio.dart'; + +class SettingsApi { + const SettingsApi(this._dio); + final Dio _dio; + + Future syncTimezone(String ianaTimezone) async { + await _dio.put( + '/api/settings', + data: {'user_timezone': ianaTimezone}, + ); + } +} diff --git a/lib/providers/api_client_provider.dart b/lib/providers/api_client_provider.dart index de7be00..2aba0ce 100644 --- a/lib/providers/api_client_provider.dart +++ b/lib/providers/api_client_provider.dart @@ -10,6 +10,7 @@ import '../data/api/milestones_api.dart'; import '../data/api/notes_api.dart'; import '../data/api/projects_api.dart'; import '../data/api/quick_capture_api.dart'; +import '../data/api/settings_api.dart'; import '../data/api/tasks_api.dart'; import '../data/repositories/auth_repository.dart'; import '../data/repositories/chat_repository.dart'; @@ -85,3 +86,7 @@ final milestonesRepositoryProvider = Provider((ref) { final briefingApiProvider = Provider((ref) { return BriefingApi(ref.watch(dioProvider)); }); + +final settingsApiProvider = Provider((ref) { + return SettingsApi(ref.watch(dioProvider)); +}); diff --git a/pubspec.lock b/pubspec.lock index 308afa8..bfc5ee8 100644 --- a/pubspec.lock +++ b/pubspec.lock @@ -169,6 +169,14 @@ packages: url: "https://pub.dev" source: hosted version: "2.1.2" + equatable: + dependency: transitive + description: + name: equatable + sha256: "3e0141505477fd8ad55d6eb4e7776d3fe8430be8e497ccb1521370c3f21a3e2b" + url: "https://pub.dev" + source: hosted + version: "2.0.8" fake_async: dependency: transitive description: @@ -299,6 +307,14 @@ packages: description: flutter source: sdk version: "0.0.0" + flutter_timezone: + dependency: "direct main" + description: + name: flutter_timezone + sha256: e8d63f50f2806a3a71a08697286a0369e1d8f0902961327810459871c0bb01c2 + url: "https://pub.dev" + source: hosted + version: "5.0.2" flutter_web_plugins: dependency: transitive description: flutter @@ -640,6 +656,54 @@ packages: url: "https://pub.dev" source: hosted version: "2.3.0" + permission_handler: + dependency: "direct main" + description: + name: permission_handler + sha256: "59adad729136f01ea9e35a48f5d1395e25cba6cea552249ddbe9cf950f5d7849" + url: "https://pub.dev" + source: hosted + version: "11.4.0" + permission_handler_android: + dependency: transitive + description: + name: permission_handler_android + sha256: d3971dcdd76182a0c198c096b5db2f0884b0d4196723d21a866fc4cdea057ebc + url: "https://pub.dev" + source: hosted + version: "12.1.0" + permission_handler_apple: + dependency: transitive + description: + name: permission_handler_apple + sha256: f000131e755c54cf4d84a5d8bd6e4149e262cc31c5a8b1d698de1ac85fa41023 + url: "https://pub.dev" + source: hosted + version: "9.4.7" + permission_handler_html: + dependency: transitive + description: + name: permission_handler_html + sha256: "38f000e83355abb3392140f6bc3030660cfaef189e1f87824facb76300b4ff24" + url: "https://pub.dev" + source: hosted + version: "0.1.3+5" + permission_handler_platform_interface: + dependency: transitive + description: + name: permission_handler_platform_interface + sha256: eb99b295153abce5d683cac8c02e22faab63e50679b937fa1bf67d58bb282878 + url: "https://pub.dev" + source: hosted + version: "4.3.0" + permission_handler_windows: + dependency: transitive + description: + name: permission_handler_windows + sha256: "1a790728016f79a41216d88672dbc5df30e686e811ad4e698bfc51f76ad91f1e" + url: "https://pub.dev" + source: hosted + version: "0.2.1" petitparser: dependency: transitive description: diff --git a/pubspec.yaml b/pubspec.yaml index 3830495..50c50e3 100644 --- a/pubspec.yaml +++ b/pubspec.yaml @@ -26,6 +26,7 @@ dependencies: flutter_inappwebview: ^6.1.5 flutter_markdown_plus: ^1.0.7 google_fonts: ^8.0.2 + flutter_timezone: ^5.0.2 dev_dependencies: flutter_test: