Skip to content
Snippets Groups Projects
configuration.dart 15.7 KiB
Newer Older
import 'dart:async';
import 'package:flutter/material.dart';
Maximilian Betz's avatar
Maximilian Betz committed
import 'package:flutter/services.dart';
import 'sensorconnector.dart';
import 'databaseconnector.dart';
import 'package:path/path.dart';
import 'package:path_provider/path_provider.dart';
class Configuration extends StatefulWidget {
Maximilian Betz's avatar
Maximilian Betz committed
  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;
  bool onlineConnectivity = false;
  bool changedCollection = false; //Flag to indicate that the chosen collection has changed and device need to be updated.

  var database = DatabaseInstance();
  SensorConnector connector = SensorConnector();
  final ConfigurationStoreInstance configuration = ConfigurationStoreInstance();

  late OverlayEntry _overlayEntry;
  late Timer _overlayCloseTimer;

  bool _initError = false;
  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);
      String dumpStr = "{\"events\":" + eventsJson + "}";
      await file.writeAsString(dumpStr);
      debugPrint('Stored file at: ' + filepath);
Maximilian Betz's avatar
Maximilian Betz committed

      setState(() {});
      HapticFeedback.vibrate();
      _showResultPopup(context, 'Event database dump created: ' + filepath,
          false
      );
  Future<void> _showDelayed(BuildContext context, String text, bool error) async {
    await Future.delayed(const Duration(milliseconds: 250));
    _showResultPopup(context, text, error);
  }

  Future<void> _showResultPopup(BuildContext context, String text, bool error) async {
    OverlayState? overlayState = Overlay.of(context);
      _overlayEntry.remove(); // Allow only one Overlay Popup at a time
    }catch(e){
      debugPrint('Overlay already removed, during dispose: ' + e.toString());
    }
    _overlayEntry = OverlayEntry(builder: (context) {
      Color backGroundColor;
      Color textColor;
      if (error == true){
        backGroundColor = Colors.redAccent; //Style for error message
        textColor = Colors.black;
      }
      else {
        backGroundColor = Colors.greenAccent; //Style for notification
        textColor = Colors.black;
      }

      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
              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),
    overlayState?.insert(_overlayEntry);
      _overlayCloseTimer.cancel(); // Kill old timers
      debugPrint('Timer cancel error: ' + e.toString());

    _overlayCloseTimer = Timer(
      const Duration(seconds: 5),
          () {
        try {
          _overlayEntry.remove(); // Allow only one Overlay Popup. NOTE: Is this a quick an dirty or a proper solution?
        }catch(e){
          debugPrint('Overlay already removed, during dispose: ' + e.toString());
        }
      },
  Future<void> updateConfiguration(BuildContext context) async {
    final EventStoreInstance event = EventStoreInstance();
    String statusPopUpMessageText = '';
    bool popUpError = false;
    try {
      if (changedCollection == true) {
        token = await connector.getAuthToken(
            configuration.loginInformation.mail,
            configuration.loginInformation.password);
Maximilian Betz's avatar
Maximilian Betz committed
        configuration.devices = await futureDevices;  //Http get already initiated
        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!');

        statusPopUpMessageText = 'Login success, got all EventTypes, loaded Collection Devices & stored configuration for offline usage.';
        popUpError = false;

        changedCollection = false; //Reset flag
        await configuration.storeToSharedPrefs();

        // collection has not been changed but store the other parameters
        statusPopUpMessageText = 'Stored changed configuration.';
        popUpError = false;
Maximilian Betz's avatar
Maximilian Betz committed
      String errorText = e.toString();
      statusPopUpMessageText = errorText.substring(10, errorText.length);  //Remove 'Exception' from string.
      popUpError = true;
    setState(() {_showResultPopup(context, statusPopUpMessageText, popUpError);});
  Future<void> fetchInitData() async {
    try {
      final ConfigurationStoreInstance configuration = ConfigurationStoreInstance();
      List<Collection> localCollections;
      futureCollections = connector.fetchCollections();
      localCollections = await futureCollections;

      // Check if selected collection is still available
      bool stillExists = false;
      for (var collection in localCollections) {
        if (collection.id == configuration.currentCollection.id){
          stillExists = true; // Collection still exists
        }
      }
      configuration.collections = localCollections;
      if (stillExists == false){
        // Set chosen collection to first in list if it not exists there
        configuration.currentCollection = configuration.collections[0];
      }

      onlineConnectivity = true;
Maximilian Betz's avatar
Maximilian Betz committed
      String errorText = e.toString();
      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');
  @override
  Widget build(BuildContext context) {
    final ConfigurationStoreInstance configuration = ConfigurationStoreInstance();
    final EventStoreInstance events = EventStoreInstance();
    if(_initError == true){
      _initError = false;
      _showDelayed(context, _status, true); //NOTE: Dirty hack with delay. Stack error without delay. Why?
Maximilian Betz's avatar
Maximilian Betz committed
        appBar: AppBar(
          title: const Text('Configuration'),
        ),
Maximilian Betz's avatar
Maximilian Betz committed
        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!',
Maximilian Betz's avatar
Maximilian Betz committed
                    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: '',
Maximilian Betz's avatar
Maximilian Betz committed
                  ),
Maximilian Betz's avatar
Maximilian Betz committed
                  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: '',
Maximilian Betz's avatar
Maximilian Betz committed
                  ),
Maximilian Betz's avatar
Maximilian Betz committed
                  onChanged: (value){
                    configuration.loginInformation.password = value;
                  },
                ),
                const SizedBox(height: 15.0),
                Row(
                  mainAxisAlignment: MainAxisAlignment.center,
                  children: [
                    Expanded(child:
                    TextFormField(
                      keyboardType: TextInputType.text,
                      autofocus: false,
                      initialValue: configuration.labelConfig.prefix,
                    inputFormatters:[FilteringTextInputFormatter.allow(RegExp(r'^[a-z A-Z . \- 0-9 , ( ) + - _ :]+$'))],
                      decoration: const InputDecoration(
                        border: OutlineInputBorder(),
                        labelText: 'Label Prefix',
                        hintText: '',
                      ),
                      onChanged: (value) {
                        configuration.labelConfig.prefix = value;
                      },
                    )
                    ),
                    const SizedBox(width: 5.0),
                    Expanded(child:
                    TextFormField(
                      keyboardType: TextInputType.number,
                      autofocus: false,
                      initialValue: configuration.labelConfig.cnt.toString(),
                      inputFormatters:[FilteringTextInputFormatter.allow(RegExp("[0-9]"))],
                      decoration: const InputDecoration(
                        border: OutlineInputBorder(),
Maximilian Betz's avatar
Maximilian Betz committed
                        labelText: 'Index count',
                        hintText: '',
                      ),
                      onChanged: (value) {
                        configuration.labelConfig.cnt = int.parse(value);
                        setState(() {});
                      },
                    )
                    ),
                    const SizedBox(width: 5.0),
                    ElevatedButton(
                      style: ElevatedButton.styleFrom(
                        padding: const EdgeInsets.symmetric(vertical: 20.0), //TODO: find a more dynamic solution without a fixed value
                        primary: Colors.blue,
                      ),
                      onPressed: () {
                          configuration.labelConfig.cntUp = !configuration.labelConfig.cntUp ;
                          setState(() {});
                      },
                      child: configuration.labelConfig.cntUp ? const Text('+') : const Text('-'),
                    ),
                  ],
                ),

                  keyboardType: TextInputType.url,
                  autofocus: false,
                  initialValue: configuration.restRequestUrl,
                  decoration: const InputDecoration(
                    border: OutlineInputBorder(),
                    labelText: 'Rest URL',
                    hintText: '',
                  ),
                  onChanged: (value) {
                    configuration.restRequestUrl = value;
                  },
                ),
Maximilian Betz's avatar
Maximilian Betz committed
                const SizedBox(height: 50),
                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: onlineConnectivity ? ((value) {
Maximilian Betz's avatar
Maximilian Betz committed
                    configuration.currentCollection = configuration.getCollectionFromName(value.toString());
                    // Start query to fetch the collection devices
                    futureDevices = connector.fetchCollectionDevices(configuration.currentCollection.id);
                    changedCollection = true;
                  }) : null,

Maximilian Betz's avatar
Maximilian Betz committed
                ),
                const SizedBox(height: 70.0),
Maximilian Betz's avatar
Maximilian Betz committed
              ],
Maximilian Betz's avatar
Maximilian Betz committed
        ),
        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(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);
                  },
                ),
              ],
            )
Maximilian Betz's avatar
Maximilian Betz committed

//TODO: write configuration on app dispose!
//TODO: grey out update button if nothing has changed. ?