mirror of
https://github.com/inventree/inventree-app.git
synced 2025-05-05 00:38:54 +00:00
Merge branch 'stock-actions'
This commit is contained in:
commit
14617686b2
21
lib/api.dart
21
lib/api.dart
@ -59,7 +59,7 @@ class InvenTreeAPI {
|
||||
|
||||
String makeApiUrl(String endpoint) {
|
||||
|
||||
return apiUrl + endpoint;
|
||||
return _makeUrl("/api/" + endpoint);
|
||||
}
|
||||
|
||||
String makeUrl(String endpoint) {
|
||||
@ -272,20 +272,18 @@ class InvenTreeAPI {
|
||||
}
|
||||
|
||||
// Perform a POST request
|
||||
Future<http.Response> post(String url, {Map<String, String> body}) async {
|
||||
Future<http.Response> post(String url, {Map<String, dynamic> body}) async {
|
||||
|
||||
var _url = makeApiUrl(url);
|
||||
var _headers = defaultHeaders();
|
||||
var _body = Map<String, String>();
|
||||
var _headers = jsonHeaders();
|
||||
|
||||
// Copy across provided data
|
||||
body.forEach((K, V) => _body[K] = V);
|
||||
print("POST: ${_url} -> ${body.toString()}");
|
||||
|
||||
print("POST: " + _url);
|
||||
var data = jsonEncode(body);
|
||||
|
||||
return http.post(_url,
|
||||
headers: _headers,
|
||||
body: _body,
|
||||
body: data,
|
||||
);
|
||||
}
|
||||
|
||||
@ -324,6 +322,13 @@ class InvenTreeAPI {
|
||||
return headers;
|
||||
}
|
||||
|
||||
Map<String, String> jsonHeaders() {
|
||||
|
||||
var headers = defaultHeaders();
|
||||
headers['Content-Type'] = 'application/json';
|
||||
return headers;
|
||||
}
|
||||
|
||||
String _authorizationHeader () {
|
||||
if (_token.isNotEmpty) {
|
||||
return "Token $_token";
|
||||
|
@ -56,7 +56,8 @@ class InvenTreeModel {
|
||||
return obj;
|
||||
}
|
||||
|
||||
String get url{ return path.join(URL, pk.toString()); }
|
||||
// Return the API detail endpoint for this Model object
|
||||
String get url => "${URL}/${pk}/";
|
||||
|
||||
/*
|
||||
// Search this Model type in the database
|
||||
@ -79,6 +80,25 @@ class InvenTreeModel {
|
||||
// A map of "default" headers to use when performing a GET request
|
||||
Map<String, String> defaultGetFilters() { return Map<String, String>(); }
|
||||
|
||||
/*
|
||||
* Reload this object, by requesting data from the server
|
||||
*/
|
||||
Future<bool> reload() async {
|
||||
|
||||
var response = await api.get(url, params: defaultGetFilters());
|
||||
|
||||
if (response.statusCode != 200) {
|
||||
print("Error retrieving data");
|
||||
return false;
|
||||
}
|
||||
|
||||
final Map<String, dynamic> data = json.decode(response.body);
|
||||
|
||||
jsondata = data;
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
// Return the detail view for the associated pk
|
||||
Future<InvenTreeModel> get(int pk, {Map<String, String> filters}) async {
|
||||
|
||||
@ -102,7 +122,7 @@ class InvenTreeModel {
|
||||
|
||||
print("GET: $addr ${params.toString()}");
|
||||
|
||||
var response = await InvenTreeAPI().get(addr, params: params);
|
||||
var response = await api.get(addr, params: params);
|
||||
|
||||
if (response.statusCode != 200) {
|
||||
print("Error retrieving data");
|
||||
@ -134,7 +154,7 @@ class InvenTreeModel {
|
||||
// TODO - Add "timeout"
|
||||
// TODO - Add error catching
|
||||
|
||||
var response = await InvenTreeAPI().get(URL, params:params);
|
||||
var response = await api.get(URL, params:params);
|
||||
|
||||
// A list of "InvenTreeModel" items
|
||||
List<InvenTreeModel> results = new List<InvenTreeModel>();
|
||||
|
@ -1,5 +1,6 @@
|
||||
import 'dart:convert';
|
||||
|
||||
import 'package:http/http.dart' as http;
|
||||
import 'model.dart';
|
||||
|
||||
import 'package:InvenTree/api.dart';
|
||||
@ -121,6 +122,8 @@ class InvenTreeStockItem extends InvenTreeModel {
|
||||
|
||||
int get locationId => jsondata['location'] as int ?? -1;
|
||||
|
||||
bool isSerialized() => serialNumber != null && quantity.toInt() == 1;
|
||||
|
||||
String get locationName {
|
||||
String loc = '';
|
||||
|
||||
@ -153,6 +156,54 @@ class InvenTreeStockItem extends InvenTreeModel {
|
||||
|
||||
return item;
|
||||
}
|
||||
|
||||
Future<http.Response> countStock(double quan, {String notes}) async {
|
||||
|
||||
// Cannot 'count' a serialized StockItem
|
||||
if (isSerialized()) {
|
||||
return null;
|
||||
}
|
||||
|
||||
// Cannot count negative stock
|
||||
if (quan < 0) {
|
||||
return null;
|
||||
}
|
||||
|
||||
return api.post("/stock/count/", body: {
|
||||
"item": {
|
||||
"pk": "${pk}",
|
||||
"quantity": "${quan}",
|
||||
},
|
||||
"notes": notes ?? '',
|
||||
});
|
||||
}
|
||||
|
||||
Future<http.Response> addStock(double quan, {String notes}) async {
|
||||
|
||||
if (isSerialized() || quan <= 0) return null;
|
||||
|
||||
return api.post("/stock/add/", body: {
|
||||
"item": {
|
||||
"pk": "${pk}",
|
||||
"quantity": "${quan}",
|
||||
},
|
||||
"notes": notes ?? '',
|
||||
});
|
||||
}
|
||||
|
||||
Future<http.Response> removeStock(double quan, {String notes}) async {
|
||||
|
||||
if (isSerialized() || quan <= 0) return null;
|
||||
|
||||
return api.post("/stock/remove/", body: {
|
||||
"item": {
|
||||
"pk": "${pk}",
|
||||
"quantity": "${quan}",
|
||||
},
|
||||
"notes": notes ?? '',
|
||||
});
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
|
||||
@ -194,5 +245,4 @@ class InvenTreeStockLocation extends InvenTreeModel {
|
||||
|
||||
return loc;
|
||||
}
|
||||
|
||||
}
|
@ -4,6 +4,7 @@ import 'package:InvenTree/inventree/stock.dart';
|
||||
import 'package:InvenTree/widget/category_display.dart';
|
||||
import 'package:InvenTree/widget/company_list.dart';
|
||||
import 'package:InvenTree/widget/location_display.dart';
|
||||
import 'package:InvenTree/widget/search.dart';
|
||||
import 'package:InvenTree/widget/drawer.dart';
|
||||
import 'package:flutter/cupertino.dart';
|
||||
import 'package:flutter/material.dart';
|
||||
@ -192,7 +193,8 @@ class _MyHomePageState extends State<MyHomePage> {
|
||||
void _search() {
|
||||
if (!InvenTreeAPI().checkConnection(context)) return;
|
||||
|
||||
// TODO
|
||||
Navigator.push(context, MaterialPageRoute(builder: (context) => SearchWidget()));
|
||||
|
||||
}
|
||||
|
||||
void _scan() {
|
||||
@ -251,7 +253,7 @@ class _MyHomePageState extends State<MyHomePage> {
|
||||
IconButton(
|
||||
icon: FaIcon(FontAwesomeIcons.search),
|
||||
tooltip: 'Search',
|
||||
onPressed: null,
|
||||
onPressed: _search,
|
||||
),
|
||||
],
|
||||
),
|
||||
@ -271,7 +273,7 @@ class _MyHomePageState extends State<MyHomePage> {
|
||||
IconButton(
|
||||
icon: new FaIcon(FontAwesomeIcons.search),
|
||||
tooltip: 'Search',
|
||||
onPressed: _unsupported,
|
||||
onPressed: _search,
|
||||
),
|
||||
Text("Search"),
|
||||
],
|
||||
|
@ -1,6 +1,7 @@
|
||||
import 'package:InvenTree/api.dart';
|
||||
import 'package:InvenTree/barcode.dart';
|
||||
import 'package:InvenTree/widget/company_list.dart';
|
||||
import 'package:InvenTree/widget/search.dart';
|
||||
import 'package:flutter/material.dart';
|
||||
|
||||
import 'package:InvenTree/api.dart';
|
||||
@ -32,6 +33,11 @@ class InvenTreeDrawer extends StatelessWidget {
|
||||
Navigator.pushNamedAndRemoveUntil(context, "/", (r) => false);
|
||||
}
|
||||
|
||||
void _search() {
|
||||
_closeDrawer();
|
||||
Navigator.push(context, MaterialPageRoute(builder: (context) => SearchWidget()));
|
||||
}
|
||||
|
||||
/*
|
||||
* Launch the camera to scan a QR code.
|
||||
* Upon successful scan, data are passed off to be decoded.
|
||||
@ -102,7 +108,7 @@ class InvenTreeDrawer extends StatelessWidget {
|
||||
new ListTile(
|
||||
title: new Text("Search"),
|
||||
leading: new FaIcon(FontAwesomeIcons.search),
|
||||
onTap: null,
|
||||
onTap: _search,
|
||||
),
|
||||
new ListTile(
|
||||
title: new Text("Scan Barcode"),
|
||||
|
@ -160,6 +160,11 @@ class _PartDisplayState extends State<PartDetailWidget> {
|
||||
title: Text("Part Details"),
|
||||
),
|
||||
drawer: new InvenTreeDrawer(context),
|
||||
floatingActionButton: FloatingActionButton(
|
||||
child: FaIcon(FontAwesomeIcons.ellipsisH),
|
||||
// TODO - Add pop-up icons
|
||||
// Ref: https://stackoverflow.com/questions/46480221/flutter-floating-action-button-with-speed-dial#46480722
|
||||
),
|
||||
body: Center(
|
||||
child: ListView(
|
||||
children: partTiles(),
|
||||
|
33
lib/widget/search.dart
Normal file
33
lib/widget/search.dart
Normal file
@ -0,0 +1,33 @@
|
||||
|
||||
import 'package:InvenTree/widget/drawer.dart';
|
||||
import 'package:flutter/cupertino.dart';
|
||||
import 'package:flutter/material.dart';
|
||||
|
||||
class SearchWidget extends StatefulWidget {
|
||||
|
||||
@override
|
||||
_SearchState createState() => _SearchState();
|
||||
}
|
||||
|
||||
|
||||
class _SearchState extends State<SearchWidget> {
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
|
||||
return Scaffold(
|
||||
appBar: AppBar(
|
||||
title: Text("Search"),
|
||||
),
|
||||
drawer: new InvenTreeDrawer(context),
|
||||
body: Center(
|
||||
child: ListView(
|
||||
children: <Widget>[
|
||||
|
||||
],
|
||||
)
|
||||
)
|
||||
);
|
||||
|
||||
}
|
||||
}
|
@ -10,7 +10,9 @@ import 'package:flutter/material.dart';
|
||||
import 'package:InvenTree/api.dart';
|
||||
|
||||
import 'package:InvenTree/widget/drawer.dart';
|
||||
import 'package:flutter/services.dart';
|
||||
import 'package:font_awesome_flutter/font_awesome_flutter.dart';
|
||||
import 'package:flutter_speed_dial/flutter_speed_dial.dart';
|
||||
|
||||
class StockDetailWidget extends StatefulWidget {
|
||||
|
||||
@ -25,12 +27,270 @@ class StockDetailWidget extends StatefulWidget {
|
||||
|
||||
class _StockItemDisplayState extends State<StockDetailWidget> {
|
||||
|
||||
final TextEditingController _quantityController = TextEditingController();
|
||||
final TextEditingController _notesController = TextEditingController();
|
||||
|
||||
final _addStockKey = GlobalKey<FormState>();
|
||||
final _removeStockKey = GlobalKey<FormState>();
|
||||
final _countStockKey = GlobalKey<FormState>();
|
||||
final _moveStockKey = GlobalKey<FormState>();
|
||||
final _editStockKey = GlobalKey<FormState>();
|
||||
|
||||
_StockItemDisplayState(this.item) {
|
||||
// TODO
|
||||
}
|
||||
|
||||
final InvenTreeStockItem item;
|
||||
|
||||
/**
|
||||
* Function to reload the page data
|
||||
*/
|
||||
Future<void> _refresh() async {
|
||||
|
||||
await item.reload();
|
||||
setState(() {});
|
||||
}
|
||||
|
||||
void _editStockItem() {
|
||||
// TODO - Form for editing stock item
|
||||
}
|
||||
|
||||
void _editStockItemDialog() {
|
||||
|
||||
return;
|
||||
// TODO - Finish implementing this
|
||||
|
||||
showDialog(
|
||||
context: context,
|
||||
builder: (BuildContext context) {
|
||||
return AlertDialog(
|
||||
title: Text("Edit Stock Item"),
|
||||
actions: <Widget>[
|
||||
FlatButton(
|
||||
child: Text("Save"),
|
||||
onPressed: () {
|
||||
if (_editStockKey.currentState.validate()) {
|
||||
// TODO
|
||||
}
|
||||
},
|
||||
)
|
||||
],
|
||||
);
|
||||
}
|
||||
);
|
||||
}
|
||||
|
||||
void _addStock() async {
|
||||
|
||||
Navigator.of(context).pop();
|
||||
|
||||
double quantity = double.parse(_quantityController.text);
|
||||
_quantityController.clear();
|
||||
|
||||
// Await response to prevent the button from being pressed multiple times
|
||||
var response = await item.addStock(quantity, notes: _notesController.text);
|
||||
_notesController.clear();
|
||||
|
||||
// TODO - Handle error cases
|
||||
_refresh();
|
||||
}
|
||||
|
||||
void _addStockDialog() async {
|
||||
showDialog(context: context,
|
||||
builder: (BuildContext context) {
|
||||
return AlertDialog(
|
||||
title: Text("Add Stock"),
|
||||
actions: <Widget>[
|
||||
FlatButton(
|
||||
child: Text("Add"),
|
||||
onPressed: () {
|
||||
if (_addStockKey.currentState.validate()) _addStock();
|
||||
},
|
||||
)
|
||||
],
|
||||
content: Form(
|
||||
key: _addStockKey,
|
||||
child: Column(
|
||||
mainAxisAlignment: MainAxisAlignment.start,
|
||||
crossAxisAlignment: CrossAxisAlignment.start,
|
||||
mainAxisSize: MainAxisSize.min,
|
||||
children: <Widget>[
|
||||
Text("Current Quantity: ${item.quantity}"),
|
||||
TextFormField(
|
||||
decoration: InputDecoration(
|
||||
labelText: "Add stock",
|
||||
),
|
||||
keyboardType: TextInputType.numberWithOptions(signed: false, decimal: true),
|
||||
controller: _quantityController,
|
||||
validator: (value) {
|
||||
if (value.isEmpty) return "Value cannot be empty";
|
||||
|
||||
double quantity = double.tryParse(value);
|
||||
if (quantity == null) return "Value cannot be converted to a number";
|
||||
if (quantity <= 0) return "Value must be positive";
|
||||
|
||||
return null;
|
||||
},
|
||||
),
|
||||
TextFormField(
|
||||
decoration: InputDecoration(
|
||||
labelText: "Notes",
|
||||
),
|
||||
controller: _notesController,
|
||||
)
|
||||
],
|
||||
)
|
||||
),
|
||||
);
|
||||
}
|
||||
);
|
||||
// TODO - Form for adding stock
|
||||
}
|
||||
|
||||
void _removeStock() async {
|
||||
Navigator.of(context).pop();
|
||||
|
||||
double quantity = double.parse(_quantityController.text);
|
||||
_quantityController.clear();
|
||||
|
||||
var response = await item.removeStock(quantity, notes: _notesController.text);
|
||||
_notesController.clear();
|
||||
|
||||
// TODO - Handle error cases
|
||||
|
||||
_refresh();
|
||||
}
|
||||
|
||||
void _removeStockDialog() {
|
||||
showDialog(context: context,
|
||||
builder: (BuildContext context) {
|
||||
return AlertDialog(
|
||||
title: Text("Remove Stock"),
|
||||
actions: <Widget>[
|
||||
FlatButton(
|
||||
child: Text("Remove"),
|
||||
onPressed: () {
|
||||
if (_removeStockKey.currentState.validate()) _removeStock();
|
||||
},
|
||||
)
|
||||
],
|
||||
content: Form(
|
||||
key: _removeStockKey,
|
||||
child: Column(
|
||||
mainAxisSize: MainAxisSize.min,
|
||||
mainAxisAlignment: MainAxisAlignment.start,
|
||||
crossAxisAlignment: CrossAxisAlignment.start,
|
||||
children: <Widget>[
|
||||
Text("Current quantity: ${item.quantity}"),
|
||||
TextFormField(
|
||||
decoration: InputDecoration(
|
||||
labelText: "Remove stock",
|
||||
),
|
||||
controller: _quantityController,
|
||||
keyboardType: TextInputType.numberWithOptions(signed: false, decimal: true),
|
||||
validator: (value) {
|
||||
if (value.isEmpty) return "Value cannot be empty";
|
||||
|
||||
double quantity = double.tryParse(value);
|
||||
|
||||
if (quantity == null) return "Value cannot be converted to a number";
|
||||
if (quantity <= 0) return "Value must be positive";
|
||||
|
||||
if (quantity > item.quantity) return "Cannot take more than current quantity";
|
||||
|
||||
return null;
|
||||
},
|
||||
),
|
||||
TextFormField(
|
||||
decoration: InputDecoration(
|
||||
labelText: "Notes",
|
||||
),
|
||||
controller: _notesController,
|
||||
),
|
||||
],
|
||||
)
|
||||
),
|
||||
);
|
||||
}
|
||||
);
|
||||
}
|
||||
|
||||
void _countStock() async {
|
||||
|
||||
Navigator.of(context).pop();
|
||||
|
||||
double quantity = double.parse(_quantityController.text);
|
||||
_quantityController.clear();
|
||||
|
||||
var response = await item.countStock(quantity, notes: _notesController.text);
|
||||
_notesController.clear();
|
||||
|
||||
// TODO - Handle error cases
|
||||
|
||||
_refresh();
|
||||
}
|
||||
|
||||
void _countStockDialog() async {
|
||||
showDialog(
|
||||
context: context,
|
||||
builder: (BuildContext context) {
|
||||
return AlertDialog(
|
||||
title: Text("Count Stock"),
|
||||
actions: <Widget>[
|
||||
FlatButton(
|
||||
child: Text("Count"),
|
||||
onPressed: () {
|
||||
if (_countStockKey.currentState.validate()) _countStock();
|
||||
},
|
||||
)
|
||||
],
|
||||
content: Form(
|
||||
key: _countStockKey,
|
||||
child: Column(
|
||||
mainAxisAlignment: MainAxisAlignment.start,
|
||||
crossAxisAlignment: CrossAxisAlignment.start,
|
||||
mainAxisSize: MainAxisSize.min,
|
||||
children: <Widget>[
|
||||
TextFormField(
|
||||
decoration: InputDecoration(
|
||||
labelText: "Count stock",
|
||||
hintText: "${item.quantity}",
|
||||
),
|
||||
controller: _quantityController,
|
||||
keyboardType: TextInputType.numberWithOptions(signed: false, decimal: true),
|
||||
validator: (value) {
|
||||
if (value.isEmpty) return "Value cannot be empty";
|
||||
|
||||
double quantity = double.tryParse(value);
|
||||
if (quantity == null) return "Value cannot be converted to a number";
|
||||
if (quantity < 0) return "Value cannot be negative";
|
||||
|
||||
return null;
|
||||
},
|
||||
),
|
||||
TextFormField(
|
||||
decoration: InputDecoration(
|
||||
labelText: "Notes",
|
||||
),
|
||||
controller: _notesController,
|
||||
)
|
||||
],
|
||||
)
|
||||
)
|
||||
);
|
||||
}
|
||||
);
|
||||
}
|
||||
|
||||
|
||||
void _transferStock(int location) {
|
||||
// TODO
|
||||
}
|
||||
|
||||
void _transferStockDialog() {
|
||||
// TODO - Form for transferring stock
|
||||
}
|
||||
|
||||
/*
|
||||
* Construct a list of detail elements about this StockItem.
|
||||
* The number of elements may vary depending on the StockItem details
|
||||
@ -49,7 +309,7 @@ class _StockItemDisplayState extends State<StockDetailWidget> {
|
||||
),
|
||||
trailing: IconButton(
|
||||
icon: FaIcon(FontAwesomeIcons.edit),
|
||||
onPressed: null,
|
||||
onPressed: _editStockItemDialog,
|
||||
)
|
||||
)
|
||||
)
|
||||
@ -73,13 +333,25 @@ class _StockItemDisplayState extends State<StockDetailWidget> {
|
||||
);
|
||||
|
||||
// Quantity information
|
||||
tiles.add(
|
||||
ListTile(
|
||||
title: Text("Quantity"),
|
||||
leading: FaIcon(FontAwesomeIcons.cubes),
|
||||
trailing: Text("${item.quantity}"),
|
||||
)
|
||||
);
|
||||
if (item.isSerialized()) {
|
||||
tiles.add(
|
||||
ListTile(
|
||||
title: Text("Serial Number"),
|
||||
leading: FaIcon(FontAwesomeIcons.hashtag),
|
||||
trailing: Text("${item.serialNumber}"),
|
||||
)
|
||||
);
|
||||
} else {
|
||||
tiles.add(
|
||||
ListTile(
|
||||
title: Text("Quantity"),
|
||||
leading: FaIcon(FontAwesomeIcons.cubes),
|
||||
trailing: Text("${item.quantity}"),
|
||||
)
|
||||
);
|
||||
|
||||
}
|
||||
|
||||
|
||||
// Location information
|
||||
if (item.locationName.isNotEmpty) {
|
||||
@ -152,6 +424,46 @@ class _StockItemDisplayState extends State<StockDetailWidget> {
|
||||
return tiles;
|
||||
}
|
||||
|
||||
/*
|
||||
* Return a list of context-sensitive action buttons.
|
||||
* Not all buttons will be avaialable for a given StockItem,
|
||||
* depending on the properties of that StockItem
|
||||
*/
|
||||
List<SpeedDialChild> actionButtons() {
|
||||
var buttons = List<SpeedDialChild>();
|
||||
|
||||
// The following actions only apply if the StockItem is not serialized
|
||||
if (!item.isSerialized()) {
|
||||
buttons.add(SpeedDialChild(
|
||||
child: Icon(FontAwesomeIcons.plusCircle),
|
||||
label: "Add Stock",
|
||||
onTap: _addStockDialog,
|
||||
)
|
||||
);
|
||||
|
||||
buttons.add(SpeedDialChild(
|
||||
child: Icon(FontAwesomeIcons.minusCircle),
|
||||
label: "Remove Stock",
|
||||
onTap: _removeStockDialog,
|
||||
),
|
||||
);
|
||||
|
||||
buttons.add(SpeedDialChild(
|
||||
child: Icon(FontAwesomeIcons.checkCircle),
|
||||
label: "Count Stock",
|
||||
onTap: _countStockDialog,
|
||||
));
|
||||
}
|
||||
|
||||
buttons.add(SpeedDialChild(
|
||||
child: Icon(FontAwesomeIcons.exchangeAlt),
|
||||
label: "Transfer Stock",
|
||||
onTap: _transferStockDialog,
|
||||
));
|
||||
|
||||
return buttons;
|
||||
}
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
return Scaffold(
|
||||
@ -159,9 +471,18 @@ class _StockItemDisplayState extends State<StockDetailWidget> {
|
||||
title: Text("Stock Item"),
|
||||
),
|
||||
drawer: new InvenTreeDrawer(context),
|
||||
floatingActionButton: SpeedDial(
|
||||
visible: true,
|
||||
animatedIcon: AnimatedIcons.menu_close,
|
||||
heroTag: 'stock-item-fab',
|
||||
children: actionButtons(),
|
||||
),
|
||||
body: Center(
|
||||
child: ListView(
|
||||
children: stockTiles(),
|
||||
child: new RefreshIndicator(
|
||||
onRefresh: _refresh,
|
||||
child: ListView(
|
||||
children: stockTiles(),
|
||||
)
|
||||
)
|
||||
)
|
||||
);
|
||||
|
@ -83,6 +83,13 @@ packages:
|
||||
url: "https://pub.dartlang.org"
|
||||
source: hosted
|
||||
version: "0.7.4"
|
||||
flutter_speed_dial:
|
||||
dependency: "direct main"
|
||||
description:
|
||||
name: flutter_speed_dial
|
||||
url: "https://pub.dartlang.org"
|
||||
source: hosted
|
||||
version: "1.2.5"
|
||||
flutter_svg:
|
||||
dependency: transitive
|
||||
description:
|
||||
@ -225,7 +232,7 @@ packages:
|
||||
name: preferences
|
||||
url: "https://pub.dartlang.org"
|
||||
source: hosted
|
||||
version: "5.1.0"
|
||||
version: "5.2.0"
|
||||
qr_utils:
|
||||
dependency: "direct main"
|
||||
description:
|
||||
|
15
pubspec.yaml
15
pubspec.yaml
@ -26,15 +26,12 @@ dependencies:
|
||||
http: ^0.12.0+2
|
||||
shared_preferences: ^0.5.3+1
|
||||
|
||||
flutter_advanced_networkimage: any
|
||||
|
||||
preferences: ^5.1.0
|
||||
|
||||
qr_utils: ^0.1.4
|
||||
|
||||
package_info: ^0.4.0+16
|
||||
|
||||
font_awesome_flutter: ^8.8.1
|
||||
flutter_advanced_networkimage: any # Pull image from network or cache
|
||||
preferences: ^5.1.0 # Persistent settings storage
|
||||
qr_utils: ^0.1.4 # Barcode / QR-code support
|
||||
package_info: ^0.4.0+16 # App information introspection
|
||||
font_awesome_flutter: ^8.8.1 # FontAwesome icon set
|
||||
flutter_speed_dial: ^1.2.5 # FAB menu elements
|
||||
|
||||
dev_dependencies:
|
||||
flutter_test:
|
||||
|
Loading…
x
Reference in New Issue
Block a user