Newer
Older
import 'package:flutter/material.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 {
@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();
late OverlayEntry _overlayEntry;
bool _initError = false;
TextStyle _statusStyle = const TextStyle(color: Colors.black);

Maximilian Betz
committed
Future<void> dumpToFile(BuildContext context) 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);
_showResultPopup(context, 'Event database dump created: ' + filepath,
false
);
Future<void> _showResultPopup(BuildContext context, String text, bool error) async {

Maximilian Betz
committed
// Declaring and Initializing OverlayState
// and OverlayEntry objects
OverlayState? overlayState = Overlay.of(context);
try {
_overlayEntry.remove(); // Allow only one Overlay Popup
}catch(e){
debugPrint('Overlay already removed, during dispose: ' + e.toString());
}
_overlayEntry = OverlayEntry(builder: (context) { //:
Color backGroundColor;
Color textColor;
if (error == true){
//This is a error message display red!
backGroundColor = Colors.redAccent;
textColor = Colors.black;
}
else {
//This is a normal message display green!
backGroundColor = Colors.greenAccent;
textColor = Colors.black;
}

Maximilian Betz
committed
// You can return any widget you like
// here to be displayed on the Overlay
return Stack(
alignment: Alignment.center,
children: [
Positioned(
// Position at 10% of height from bottom
bottom: MediaQuery.of(context).size.height * 0.1,
child: Material(
borderRadius: BorderRadius.circular(8.0),
color: backGroundColor, //Some transparency remains

Maximilian Betz
committed
child: Container(
padding: const EdgeInsets.all(5.0), // Space between Text and Bubble
width: MediaQuery.of(context).size.width * 0.95,
child: TextFormField(
minLines: 1,
maxLines: 5,
readOnly: true,
autofocus: false,
enabled: false,
style: TextStyle(color: textColor),

Maximilian Betz
committed
controller: TextEditingController(

Maximilian Betz
committed
),
),
),
),
),
],
);
});
// Inserting the OverlayEntry into the Overlay
overlayState?.insert(_overlayEntry);

Maximilian Betz
committed
// Awaiting for 5 seconds
await Future.delayed(const Duration(seconds: 5));
// Removing the OverlayEntry from the Overlay
try {
_overlayEntry.remove(); //NOTE: Is this a quick an dirty or a proper solution?
}catch(e){
debugPrint('Overlay already removed, during dispose: ' + e.toString());
}

Maximilian Betz
committed
}
Future<void> updateConfiguration(BuildContext context) 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();
if (configuration.devices.isEmpty){
throw Exception('Collection with 0 devices.');
}
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();
await event.storeToSharedPrefs();
await configuration.storeToSharedPrefs();
debugPrint('Configuration stored!');
_showResultPopup(context,
'Login success, got all EventTypes, loaded Collection Devices & stored configuration for offline usage.',
false
);
String errorText = e.toString();
errorText = errorText.substring(10, errorText.length); //Remove 'Exception' from string.
_showResultPopup(context,
errorText,
true
);
setState(() {});
}
Future<void> fetchInitData() async {
try {
futureCollections = connector.fetchCollections();
await futureCollections; //wait and catch error here!
errorText = errorText.substring(10, errorText.length);
_status = errorText; //store errorText globally for pop up message during Widget build
_initError = true; //Trigger popup error display!
}
@override
void initState() {
super.initState();
fetchInitData();
@override
void dispose() {
try {
_overlayEntry.remove();
}catch(e){
debugPrint('Dispose error: ' + e.toString());
}
super.dispose();
}
@override
Widget build(BuildContext context) {
final ConfigurationStoreInstance configuration = ConfigurationStoreInstance();
final EventStoreInstance events = EventStoreInstance();
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(
'Online access required for initial configuration!',
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');
if(_initError == true){
_initError = false;
//_showResultPopup(context, _status, true); //TODO: opening a popup does not work properly from here.
}
return const CircularProgressIndicator();
}
else{
return const CircularProgressIndicator();
}
}
),
const SizedBox(height: 70.0),
TextFormField(
minLines: 1,
maxLines: 5,
readOnly: true,
autofocus: false,
enabled: false,
style: _statusStyle,
controller: TextEditingController(
text: _status,
),
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
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(context);
//Reset for debug testing only. //Remove in RELEASE Version:
//database.delete();
//events.reset();
//configuration.reset();
},
),
FloatingActionButton.extended(
heroTag: null,
tooltip: 'Download corresponding devices and store locally',
icon: const Icon(Icons.save),
label: const Text('Set'),
onPressed: () {
updateConfiguration(context);
},
),
],
)
//TODO: write configuration on app dispose!
//TODO: add label prefix with appended upcounting number.
//TODO: grey out update button if nothing has changed. ?