From 1063f06546e5e72527ed294b7ebf33b82076cc44 Mon Sep 17 00:00:00 2001 From: Patrick Schmidt Date: Thu, 5 Aug 2021 21:39:33 +0200 Subject: [PATCH] * Added edit menu * Added ability to add webcams (For now only the first one will work) --- lib/app/AppSetup.dart | 4 + lib/dto/machine/PrinterSetting.dart | 13 +- lib/dto/machine/WebcamSetting.dart | 25 +++ lib/service/PrinterService.dart | 2 +- lib/ui/drawer/nav_drawer_view.dart | 5 + lib/ui/overview/overview_view.dart | 23 +- lib/ui/overview/overview_viewmodel.dart | 20 +- lib/ui/printers/add/printers_add_view.dart | 2 +- .../printers/add/printers_add_viewmodel.dart | 9 +- .../printers_slidable_viewmodel.dart | 5 +- lib/ui/printers/edit/printers_edit_view.dart | 208 ++++++++++++++++++ .../edit/printers_edit_viewmodel.dart | 89 ++++++++ lib/ui/printers/printers_view.dart | 14 +- lib/ui/printers/printers_viewmodel.dart | 2 + 14 files changed, 384 insertions(+), 37 deletions(-) create mode 100644 lib/dto/machine/WebcamSetting.dart create mode 100644 lib/ui/printers/edit/printers_edit_view.dart create mode 100644 lib/ui/printers/edit/printers_edit_viewmodel.dart diff --git a/lib/app/AppSetup.dart b/lib/app/AppSetup.dart index add0838a..43e1e05f 100644 --- a/lib/app/AppSetup.dart +++ b/lib/app/AppSetup.dart @@ -7,6 +7,7 @@ import 'package:hive_flutter/hive_flutter.dart'; import 'package:logger/logger.dart'; import 'package:mobileraker/WebSocket.dart'; import 'package:mobileraker/dto/machine/PrinterSetting.dart'; +import 'package:mobileraker/dto/machine/WebcamSetting.dart'; import 'package:mobileraker/service/KlippyService.dart'; import 'package:mobileraker/service/PrinterService.dart'; import 'package:mobileraker/service/PrinterSettingsService.dart'; @@ -14,6 +15,7 @@ import 'package:mobileraker/service/SelectedMachineService.dart'; import 'package:mobileraker/ui/dialog/editForm/editForm_view.dart'; import 'package:mobileraker/ui/overview/overview_view.dart'; import 'package:mobileraker/ui/printers/add/printers_add_view.dart'; +import 'package:mobileraker/ui/printers/edit/printers_edit_view.dart'; import 'package:mobileraker/ui/printers/printers_view.dart'; import 'package:mobileraker/ui/setting/setting_view.dart'; import 'package:mobileraker/ui/test_view.dart'; @@ -28,6 +30,7 @@ import 'AppSetup.locator.dart'; CupertinoRoute(page: SettingView), CupertinoRoute(page: Printers), MaterialRoute(page: PrintersAdd), + MaterialRoute(page: PrintersEdit), ], dependencies: [ LazySingleton(classType: NavigationService), LazySingleton(classType: SnackbarService), @@ -49,6 +52,7 @@ registerPrinters() async { openBoxes() async { await Hive.initFlutter(); Hive.registerAdapter(PrinterSettingAdapter()); + Hive.registerAdapter(WebcamSettingAdapter()); // Hive.deleteBoxFromDisk('printers'); await Future.wait([ Hive.openBox('printers'), diff --git a/lib/dto/machine/PrinterSetting.dart b/lib/dto/machine/PrinterSetting.dart index 0d4d67dc..7d0e9285 100644 --- a/lib/dto/machine/PrinterSetting.dart +++ b/lib/dto/machine/PrinterSetting.dart @@ -1,5 +1,6 @@ import 'package:hive/hive.dart'; import 'package:mobileraker/WebSocket.dart'; +import 'package:mobileraker/dto/machine/WebcamSetting.dart'; import 'package:mobileraker/service/KlippyService.dart'; import 'package:mobileraker/service/PrinterService.dart'; import 'package:uuid/uuid.dart'; @@ -14,13 +15,14 @@ class PrinterSetting extends HiveObject { String wsUrl; @HiveField(2) String uuid = Uuid().v4(); + @HiveField(3) + List cams = List.empty(growable: true); WebSocketWrapper? _webSocket; WebSocketWrapper get websocket { if (_webSocket == null) - _webSocket = - WebSocketWrapper(wsUrl, Duration(seconds: 5)); + _webSocket = WebSocketWrapper(wsUrl, Duration(seconds: 5)); return _webSocket!; } @@ -28,16 +30,14 @@ class PrinterSetting extends HiveObject { PrinterService? _printerService; PrinterService get printerService { - if (_printerService == null) - _printerService = PrinterService(websocket); + if (_printerService == null) _printerService = PrinterService(websocket); return _printerService!; } KlippyService? _klippyService; KlippyService get klippyService { - if (_klippyService == null) - _klippyService = KlippyService(websocket); + if (_klippyService == null) _klippyService = KlippyService(websocket); return _klippyService!; } @@ -48,6 +48,7 @@ class PrinterSetting extends HiveObject { await super.delete(); _printerService?.printerStream.close(); _klippyService?.klipperStream.close(); + _webSocket?.reset(); _webSocket?.stateStream.close(); return; } diff --git a/lib/dto/machine/WebcamSetting.dart b/lib/dto/machine/WebcamSetting.dart new file mode 100644 index 00000000..199d577a --- /dev/null +++ b/lib/dto/machine/WebcamSetting.dart @@ -0,0 +1,25 @@ +import 'package:hive_flutter/hive_flutter.dart'; +import 'package:uuid/uuid.dart'; + +part 'WebcamSetting.g.dart'; + +@HiveType(typeId: 2) +class WebcamSetting { + @HiveField(0) + String name; + @HiveField(1) + String uuid = Uuid().v4(); + @HiveField(2) + String url; + @HiveField(3) + bool flipHorizontal = false; + @HiveField(4) + bool flipVertical = false; + + WebcamSetting(this.name, this.url); + + @override + String toString() { + return 'WebcamSetting{name: $name, uuid: $uuid, url: $url, flipHorizontal: $flipHorizontal, flipVertical: $flipVertical}'; + } +} diff --git a/lib/service/PrinterService.dart b/lib/service/PrinterService.dart index 0894a097..2089c790 100644 --- a/lib/service/PrinterService.dart +++ b/lib/service/PrinterService.dart @@ -270,7 +270,7 @@ class PrinterService { if (virtualSDJson.containsKey('is_active')) printer.virtualSdCard.isActive = virtualSDJson['is_active']; if (virtualSDJson.containsKey('file_position')) - printer.virtualSdCard.filePosition = virtualSDJson['file_position']; + printer.virtualSdCard.filePosition = int.parse(virtualSDJson['file_position'].toString()); } _updatePrintStat(Map printStatJson, {Printer? printer}) { diff --git a/lib/ui/drawer/nav_drawer_view.dart b/lib/ui/drawer/nav_drawer_view.dart index f347f51e..b9074e2b 100644 --- a/lib/ui/drawer/nav_drawer_view.dart +++ b/lib/ui/drawer/nav_drawer_view.dart @@ -28,6 +28,11 @@ class NavigationDrawerWidget extends StatelessWidget { padding: EdgeInsets.symmetric(horizontal: 00), child: Column( children: [ + // Divider(), + // ExpansionTile(title: const Text('Change Printer', style: TextStyle(color: Colors.white),), + // children: [], + // ), + // Divider(), buildMenuItem( model, text: 'Overview', diff --git a/lib/ui/overview/overview_view.dart b/lib/ui/overview/overview_view.dart index a61f68d4..7726f629 100644 --- a/lib/ui/overview/overview_view.dart +++ b/lib/ui/overview/overview_view.dart @@ -31,7 +31,10 @@ class OverView extends StatelessWidget { return ViewModelBuilder.reactive( builder: (context, model, child) => Scaffold( appBar: AppBar( - title: Text(model.title, overflow: TextOverflow.fade,), + title: Text( + model.title, + overflow: TextOverflow.fade, + ), actions: [ IconButton( icon: Icon(Icons.radio_button_on, @@ -367,13 +370,15 @@ class CamCard extends ViewModelWidget { ), Padding( padding: const EdgeInsets.fromLTRB(8, 8, 8, 15), - child: Transform( - alignment: Alignment.center, - transform: matrix4, - child: Mjpeg( - isLive: true, - stream: model.webCamUrl, - ))), + child: (model.webCamUrl == null) + ? Center(child: Text('You will have to add a cam first!')) + : Transform( + alignment: Alignment.center, + transform: matrix4, + child: Mjpeg( + isLive: true, + stream: model.webCamUrl!, + ))), ], ), ); @@ -417,7 +422,7 @@ class HeaterCard extends ViewModelWidget { title: Text('Heaters'), ), Padding( - padding: const EdgeInsets.fromLTRB(0, 0, 0, 15), + padding: const EdgeInsets.only(top: 15), child: Table( border: TableBorder( horizontalInside: BorderSide( diff --git a/lib/ui/overview/overview_viewmodel.dart b/lib/ui/overview/overview_viewmodel.dart index 1238278c..8e3aac9f 100644 --- a/lib/ui/overview/overview_viewmodel.dart +++ b/lib/ui/overview/overview_viewmodel.dart @@ -8,6 +8,7 @@ import 'package:mobileraker/app/AppSetup.locator.dart'; import 'package:mobileraker/app/AppSetup.router.dart'; import 'package:mobileraker/dto/machine/Printer.dart'; import 'package:mobileraker/dto/machine/PrinterSetting.dart'; +import 'package:mobileraker/dto/machine/WebcamSetting.dart'; import 'package:mobileraker/dto/server/Klipper.dart'; import 'package:mobileraker/service/KlippyService.dart'; import 'package:mobileraker/service/PrinterService.dart'; @@ -56,12 +57,21 @@ class OverViewModel extends MultipleStreamViewModel { _selectedMachineService.selectedPrinter.hasValue; String get title => - '${_selectedMachineService.selectedPrinter.valueOrNull?.name??'Printer'} - Dashboard'; + '${_selectedMachineService.selectedPrinter.valueOrNull?.name ?? 'Printer'} - Dashboard'; - String get webCamUrl => 'http://192.168.178.135/webcam/?action=stream'; //TODO + WebcamSetting? _camHack() { + if (_printerSetting != null && _printerSetting!.cams.isNotEmpty) { + return _printerSetting?.cams.first; + } + return null; + } + + String? get webCamUrl { + return _camHack()?.url; + } double get webCamYSwap { - var vertical = Settings.getValue('webcam.swap-vertical', false); + var vertical = _camHack()?.flipVertical?? false; if (vertical) return pi; @@ -70,9 +80,9 @@ class OverViewModel extends MultipleStreamViewModel { } double get webCamXSwap { - var vertical = Settings.getValue('webcam.swap-horizontal', false); + var horizontal = _camHack()?.flipVertical?? false; - if (vertical) + if (horizontal) return pi; else return 0; diff --git a/lib/ui/printers/add/printers_add_view.dart b/lib/ui/printers/add/printers_add_view.dart index da8975a5..0354ac18 100644 --- a/lib/ui/printers/add/printers_add_view.dart +++ b/lib/ui/printers/add/printers_add_view.dart @@ -75,7 +75,7 @@ class PrintersAdd extends StatelessWidget { Text('Result: ${model.wsResult}'), Spacer(flex: 30), ElevatedButton( - onPressed: (model.dataReady && model.data != WebSocketState.connecting)? model.onTestConnectionTap:null, + onPressed: (model.data != WebSocketState.connecting)? model.onTestConnectionTap:null, child: Text('Test')) ], ), diff --git a/lib/ui/printers/add/printers_add_viewmodel.dart b/lib/ui/printers/add/printers_add_viewmodel.dart index a04ec975..30876f8e 100644 --- a/lib/ui/printers/add/printers_add_viewmodel.dart +++ b/lib/ui/printers/add/printers_add_viewmodel.dart @@ -59,11 +59,9 @@ class PrintersAddViewModel extends StreamViewModel { String? get wsUrl { var printerUrl = inputUrl; - if (printerUrl != null) { - return (Uri.parse(printerUrl).hasScheme) - ? printerUrl - : 'ws://$printerUrl/websocket'; - } + return (Uri.parse(printerUrl).hasScheme) + ? printerUrl + : 'ws://$printerUrl/websocket'; } onUrlEntered(value) { @@ -79,7 +77,6 @@ class PrintersAddViewModel extends StreamViewModel { printerUrl = 'ws://$printerUrl/websocket'; } var printerSetting = PrinterSetting(printerName, printerUrl); - print('$printerUrl'); _printerSettingService .addPrinter(printerSetting) .then((value) => _navigationService.popUntil((route) { diff --git a/lib/ui/printers/components/printers_slidable_viewmodel.dart b/lib/ui/printers/components/printers_slidable_viewmodel.dart index da542aa7..48219a40 100644 --- a/lib/ui/printers/components/printers_slidable_viewmodel.dart +++ b/lib/ui/printers/components/printers_slidable_viewmodel.dart @@ -3,6 +3,7 @@ import 'package:flutter/widgets.dart'; import 'package:flutter_slidable/flutter_slidable.dart'; import 'package:hive/hive.dart'; import 'package:mobileraker/app/AppSetup.locator.dart'; +import 'package:mobileraker/app/AppSetup.router.dart'; import 'package:mobileraker/dto/machine/Printer.dart'; import 'package:mobileraker/dto/machine/PrinterSetting.dart'; import 'package:mobileraker/dto/server/Klipper.dart'; @@ -16,7 +17,7 @@ const String _SelectedPrinterStreamKey = 'selectedPrinter'; const String _ServerStreamKey = 'server'; class PrintersSlidableViewModel extends MultipleStreamViewModel { - final _snackbarService = locator(); + final _navigationService = locator(); final _printerSettingsService = locator(); final _selectedMachineService = locator(); @@ -37,7 +38,7 @@ class PrintersSlidableViewModel extends MultipleStreamViewModel { } onEditTap() { - _snackbarService.showSnackbar(message: "WIP!... Not yet implemented."); + _navigationService.navigateTo(Routes.printersEdit, arguments: PrintersEditArguments(printerSetting: _printerSetting)); } onSetActiveTap() { diff --git a/lib/ui/printers/edit/printers_edit_view.dart b/lib/ui/printers/edit/printers_edit_view.dart new file mode 100644 index 00000000..392b2e05 --- /dev/null +++ b/lib/ui/printers/edit/printers_edit_view.dart @@ -0,0 +1,208 @@ +import 'package:flutter/material.dart'; +import 'package:flutter/widgets.dart'; +import 'package:flutter_form_builder/flutter_form_builder.dart'; +import 'package:flutter_icons/flutter_icons.dart'; +import 'package:mobileraker/WebSocket.dart'; +import 'package:mobileraker/dto/machine/PrinterSetting.dart'; +import 'package:mobileraker/dto/machine/WebcamSetting.dart'; +import 'package:stacked/stacked.dart'; + +import 'printers_edit_viewmodel.dart'; + +class PrintersEdit extends StatelessWidget { + const PrintersEdit({Key? key, required this.printerSetting}) + : super(key: key); + final PrinterSetting printerSetting; + + @override + Widget build(BuildContext context) { + return ViewModelBuilder.reactive( + builder: (context, model, child) { + return Scaffold( + appBar: AppBar( + title: Text('Edit'), + actions: [ + IconButton( + onPressed: model.onFormConfirm, + tooltip: 'Add printer', + icon: Icon(Icons.save_outlined)) + ], + ), + body: SingleChildScrollView( + child: FormBuilder( + key: model.formKey, + autovalidateMode: AutovalidateMode.onUserInteraction, + child: Padding( + padding: const EdgeInsets.all(10.0), + child: Column( + children: [ + _SectionHeader(title: 'General'), + FormBuilderTextField( + decoration: InputDecoration( + labelText: 'Displayname', + ), + name: 'printerName', + initialValue: model.printerSetting.name, + validator: FormBuilderValidators.compose( + [FormBuilderValidators.required(context)]), + ), + FormBuilderTextField( + decoration: InputDecoration( + labelText: 'Printer-Address', + helperText: model.wsUrl != null + ? 'WS-URL: ${model.wsUrl}' + : '' //TODO + ), + onChanged: model.onUrlEntered, + name: 'printerUrl', + initialValue: model.inputUrl, + validator: FormBuilderValidators.compose([ + FormBuilderValidators.required(context), + FormBuilderValidators.url(context, + protocols: ['ws', 'wss']) + ]), + ), + Divider(), + Row( + mainAxisAlignment: MainAxisAlignment.spaceBetween, + children: [ + Text( + 'WEBCAM', + style: TextStyle( + color: Theme.of(context).accentColor, + fontSize: 12.0, + fontWeight: FontWeight.bold, + ), + ), + TextButton.icon( + onPressed: model.onWebCamAdd, + label: Text('Add'), + icon: Icon(FlutterIcons.webcam_mco), + ) + ], + ), + ..._buildWebCams(model), + Divider(), + ], + ), + ), + ), + ), + ); + }, + viewModelBuilder: () => PrintersEditViewModel(printerSetting)); + } + + List _buildWebCams(PrintersEditViewModel model) { + if (model.webcams.isEmpty) { + return [ + Padding( + padding: const EdgeInsets.all(8.0), + child: Text('No webcams added'), + ) + ]; + } + + List camW = List.generate(model.webcams.length, (index) { + WebcamSetting cam = model.webcams[index]; + return _WebCamItem( + key: ValueKey(cam.uuid), + model: model, + cam: cam, + idx: index, + ); + }); + return camW; + } +} + +class _SectionHeader extends StatelessWidget { + final String title; + + const _SectionHeader({Key? key, required this.title}) : super(key: key); + + @override + Widget build(BuildContext context) { + return Align( + alignment: Alignment.centerLeft, + child: Text( + title.toUpperCase(), + style: TextStyle( + color: Theme.of(context).accentColor, + fontSize: 12.0, + fontWeight: FontWeight.bold, + ), + ), + ); + } +} + +class _WebCamItem extends StatelessWidget { + final WebcamSetting cam; + final PrintersEditViewModel model; + final int idx; + + _WebCamItem({ + Key? key, + required this.model, + required this.cam, + required this.idx, + }) : super(key: key); + + @override + Widget build(BuildContext context) { + return Card( + child: ExpansionTile( + maintainState: true, + tilePadding: EdgeInsets.symmetric(horizontal: 10), + childrenPadding: EdgeInsets.symmetric(horizontal: 10), + title: Text('CAM#$idx'), + children: [ + FormBuilderTextField( + decoration: InputDecoration( + labelText: 'Displayname', + ), + name: '${cam.uuid}-camName', + initialValue: cam.name, + validator: FormBuilderValidators.compose( + [FormBuilderValidators.required(context)]), + ), + FormBuilderTextField( + decoration: InputDecoration( + labelText: 'Webcam-Address', + helperText: 'Default address: http:///webcam/?action=stream'), + name: '${cam.uuid}-camUrl', + initialValue: cam.url, + validator: FormBuilderValidators.compose([ + FormBuilderValidators.required(context), + FormBuilderValidators.url(context, protocols: ['http', 'https'], requireProtocol: true) + ]), + ), + FormBuilderSwitch( + title: const Text('Flip vertical'), + decoration: InputDecoration( + border: InputBorder.none + ), + secondary: const Icon(FlutterIcons.swap_horizontal_mco), + initialValue: cam.flipVertical, + name: '${cam.uuid}-camFV', + ), + FormBuilderSwitch( + title: const Text('Flip horizontal'), + decoration: InputDecoration( + border: InputBorder.none + ), + secondary: const Icon(FlutterIcons.swap_vertical_mco), + initialValue: cam.flipHorizontal, + name: '${cam.uuid}-camFH', + ), + Align( + alignment: Alignment.centerLeft, + child: ElevatedButton( + onPressed: () => model.onWebCamRemove(cam), + child: Text('Remove'), + ), + ) + ])); + } +} diff --git a/lib/ui/printers/edit/printers_edit_viewmodel.dart b/lib/ui/printers/edit/printers_edit_viewmodel.dart new file mode 100644 index 00000000..61314a49 --- /dev/null +++ b/lib/ui/printers/edit/printers_edit_viewmodel.dart @@ -0,0 +1,89 @@ +import 'package:flutter/material.dart'; +import 'package:flutter/widgets.dart'; +import 'package:flutter_form_builder/flutter_form_builder.dart'; +import 'package:hive_flutter/hive_flutter.dart'; +import 'package:mobileraker/WebSocket.dart'; +import 'package:mobileraker/app/AppSetup.locator.dart'; +import 'package:mobileraker/app/AppSetup.router.dart'; +import 'package:mobileraker/dto/machine/PrinterSetting.dart'; +import 'package:mobileraker/dto/machine/WebcamSetting.dart'; +import 'package:mobileraker/service/PrinterSettingsService.dart'; +import 'package:stacked/stacked.dart'; +import 'package:stacked_services/stacked_services.dart'; + +class PrintersEditViewModel extends BaseViewModel { + final _navigationService = locator(); + final _snackbarService = locator(); + final _printerSettingService = locator(); + final _fbKey = GlobalKey(); + final PrinterSetting printerSetting; + late final webcams = printerSetting.cams.toList(); + late String inputUrl = printerSetting.wsUrl; + + PrintersEditViewModel(this.printerSetting); + + GlobalKey get formKey => _fbKey; + + String? get wsUrl { + var printerUrl = inputUrl; + return (Uri.parse(printerUrl).hasScheme) + ? printerUrl + : 'ws://$printerUrl/websocket'; + } + + onUrlEntered(value) { + inputUrl = value; + notifyListeners(); + } + + onWebCamAdd() { + WebcamSetting cam = WebcamSetting('New Webcam', + 'http://${Uri.parse(printerSetting.wsUrl).host}/webcam/?action=stream'); + webcams.add(cam); + + notifyListeners(); + } + + onWebCamRemove(WebcamSetting toRemoved) { + webcams.remove(toRemoved); + webcams.forEach((element) { + _saveCam(element); + }); + notifyListeners(); + } + + _saveCam(WebcamSetting toSave) { + _fbKey.currentState?.save(); + var name = _fbKey.currentState!.value['${toSave.uuid}-camName']; + var url = _fbKey.currentState!.value['${toSave.uuid}-camUrl']; + var fH = _fbKey.currentState!.value['${toSave.uuid}-camFH']; + var fV = _fbKey.currentState!.value['${toSave.uuid}-camFV']; + if (name != null) toSave.name = name; + if (url != null) toSave.url = url; + if (fH != null) toSave.flipHorizontal = fH; + if (fV != null) toSave.flipVertical = fV; + } + + onFormConfirm() { + if (_fbKey.currentState!.saveAndValidate()) { + var printerName = _fbKey.currentState!.value['printerName']; + var printerUrl = _fbKey.currentState!.value['printerUrl']; + if (!Uri.parse(printerUrl).hasScheme) { + printerUrl = 'ws://$printerUrl/websocket'; + } + webcams.forEach((element) { + _saveCam(element); + }); + printerSetting + ..name = printerName + ..wsUrl = printerUrl + ..cams = webcams; + print(printerSetting.cams); + printerSetting + .save() + .then((value) => _navigationService.popUntil((route) { + return route.settings.name == Routes.printers; + })); + } + } +} diff --git a/lib/ui/printers/printers_view.dart b/lib/ui/printers/printers_view.dart index da80ce76..a07b313a 100644 --- a/lib/ui/printers/printers_view.dart +++ b/lib/ui/printers/printers_view.dart @@ -12,14 +12,15 @@ class Printers extends StatelessWidget { return Scaffold( appBar: AppBar( title: Text("Printers"), + actions: [ + IconButton( + onPressed: model.onAddPrinterPressed, + tooltip: 'Add Printer', + icon: Icon(Icons.add)) + ], ), body: getBody(model, context), - floatingActionButton: FloatingActionButton( - mini: true, - tooltip: "Add Printer", - child: Icon(Icons.add), - onPressed: model.onAddPrinterPressed, - ), + floatingActionButtonLocation: FloatingActionButtonLocation.miniCenterFloat, ); @@ -37,7 +38,6 @@ class Printers extends StatelessWidget { )); return ListView.builder( - padding: EdgeInsets.only(top: 8), itemCount: settings.length, itemBuilder: (context, index) { var cur = settings.elementAt(index); diff --git a/lib/ui/printers/printers_viewmodel.dart b/lib/ui/printers/printers_viewmodel.dart index 443f0d16..5a6d2bd8 100644 --- a/lib/ui/printers/printers_viewmodel.dart +++ b/lib/ui/printers/printers_viewmodel.dart @@ -28,4 +28,6 @@ class PrintersViewModel extends StreamViewModel> { onAddPrinterPressed() { _navigationService.navigateTo(Routes.printersAdd); } + + }