diff --git a/lib/context.dart b/lib/context.dart index 0240cebaf28fa30235194ab2fc5f422518f6895e..a0c24a7cf8730da62238184e88dba3ad0544f48a 100644 --- a/lib/context.dart +++ b/lib/context.dart @@ -2,14 +2,27 @@ import 'untis/class.dart'; import 'untis/fetch.dart'; import 'untis/lecture.dart'; import 'user/profile.dart'; +import 'user/save_state.dart'; import 'util/listenable.dart'; class AppContext { final UntisFetch untis; + final SaveManager _saveManager; final Listenable<Profile?> profile = Listenable(null); + final Listenable<DateTime> date = Listenable(DateTime.now()); + List<Class>? classes; List<Lecture>? lectures; - final Listenable<DateTime> date = Listenable(DateTime.now()); - AppContext(this.untis); + AppContext(this.untis, SaveManager saveManager) : _saveManager = saveManager; + + Future<void> save() => _saveManager.save(); + + Future<void> loadProfile() async { + profile.value = await _saveManager.load(); + + profile.value! + ..hiddenLectures.onChange.listen((_) => save()) + ..selectedClasses.onChange.listen((_) => save()); + } } diff --git a/lib/main.dart b/lib/main.dart index 8ae838986e6c52abcee1b3f0b27670ad70dbe191..9fd493976f7f5858fc4c9896d87f8612edff55fd 100644 --- a/lib/main.dart +++ b/lib/main.dart @@ -9,39 +9,57 @@ import 'timetable/timetable.dart'; import 'toolbar/toolbar.dart'; import 'untis/fetch.dart'; import 'untis/timetable.dart'; -import 'user/profile.dart'; +import 'user/save_state.dart'; import 'util/listenable.dart'; void main() { runApp(const MyApp()); } -class MyApp extends StatelessWidget { +class MyApp extends StatefulWidget { const MyApp({super.key}); + @override + State<MyApp> createState() => _MyAppState(); +} + +class _MyAppState extends State<MyApp> { + final SaveManager saveManager = SaveManager(); + final UntisFetch untis = UntisFetch(); + late final AppContext appContext = AppContext(untis, saveManager); + + @override + void initState() { + super.initState(); + appContext.loadProfile(); + } + @override Widget build(BuildContext context) { return MaterialApp( title: appName, theme: theme, - home: const MyHomePage(), + home: MyHomePage(appContext: appContext), ); } } class MyHomePage extends StatefulWidget { - const MyHomePage({super.key}); + final AppContext appContext; + + const MyHomePage({super.key, required this.appContext}); @override State<MyHomePage> createState() => _MyHomePageState(); } class _MyHomePageState extends State<MyHomePage> { - final UntisFetch untis = UntisFetch(); TimetableData? timetable; - late AppContext appContext = AppContext(untis); late List<StreamSubscription> _subscriptions; + AppContext get appContext => widget.appContext; + UntisFetch get untis => appContext.untis; + @override void initState() { super.initState(); @@ -57,7 +75,7 @@ class _MyHomePageState extends State<MyHomePage> { } Future<void> loadProfile() async { - final profile = Profile(selectedClasses: {19997, 20000}); + final profile = await appContext.profile.firstNotNull; final classes = await untis.allClasses.fetched; appContext.profile.value = profile; @@ -75,8 +93,7 @@ class _MyHomePageState extends State<MyHomePage> { Future<void> fetchTimetable() async { final date = appContext.date.value; - timetable = null; - setState(() {}); + TimetableData? merged; final profile = await appContext.profile.firstNotNull; final selectedClasses = profile.selectedClasses; @@ -86,14 +103,16 @@ class _MyHomePageState extends State<MyHomePage> { })); for (final fetched in allTimetables) { - if (timetable == null) { - timetable = fetched; + if (merged == null) { + merged = fetched; } else { - timetable = TimetableData.merge(timetable!, fetched); + merged = TimetableData.merge(merged, fetched); } - - setState(() {}); } + + setState(() { + timetable = merged; + }); } @override diff --git a/lib/user/save_state.dart b/lib/user/save_state.dart new file mode 100644 index 0000000000000000000000000000000000000000..0775c3b579e6fd1250cdf3de5b2e1925c888d105 --- /dev/null +++ b/lib/user/save_state.dart @@ -0,0 +1,56 @@ +import 'dart:async'; +import 'dart:convert'; +import 'dart:io'; + +import 'package:path/path.dart' as path; +import 'package:path_provider/path_provider.dart'; + +import 'profile.dart'; + +class SaveManager { + static const fileName = 'profile.json'; + + File? _saveFile; + FutureOr<File> get saveFile async => _saveFile ??= await _getSaveFile(); + + Profile? state; + + Future<Directory> _getSaveDirectory() async { + if (Platform.isWindows) { + return await getApplicationCacheDirectory(); + } + return await getApplicationDocumentsDirectory(); + } + + Future<File> _getSaveFile() async { + final directory = await _getSaveDirectory(); + return File(path.join(directory.path, fileName)); + } + + Future<void> save() async { + final file = await saveFile; + + final jsonString = jsonEncode(state!.toJson()); + await file.writeAsString(jsonString); + print('Saved to ${file.path}'); + } + + Future<Profile> _load() async { + final file = await saveFile; + + if (!await file.exists()) { + print('No profile saved, creating new one'); + return Profile(); + } + + final fileContent = await file.readAsString(); + final jsonContent = jsonDecode(fileContent); + + print('Loading from ${file.path}'); + return Profile.fromJson(jsonContent); + } + + Future<Profile> load() async { + return state = await _load(); + } +} diff --git a/macos/Flutter/GeneratedPluginRegistrant.swift b/macos/Flutter/GeneratedPluginRegistrant.swift index cccf817a52206e8a8eef501faed292993ff21a31..e777c67df2219fce2c33861bf98710f86bfc79b3 100644 --- a/macos/Flutter/GeneratedPluginRegistrant.swift +++ b/macos/Flutter/GeneratedPluginRegistrant.swift @@ -5,6 +5,8 @@ import FlutterMacOS import Foundation +import path_provider_foundation func RegisterGeneratedPlugins(registry: FlutterPluginRegistry) { + PathProviderPlugin.register(with: registry.registrar(forPlugin: "PathProviderPlugin")) } diff --git a/pubspec.lock b/pubspec.lock index b898505bdd017e792fa77b5bbd0d55e307180a22..d0aa88d36257b6b15e3b594503b4ea53ef4e10e4 100644 --- a/pubspec.lock +++ b/pubspec.lock @@ -57,6 +57,14 @@ packages: url: "https://pub.dev" source: hosted version: "1.3.1" + ffi: + dependency: transitive + description: + name: ffi + sha256: "7bf0adc28a23d395f19f3f1eb21dd7cfd1dd9f8e1c50051c069122e6853bc878" + url: "https://pub.dev" + source: hosted + version: "2.1.0" flutter: dependency: "direct main" description: flutter @@ -108,13 +116,77 @@ packages: source: hosted version: "1.9.1" path: - dependency: transitive + dependency: "direct main" description: name: path sha256: "8829d8a55c13fc0e37127c29fedf290c102f4e40ae94ada574091fe0ff96c917" url: "https://pub.dev" source: hosted version: "1.8.3" + path_provider: + dependency: "direct main" + description: + name: path_provider + sha256: a1aa8aaa2542a6bc57e381f132af822420216c80d4781f7aa085ca3229208aaa + url: "https://pub.dev" + source: hosted + version: "2.1.1" + path_provider_android: + dependency: transitive + description: + name: path_provider_android + sha256: e595b98692943b4881b219f0a9e3945118d3c16bd7e2813f98ec6e532d905f72 + url: "https://pub.dev" + source: hosted + version: "2.2.1" + path_provider_foundation: + dependency: transitive + description: + name: path_provider_foundation + sha256: "19314d595120f82aca0ba62787d58dde2cc6b5df7d2f0daf72489e38d1b57f2d" + url: "https://pub.dev" + source: hosted + version: "2.3.1" + path_provider_linux: + dependency: transitive + description: + name: path_provider_linux + sha256: f7a1fe3a634fe7734c8d3f2766ad746ae2a2884abe22e241a8b301bf5cac3279 + url: "https://pub.dev" + source: hosted + version: "2.2.1" + path_provider_platform_interface: + dependency: transitive + description: + name: path_provider_platform_interface + sha256: "94b1e0dd80970c1ce43d5d4e050a9918fce4f4a775e6142424c30a29a363265c" + url: "https://pub.dev" + source: hosted + version: "2.1.1" + path_provider_windows: + dependency: transitive + description: + name: path_provider_windows + sha256: "8bc9f22eee8690981c22aa7fc602f5c85b497a6fb2ceb35ee5a5e5ed85ad8170" + url: "https://pub.dev" + source: hosted + version: "2.2.1" + platform: + dependency: transitive + description: + name: platform + sha256: "0a279f0707af40c890e80b1e9df8bb761694c074ba7e1d4ab1bc4b728e200b59" + url: "https://pub.dev" + source: hosted + version: "3.1.3" + plugin_platform_interface: + dependency: transitive + description: + name: plugin_platform_interface + sha256: da3fdfeccc4d4ff2da8f8c556704c08f912542c5fb3cf2233ed75372384a034d + url: "https://pub.dev" + source: hosted + version: "2.1.6" sky_engine: dependency: transitive description: flutter @@ -184,5 +256,22 @@ packages: url: "https://pub.dev" source: hosted version: "0.1.4-beta" + win32: + dependency: transitive + description: + name: win32 + sha256: "350a11abd2d1d97e0cc7a28a81b781c08002aa2864d9e3f192ca0ffa18b06ed3" + url: "https://pub.dev" + source: hosted + version: "5.0.9" + xdg_directories: + dependency: transitive + description: + name: xdg_directories + sha256: "589ada45ba9e39405c198fe34eb0f607cddb2108527e658136120892beac46d2" + url: "https://pub.dev" + source: hosted + version: "1.0.3" sdks: dart: ">=3.1.0 <4.0.0" + flutter: ">=3.7.0" diff --git a/pubspec.yaml b/pubspec.yaml index e93129ea5b6054d55af147340a667e6a70b6c719..4772eba700e70331151dc5f8ede51364def94339 100644 --- a/pubspec.yaml +++ b/pubspec.yaml @@ -35,6 +35,8 @@ dependencies: # The following adds the Cupertino Icons font to your application. # Use with the CupertinoIcons class for iOS style icons. cupertino_icons: ^1.0.2 + path_provider: ^2.1.1 + path: ^1.8.3 dev_dependencies: flutter_test: