import 'dart:convert'; import 'dart:io'; import 'package:flutter/material.dart'; import 'package:flutter/services.dart'; import 'datamodel.dart'; import 'sensorconnector.dart'; import 'databaseconnector.dart'; import 'package:path/path.dart'; import 'package:path_provider/path_provider.dart'; class Configuration extends StatefulWidget { const Configuration({Key? key}) : super(key: key); @override State<Configuration> createState() => _MyHomePageState(); } class _MyHomePageState extends State<Configuration> { late Future<Collection> futureCollection; late Future<List<Collection>> futureCollections; late Future<List<EventType>> futureEventTypes; late Future<List<Device>> futureDevices; late Future<String> futureAuthToken; var database = DatabaseInstance(); SensorConnector connector = SensorConnector(); final ConfigurationStoreInstance configuration = ConfigurationStoreInstance(); String _status = ''; TextStyle _statusStyle = const TextStyle(color: Colors.black); Future<void> dumpToFile() async{ var date = DateTime.now().toUtc(); var isoDate = date.toIso8601String(); String filename = date.year.toString().padLeft(4, '0') + date.month.toString().padLeft(2, '0') + date.day.toString().padLeft(2, '0') + date.hour.toString().padLeft(2, '0') + date.minute.toString().padLeft(2, '0') + date.second.toString().padLeft(2, '0') + '_events.json'; String eventsJson = await database.eventDump(); final Directory? directory = await getExternalStorageDirectory(); if(directory != null) { String filepath = join(directory.path, filename); final File file = File(filepath); await file.writeAsString(eventsJson); debugPrint('Stored file at: ' + filepath); _status = 'Event database dump created: ' + filepath; _statusStyle = const TextStyle(color: Colors.green); setState(() {}); HapticFeedback.vibrate(); } } Future<void> updateConfiguration() async { final EventStoreInstance event = EventStoreInstance(); String token = ''; try{ token = await connector.getAuthToken(configuration.loginInformation.mail, configuration.loginInformation.password); configuration.devices = await connector.fetchCollectionDevices(configuration.currentCollection.id); //await futureDevices; //already fetched just use the data. configuration.eventTypes = await connector.fetchEventTypes(); event.currentEvent.id = 0; event.currentEvent.urnId = configuration.devices[0].id; //TODO: fix if devices are an empty list. event.currentEvent.urn = configuration.devices[0].urn; event.currentEvent.description = ''; event.currentEvent.label = ''; event.currentEvent.type = configuration.eventTypes[0].name; var date = DateTime.now().toUtc(); var isoDate = date.toIso8601String(); event.currentEvent.startDate = isoDate; event.currentEvent.endDate = isoDate; configuration.initialized = true; HapticFeedback.vibrate(); //TODO: display success user feedback //TODO: store complete "configuration" await event.storeToSharedPrefs(); await configuration.storeToSharedPrefs(); debugPrint('Configuration stored!'); _status = 'Login success, got all EventTypes, loaded Collection Devices & stored configuration for offline usage.'; _statusStyle = const TextStyle(color: Colors.green); setState(() {}); } catch (e) { String errorText = e.toString(); errorText = errorText.substring(10, errorText.length); //Remove 'Exception' from string. _status = errorText; _statusStyle = const TextStyle(color: Colors.red); setState(() {}); } } Future<void> fetchInitData() async { try { futureCollections = connector.fetchCollections(); await futureCollections; //wait and catch error here! }catch(e){ String errorText = e.toString(); errorText = errorText.substring(10, errorText.length); //Remove 'Exception' from string. _status = errorText; _statusStyle = const TextStyle(color: Colors.red); setState(() {}); } } @override void initState() { super.initState(); fetchInitData(); } @override Widget build(BuildContext context) { final ConfigurationStoreInstance configuration = ConfigurationStoreInstance(); final EventStoreInstance events = EventStoreInstance(); return Scaffold( appBar: AppBar( title: const Text('Configuration'), ), body: Container( //constraints: const BoxConstraints.expand(), margin: const EdgeInsets.symmetric(horizontal: 5.0), child: SingleChildScrollView( scrollDirection: Axis.vertical, child: Column( mainAxisAlignment: MainAxisAlignment.spaceBetween, mainAxisSize: MainAxisSize.max, children: <Widget>[ const SizedBox(height: 50), const Text( 'You must be online to do something here!', style: TextStyle(fontSize: 14) ), const SizedBox(height: 50), TextFormField( keyboardType: TextInputType.emailAddress, autofocus: false, initialValue: configuration.loginInformation.mail, decoration: const InputDecoration( border: OutlineInputBorder(), labelText: 'E-Mail', hintText: '', ), onChanged: (value) { configuration.loginInformation.mail = value; }, ), const SizedBox(height: 15.0), TextFormField( obscureText: true, autofocus: false, initialValue: configuration.loginInformation.password, decoration: const InputDecoration( border: OutlineInputBorder(), labelText: 'Password', hintText: '', ), onChanged: (value){ configuration.loginInformation.password = value; }, ), const SizedBox(height: 50), FutureBuilder<List<Collection>>( future: futureCollections, builder: (context, snapshot){ if (snapshot.hasData) { configuration.collections = []; snapshot.data?.forEach((element) { configuration.collections.add(element); }); /*Initialize active collection with first received collection if not initialized yet*/ if(configuration.currentCollection.id == -1){ configuration.currentCollection = configuration.collections[0]; } return DropdownButtonFormField( value: configuration.currentCollection.collectionName, decoration: const InputDecoration( labelText: 'Chose Collection', border: OutlineInputBorder(), ), items: configuration.collections.map((Collection collection) { return DropdownMenuItem( value: collection.collectionName, child: Text(collection.collectionName), ); }).toList(), onChanged: (value) { configuration.currentCollection = configuration.getCollectionFromName(value.toString()); //Fetch new selected collection devices futureDevices = connector.fetchCollectionDevices(configuration.currentCollection.id); } ); } if (snapshot.hasError) { debugPrint('Some error happened'); return const CircularProgressIndicator(); } else{ return const CircularProgressIndicator(); } } ), const SizedBox(height: 50.0), TextFormField( minLines: 1, maxLines: 5, readOnly: true, autofocus: false, enabled: false, style: _statusStyle, controller: TextEditingController( text: _status, ), ), ], ), ), ), bottomNavigationBar: Container( margin: const EdgeInsets.symmetric(vertical: 10.0, horizontal: 5.0), child:Row( mainAxisAlignment: MainAxisAlignment.spaceBetween, mainAxisSize: MainAxisSize.max, children: [ FloatingActionButton.extended( heroTag: null, tooltip: 'Export events to local .json file', icon: const Icon(Icons.download), label: const Text('Dump Events'), onPressed: () { dumpToFile(); }, ), FloatingActionButton.extended( heroTag: null, tooltip: 'Download corresponding devices and store locally', icon: const Icon(Icons.save), label: const Text('Set'), onPressed: () { updateConfiguration(); //TODO: display failed as user feedback somehow }, ), ], ), ) ); } } //TODO: write configuration on app dispose! //TODO: align bottom buttons nicely! //TODO: add label prefix with appended upcounting number. //TODO: grey out update button if nothing has changed. ?