2
0
mirror of https://github.com/inventree/inventree-app.git synced 2025-04-28 13:36:50 +00:00

Enable multi-line text editing for API forms

- User can edit part notes
- User can edit stock item notes
This commit is contained in:
Oliver 2021-07-28 16:19:42 +10:00
parent b8379e05db
commit d6a2a41ab2
10 changed files with 129 additions and 82 deletions

View File

@ -22,8 +22,6 @@ import 'package:inventree/widget/snacks.dart';
*/ */
class APIFormField { class APIFormField {
final _controller = TextEditingController();
// Constructor // Constructor
APIFormField(this.name, this.data); APIFormField(this.name, this.data);
@ -47,6 +45,8 @@ class APIFormField {
// Is this field read only? // Is this field read only?
bool get readOnly => (data['read_only'] ?? false) as bool; bool get readOnly => (data['read_only'] ?? false) as bool;
bool get multiline => (data['multiline'] ?? false) as bool;
// Get the "value" as a string (look for "default" if not available) // Get the "value" as a string (look for "default" if not available)
dynamic get value => (data['value'] ?? data['default']); dynamic get value => (data['value'] ?? data['default']);
@ -341,6 +341,8 @@ class APIFormField {
helperStyle: _helperStyle(), helperStyle: _helperStyle(),
hintText: placeholderText, hintText: placeholderText,
), ),
maxLines: multiline ? null : 1,
expands: false,
initialValue: value ?? '', initialValue: value ?? '',
onSaved: (val) { onSaved: (val) {
data["value"] = val; data["value"] = val;

View File

@ -5,7 +5,7 @@ import 'dart:ui';
const Color COLOR_GRAY = Color.fromRGBO(50, 50, 50, 1); const Color COLOR_GRAY = Color.fromRGBO(50, 50, 50, 1);
const Color COLOR_GRAY_LIGHT = Color.fromRGBO(150, 150, 150, 1); const Color COLOR_GRAY_LIGHT = Color.fromRGBO(150, 150, 150, 1);
const Color COLOR_CLICK = Color.fromRGBO(175, 150, 100, 0.9); const Color COLOR_CLICK = Color.fromRGBO(150, 120, 100, 0.9);
const Color COLOR_BLUE = Color.fromRGBO(0, 0, 250, 1); const Color COLOR_BLUE = Color.fromRGBO(0, 0, 250, 1);

View File

@ -1,4 +1,5 @@
import 'package:inventree/api.dart'; import 'package:inventree/api.dart';
import 'package:inventree/app_colors.dart';
import 'package:inventree/settings/release.dart'; import 'package:inventree/settings/release.dart';
import 'package:flutter/cupertino.dart'; import 'package:flutter/cupertino.dart';
@ -116,7 +117,7 @@ class InvenTreeAboutWidget extends StatelessWidget {
ListTile( ListTile(
title: Text(L10().releaseNotes), title: Text(L10().releaseNotes),
subtitle: Text(L10().appReleaseNotes), subtitle: Text(L10().appReleaseNotes),
leading: FaIcon(FontAwesomeIcons.fileAlt), leading: FaIcon(FontAwesomeIcons.fileAlt, color: COLOR_CLICK),
onTap: () { onTap: () {
_releaseNotes(context); _releaseNotes(context);
}, },
@ -127,7 +128,7 @@ class InvenTreeAboutWidget extends StatelessWidget {
ListTile( ListTile(
title: Text(L10().credits), title: Text(L10().credits),
subtitle: Text(L10().appCredits), subtitle: Text(L10().appCredits),
leading: FaIcon(FontAwesomeIcons.bullhorn), leading: FaIcon(FontAwesomeIcons.bullhorn, color: COLOR_CLICK),
onTap: () { onTap: () {
_credits(context); _credits(context);
} }

View File

@ -1,3 +1,4 @@
import 'package:inventree/app_colors.dart';
import 'package:inventree/inventree/sentry.dart'; import 'package:inventree/inventree/sentry.dart';
import 'package:inventree/settings/about.dart'; import 'package:inventree/settings/about.dart';
import 'package:inventree/settings/app_settings.dart'; import 'package:inventree/settings/app_settings.dart';
@ -46,26 +47,26 @@ class _InvenTreeSettingsState extends State<InvenTreeSettingsWidget> {
ListTile( ListTile(
title: Text(L10().server), title: Text(L10().server),
subtitle: Text(L10().configureServer), subtitle: Text(L10().configureServer),
leading: FaIcon(FontAwesomeIcons.server), leading: FaIcon(FontAwesomeIcons.server, color: COLOR_CLICK),
onTap: _editServerSettings, onTap: _editServerSettings,
), ),
ListTile( ListTile(
leading: FaIcon(FontAwesomeIcons.cogs),
title: Text(L10().appSettings), title: Text(L10().appSettings),
subtitle: Text(L10().appSettingsDetails), subtitle: Text(L10().appSettingsDetails),
leading: FaIcon(FontAwesomeIcons.cogs, color: COLOR_CLICK),
onTap: _editAppSettings, onTap: _editAppSettings,
), ),
ListTile( ListTile(
title: Text(L10().about), title: Text(L10().about),
subtitle: Text(L10().appDetails), subtitle: Text(L10().appDetails),
leading: FaIcon(FontAwesomeIcons.infoCircle), leading: FaIcon(FontAwesomeIcons.infoCircle, color: COLOR_CLICK),
onTap: _about, onTap: _about,
), ),
ListTile( ListTile(
title: Text(L10().documentation), title: Text(L10().documentation),
subtitle: Text("https://inventree.readthedocs.io"), subtitle: Text("https://inventree.readthedocs.io"),
leading: FaIcon(FontAwesomeIcons.book), leading: FaIcon(FontAwesomeIcons.book, color: COLOR_CLICK),
onTap: () { onTap: () {
_openDocs(); _openDocs();
}, },
@ -74,7 +75,7 @@ class _InvenTreeSettingsState extends State<InvenTreeSettingsWidget> {
ListTile( ListTile(
title: Text(L10().translate), title: Text(L10().translate),
subtitle: Text(L10().translateHelp), subtitle: Text(L10().translateHelp),
leading: FaIcon(FontAwesomeIcons.language), leading: FaIcon(FontAwesomeIcons.language, color: COLOR_CLICK),
onTap: () { onTap: () {
_translate(); _translate();
} }
@ -83,7 +84,7 @@ class _InvenTreeSettingsState extends State<InvenTreeSettingsWidget> {
ListTile( ListTile(
title: Text(L10().feedback), title: Text(L10().feedback),
subtitle: Text(L10().submitFeedback), subtitle: Text(L10().submitFeedback),
leading: FaIcon(FontAwesomeIcons.comments), leading: FaIcon(FontAwesomeIcons.comments, color: COLOR_CLICK),
onTap: () { onTap: () {
_submitFeedback(context); _submitFeedback(context);
}, },

View File

@ -8,10 +8,6 @@ import 'package:inventree/widget/progress.dart';
import 'package:inventree/l10.dart'; import 'package:inventree/l10.dart';
import 'package:inventree/widget/fields.dart';
import 'package:inventree/widget/dialogs.dart';
import 'package:inventree/widget/snacks.dart';
import 'package:inventree/widget/part_detail.dart'; import 'package:inventree/widget/part_detail.dart';
import 'package:inventree/widget/refreshable_state.dart'; import 'package:inventree/widget/refreshable_state.dart';
import 'package:inventree/widget/paginator.dart'; import 'package:inventree/widget/paginator.dart';

View File

@ -8,9 +8,6 @@ import 'package:inventree/inventree/stock.dart';
import 'package:inventree/widget/progress.dart'; import 'package:inventree/widget/progress.dart';
import 'package:inventree/widget/refreshable_state.dart'; import 'package:inventree/widget/refreshable_state.dart';
import 'package:inventree/widget/fields.dart';
import 'package:inventree/widget/dialogs.dart';
import 'package:inventree/widget/snacks.dart';
import 'package:inventree/widget/stock_detail.dart'; import 'package:inventree/widget/stock_detail.dart';
import 'package:inventree/widget/paginator.dart'; import 'package:inventree/widget/paginator.dart';
import 'package:inventree/l10.dart'; import 'package:inventree/l10.dart';
@ -37,8 +34,6 @@ class _LocationDisplayState extends RefreshableState<LocationDisplayWidget> {
final InvenTreeStockLocation? location; final InvenTreeStockLocation? location;
final _editLocationKey = GlobalKey<FormState>();
@override @override
String getAppBarTitle(BuildContext context) { return L10().stockLocation; } String getAppBarTitle(BuildContext context) { return L10().stockLocation; }
@ -101,14 +96,6 @@ class _LocationDisplayState extends RefreshableState<LocationDisplayWidget> {
modelData: _loc.jsondata, modelData: _loc.jsondata,
onSuccess: refresh onSuccess: refresh
); );
// Values which an be edited
var _name;
var _description;
if (location == null) {
return;
}
} }
_LocationDisplayState(this.location); _LocationDisplayState(this.location);

View File

@ -37,7 +37,6 @@ class PartDetailWidget extends StatefulWidget {
class _PartDisplayState extends RefreshableState<PartDetailWidget> { class _PartDisplayState extends RefreshableState<PartDetailWidget> {
final _editImageKey = GlobalKey<FormState>(); final _editImageKey = GlobalKey<FormState>();
final _editPartKey = GlobalKey<FormState>();
@override @override
String getAppBarTitle(BuildContext context) => L10().partDetails; String getAppBarTitle(BuildContext context) => L10().partDetails;
@ -104,23 +103,6 @@ class _PartDisplayState extends RefreshableState<PartDetailWidget> {
} }
} }
void _savePart(Map<String, String> values) async {
final bool result = await part.update(values: values);
if (result) {
showSnackIcon(L10().partEdited, success: true);
}
/*
showSnackIcon(
result ? "Part edited" : "Part editing failed",
success: result
);
*/
refresh();
}
/** /**
* Upload image for this Part. * Upload image for this Part.
* Show a SnackBar with upload result. * Show a SnackBar with upload result.
@ -411,8 +393,7 @@ class _PartDisplayState extends RefreshableState<PartDetailWidget> {
); );
} }
// Notes field? // Notes field
if (part.notes.isNotEmpty) {
tiles.add( tiles.add(
ListTile( ListTile(
title: Text(L10().notes), title: Text(L10().notes),
@ -426,7 +407,6 @@ class _PartDisplayState extends RefreshableState<PartDetailWidget> {
}, },
) )
); );
}
return tiles; return tiles;

View File

@ -1,9 +1,14 @@
import 'package:flutter/material.dart';
import 'package:font_awesome_flutter/font_awesome_flutter.dart';
import 'package:inventree/api.dart';
import 'package:inventree/inventree/part.dart'; import 'package:inventree/inventree/part.dart';
import 'package:inventree/widget/refreshable_state.dart'; import 'package:inventree/widget/refreshable_state.dart';
import 'package:flutter/cupertino.dart'; import 'package:flutter/cupertino.dart';
import 'package:flutter_markdown/flutter_markdown.dart'; import 'package:flutter_markdown/flutter_markdown.dart';
import 'package:inventree/l10.dart'; import 'package:inventree/l10.dart';
import '../api_form.dart';
class PartNotesWidget extends StatefulWidget { class PartNotesWidget extends StatefulWidget {
@ -22,9 +27,47 @@ class _PartNotesState extends RefreshableState<PartNotesWidget> {
_PartNotesState(this.part); _PartNotesState(this.part);
@override
Future<void> request() async {
await part.reload();
}
@override @override
String getAppBarTitle(BuildContext context) => L10().partNotes; String getAppBarTitle(BuildContext context) => L10().partNotes;
@override
List<Widget> getAppBarActions(BuildContext context) {
List<Widget> actions = [];
if (InvenTreeAPI().checkPermission('part', 'change')) {
actions.add(
IconButton(
icon: FaIcon(FontAwesomeIcons.edit),
tooltip: L10().edit,
onPressed: () {
launchApiForm(
context,
L10().editNotes,
part.url,
{
"notes": {
"multiline": true,
}
},
modelData: part.jsondata,
onSuccess: () async {
refresh();
}
);
}
)
);
}
return actions;
}
@override @override
Widget getBody(BuildContext context) { Widget getBody(BuildContext context) {
return Markdown( return Markdown(

View File

@ -47,7 +47,6 @@ class _StockItemDisplayState extends RefreshableState<StockDetailWidget> {
final _removeStockKey = GlobalKey<FormState>(); final _removeStockKey = GlobalKey<FormState>();
final _countStockKey = GlobalKey<FormState>(); final _countStockKey = GlobalKey<FormState>();
final _moveStockKey = GlobalKey<FormState>(); final _moveStockKey = GlobalKey<FormState>();
final _editStockKey = GlobalKey<FormState>();
_StockItemDisplayState(this.item); _StockItemDisplayState(this.item);
@ -291,9 +290,6 @@ class _StockItemDisplayState extends RefreshableState<StockDetailWidget> {
void _transferStockDialog(BuildContext context) async { void _transferStockDialog(BuildContext context) async {
var locations = await InvenTreeStockLocation().list();
final _selectedController = TextEditingController();
int? location_pk; int? location_pk;
_quantityController.text = "${item.quantityString}"; _quantityController.text = "${item.quantityString}";
@ -563,7 +559,7 @@ class _StockItemDisplayState extends RefreshableState<StockDetailWidget> {
); );
} }
if (item.notes.isNotEmpty) { // Notes field
tiles.add( tiles.add(
ListTile( ListTile(
title: Text(L10().notes), title: Text(L10().notes),
@ -578,7 +574,6 @@ class _StockItemDisplayState extends RefreshableState<StockDetailWidget> {
} }
) )
); );
}
return tiles; return tiles;
} }

View File

@ -1,10 +1,15 @@
import 'package:flutter/material.dart';
import 'package:font_awesome_flutter/font_awesome_flutter.dart';
import 'package:inventree/inventree/stock.dart'; import 'package:inventree/inventree/stock.dart';
import 'package:inventree/widget/refreshable_state.dart'; import 'package:inventree/widget/refreshable_state.dart';
import 'package:flutter/cupertino.dart'; import 'package:flutter/cupertino.dart';
import 'package:flutter_markdown/flutter_markdown.dart'; import 'package:flutter_markdown/flutter_markdown.dart';
import 'package:inventree/l10.dart'; import 'package:inventree/l10.dart';
import '../api.dart';
import '../api_form.dart';
class StockNotesWidget extends StatefulWidget { class StockNotesWidget extends StatefulWidget {
@ -26,6 +31,43 @@ class _StockNotesState extends RefreshableState<StockNotesWidget> {
@override @override
String getAppBarTitle(BuildContext context) => L10().stockItemNotes; String getAppBarTitle(BuildContext context) => L10().stockItemNotes;
@override
Future<void> request() async {
await item.reload();
}
@override
List<Widget> getAppBarActions(BuildContext context) {
List<Widget> actions = [];
if (InvenTreeAPI().checkPermission('stock', 'change')) {
actions.add(
IconButton(
icon: FaIcon(FontAwesomeIcons.edit),
tooltip: L10().edit,
onPressed: () {
launchApiForm(
context,
L10().editNotes,
item.url,
{
"notes": {
"multiline": true,
}
},
modelData: item.jsondata,
onSuccess: () {
refresh();
}
);
}
)
);
}
return actions;
}
@override @override
Widget getBody(BuildContext context) { Widget getBody(BuildContext context) {
return Markdown( return Markdown(