import 'package:InvenTree/api.dart'; import 'package:InvenTree/inventree/stock.dart'; import 'package:InvenTree/preferences.dart'; import 'package:InvenTree/widget/progress.dart'; import 'package:InvenTree/widget/refreshable_state.dart'; import 'package:InvenTree/widget/fields.dart'; import 'package:InvenTree/widget/dialogs.dart'; import 'package:InvenTree/widget/search.dart'; import 'package:InvenTree/widget/snacks.dart'; import 'package:InvenTree/widget/stock_detail.dart'; import 'package:flutter/cupertino.dart'; import 'package:flutter/material.dart'; import 'package:flutter/foundation.dart'; import 'package:font_awesome_flutter/font_awesome_flutter.dart'; import 'package:flutter_gen/gen_l10n/app_localizations.dart'; class LocationDisplayWidget extends StatefulWidget { LocationDisplayWidget(this.location, {Key key}) : super(key: key); final InvenTreeStockLocation location; final String title = "Location"; @override _LocationDisplayState createState() => _LocationDisplayState(location); } class _LocationDisplayState extends RefreshableState { final InvenTreeStockLocation location; final _editLocationKey = GlobalKey(); @override String getAppBarTitle(BuildContext context) { return "Stock Location"; } @override List getAppBarActions(BuildContext context) { return [ IconButton( icon: FaIcon(FontAwesomeIcons.search), onPressed: () { showSearch( context: context, delegate: StockSearchDelegate(context, filters: {"location": "${location.pk}"}) ); } ), IconButton( icon: FaIcon(FontAwesomeIcons.edit), tooltip: I18N.of(context).edit, onPressed: _editLocationDialog, ) ]; } void _editLocation(Map values) async { final bool result = await location.update(context, values: values); showSnackIcon( refreshableKey, result ? "Location edited" : "Location editing failed", success: result ); refresh(); } void _editLocationDialog() { // Values which an be edited var _name; var _description; showFormDialog(I18N.of(context).editLocation, key: _editLocationKey, callback: () { _editLocation({ "name": _name, "description": _description }); }, fields: [ StringField( label: I18N.of(context).name, initial: location.name, onSaved: (value) => _name = value, ), StringField( label: I18N.of(context).description, initial: location.description, onSaved: (value) => _description = value, ) ] ); } _LocationDisplayState(this.location) {} List _sublocations = List(); String _locationFilter = ''; List get sublocations { if (_locationFilter.isEmpty || _sublocations.isEmpty) { return _sublocations; } else { return _sublocations.where((loc) => loc.filter(_locationFilter)).toList(); } } List _items = List(); @override Future onBuild(BuildContext context) async { refresh(); } @override Future request(BuildContext context) async { int pk = location?.pk ?? -1; // Reload location information if (location != null) { await location.reload(context); } // Request a list of sub-locations under this one await InvenTreeStockLocation().list(context, filters: {"parent": "$pk"}).then((var locs) { _sublocations.clear(); for (var loc in locs) { if (loc is InvenTreeStockLocation) { _sublocations.add(loc); } } }); setState(() {}); await InvenTreeStockItem().list(context, filters: {"location": "$pk"}).then((var items) { _items.clear(); for (var item in items) { if (item is InvenTreeStockItem) { _items.add(item); } } }); setState(() {}); } Widget locationDescriptionCard() { if (location == null) { return Card( child: ListTile( title: Text(I18N.of(context).stockTopLevel), ) ); } else { return Card( child: Column( children: [ ListTile( title: Text("${location.name}"), subtitle: Text("${location.description}"), ), ListTile( title: Text("Parent Category"), subtitle: Text("${location.parentpathstring}"), leading: FaIcon(FontAwesomeIcons.levelUpAlt), onTap: () { if (location.parentId < 0) { Navigator.push(context, MaterialPageRoute(builder: (context) => LocationDisplayWidget(null))); } else { InvenTreeStockLocation().get(context, location.parentId).then((var loc) { if (loc is InvenTreeStockLocation) { Navigator.push(context, MaterialPageRoute(builder: (context) => LocationDisplayWidget(loc))); } }); } }, ) ] ) ); } } @override Widget getBottomNavBar(BuildContext context) { return BottomNavigationBar( currentIndex: tabIndex, onTap: onTabSelectionChanged, items: [ BottomNavigationBarItem( icon: FaIcon(FontAwesomeIcons.sitemap), label: I18N.of(context).details, ), BottomNavigationBarItem( icon: FaIcon(FontAwesomeIcons.boxes), label: I18N.of(context).stock, ), // TODO - Add in actions when they are written... /* BottomNavigationBarItem( icon: FaIcon(FontAwesomeIcons.wrench), label: I18N.of(context).actions, ) */ ] ); } Widget getSelectedWidget(int index) { switch (index) { case 0: return ListView( children: detailTiles(), ); case 1: return ListView( children: stockTiles(), ); case 2: return ListView( children: ListTile.divideTiles( context: context, tiles: actionTiles() ).toList() ); default: return null; } } @override Widget getBody(BuildContext context) { return getSelectedWidget(tabIndex); } List detailTiles() { List tiles = [ locationDescriptionCard(), ListTile( title: Text( I18N.of(context).sublocations, style: TextStyle(fontWeight: FontWeight.bold), ), trailing: sublocations.length > 0 ? Text("${sublocations.length}") : null, ), ]; if (loading) { tiles.add(progressIndicator()); } else if (_sublocations.length > 0) { tiles.add(SublocationList(_sublocations)); } else { tiles.add(ListTile( title: Text("No Sublocations"), subtitle: Text("No sublocations available") )); } return tiles; } List stockTiles() { List tiles = [ locationDescriptionCard(), ListTile( title: Text( I18N.of(context).stockItems, style: TextStyle(fontWeight: FontWeight.bold) ), trailing: _items.length > 0 ? Text("${_items.length}") : null, ) ]; if (loading) { tiles.add(progressIndicator()); } else if (_items.length > 0) { tiles.add(StockList(_items)); } else { tiles.add(ListTile( title: Text("No Stock Items"), subtitle: Text("No stock items available in this location") )); } return tiles; } List actionTiles() { List tiles = []; tiles.add(locationDescriptionCard()); // Scan items into location tiles.add( ListTile( title: Text("Scan in Stock Item"), leading: FaIcon(FontAwesomeIcons.exchangeAlt), trailing: FaIcon(FontAwesomeIcons.qrcode), onTap: null, ) ); // Move location into another location tiles.add( ListTile( title: Text("Move Stock Location"), leading: FaIcon(FontAwesomeIcons.sitemap), trailing: FaIcon(FontAwesomeIcons.qrcode), ) ); return tiles; } } class SublocationList extends StatelessWidget { final List _locations; SublocationList(this._locations); void _openLocation(BuildContext context, int pk) { InvenTreeStockLocation().get(context, pk).then((var loc) { if (loc is InvenTreeStockLocation) { Navigator.push(context, MaterialPageRoute(builder: (context) => LocationDisplayWidget(loc))); } }); } Widget _build(BuildContext context, int index) { InvenTreeStockLocation loc = _locations[index]; return ListTile( title: Text('${loc.name}'), subtitle: Text("${loc.description}"), trailing: Text("${loc.itemcount}"), onTap: () { _openLocation(context, loc.pk); }, ); } @override Widget build(BuildContext context) { return ListView.separated( shrinkWrap: true, physics: ClampingScrollPhysics(), itemBuilder: _build, separatorBuilder: (_, __) => const Divider(height: 3), itemCount: _locations.length ); } } class StockList extends StatelessWidget { final List _items; StockList(this._items); void _openItem(BuildContext context, int pk) { InvenTreeStockItem().get(context, pk).then((var item) { if (item is InvenTreeStockItem) { Navigator.push(context, MaterialPageRoute(builder: (context) => StockDetailWidget(item))); } }); } Widget _build(BuildContext context, int index) { InvenTreeStockItem item = _items[index]; return ListTile( title: Text("${item.partName}"), subtitle: Text("${item.partDescription}"), leading: InvenTreeAPI().getImage( item.partThumbnail, width: 40, height: 40, ), trailing: Text("${item.displayQuantity}", style: TextStyle(fontWeight: FontWeight.bold), ), onTap: () { _openItem(context, item.pk); }, ); } @override Widget build(BuildContext context) { return ListView.separated( shrinkWrap: true, physics: ClampingScrollPhysics(), separatorBuilder: (_, __) => const Divider(height: 3), itemBuilder: _build, itemCount: _items.length); } }