mirror of
				https://github.com/inventree/inventree-app.git
				synced 2025-11-04 07:15:46 +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:
		@@ -22,8 +22,6 @@ import 'package:inventree/widget/snacks.dart';
 | 
			
		||||
 */
 | 
			
		||||
class APIFormField {
 | 
			
		||||
 | 
			
		||||
  final _controller = TextEditingController();
 | 
			
		||||
 | 
			
		||||
  // Constructor
 | 
			
		||||
  APIFormField(this.name, this.data);
 | 
			
		||||
 | 
			
		||||
@@ -47,6 +45,8 @@ class APIFormField {
 | 
			
		||||
  // Is this field read only?
 | 
			
		||||
  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)
 | 
			
		||||
  dynamic get value => (data['value'] ?? data['default']);
 | 
			
		||||
 | 
			
		||||
@@ -341,6 +341,8 @@ class APIFormField {
 | 
			
		||||
        helperStyle: _helperStyle(),
 | 
			
		||||
        hintText: placeholderText,
 | 
			
		||||
      ),
 | 
			
		||||
      maxLines: multiline ? null : 1,
 | 
			
		||||
      expands: false,
 | 
			
		||||
      initialValue: value ?? '',
 | 
			
		||||
      onSaved: (val) {
 | 
			
		||||
        data["value"] = val;
 | 
			
		||||
 
 | 
			
		||||
@@ -5,7 +5,7 @@ import 'dart:ui';
 | 
			
		||||
const Color COLOR_GRAY = Color.fromRGBO(50, 50, 50, 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);
 | 
			
		||||
 | 
			
		||||
 
 | 
			
		||||
@@ -1,4 +1,5 @@
 | 
			
		||||
import 'package:inventree/api.dart';
 | 
			
		||||
import 'package:inventree/app_colors.dart';
 | 
			
		||||
import 'package:inventree/settings/release.dart';
 | 
			
		||||
 | 
			
		||||
import 'package:flutter/cupertino.dart';
 | 
			
		||||
@@ -116,7 +117,7 @@ class InvenTreeAboutWidget extends StatelessWidget {
 | 
			
		||||
      ListTile(
 | 
			
		||||
        title: Text(L10().releaseNotes),
 | 
			
		||||
        subtitle: Text(L10().appReleaseNotes),
 | 
			
		||||
        leading: FaIcon(FontAwesomeIcons.fileAlt),
 | 
			
		||||
        leading: FaIcon(FontAwesomeIcons.fileAlt, color: COLOR_CLICK),
 | 
			
		||||
        onTap: () {
 | 
			
		||||
          _releaseNotes(context);
 | 
			
		||||
        },
 | 
			
		||||
@@ -127,7 +128,7 @@ class InvenTreeAboutWidget extends StatelessWidget {
 | 
			
		||||
      ListTile(
 | 
			
		||||
        title: Text(L10().credits),
 | 
			
		||||
        subtitle: Text(L10().appCredits),
 | 
			
		||||
        leading: FaIcon(FontAwesomeIcons.bullhorn),
 | 
			
		||||
        leading: FaIcon(FontAwesomeIcons.bullhorn, color: COLOR_CLICK),
 | 
			
		||||
        onTap: () {
 | 
			
		||||
          _credits(context);
 | 
			
		||||
        }
 | 
			
		||||
 
 | 
			
		||||
@@ -1,3 +1,4 @@
 | 
			
		||||
import 'package:inventree/app_colors.dart';
 | 
			
		||||
import 'package:inventree/inventree/sentry.dart';
 | 
			
		||||
import 'package:inventree/settings/about.dart';
 | 
			
		||||
import 'package:inventree/settings/app_settings.dart';
 | 
			
		||||
@@ -46,26 +47,26 @@ class _InvenTreeSettingsState extends State<InvenTreeSettingsWidget> {
 | 
			
		||||
              ListTile(
 | 
			
		||||
                  title: Text(L10().server),
 | 
			
		||||
                  subtitle: Text(L10().configureServer),
 | 
			
		||||
                  leading: FaIcon(FontAwesomeIcons.server),
 | 
			
		||||
                  leading: FaIcon(FontAwesomeIcons.server, color: COLOR_CLICK),
 | 
			
		||||
                  onTap: _editServerSettings,
 | 
			
		||||
              ),
 | 
			
		||||
              ListTile(
 | 
			
		||||
                leading: FaIcon(FontAwesomeIcons.cogs),
 | 
			
		||||
                title: Text(L10().appSettings),
 | 
			
		||||
                subtitle: Text(L10().appSettingsDetails),
 | 
			
		||||
                leading: FaIcon(FontAwesomeIcons.cogs, color: COLOR_CLICK),
 | 
			
		||||
                onTap: _editAppSettings,
 | 
			
		||||
              ),
 | 
			
		||||
              ListTile(
 | 
			
		||||
                title: Text(L10().about),
 | 
			
		||||
                subtitle: Text(L10().appDetails),
 | 
			
		||||
                leading: FaIcon(FontAwesomeIcons.infoCircle),
 | 
			
		||||
                leading: FaIcon(FontAwesomeIcons.infoCircle, color: COLOR_CLICK),
 | 
			
		||||
                onTap: _about,
 | 
			
		||||
              ),
 | 
			
		||||
 | 
			
		||||
              ListTile(
 | 
			
		||||
                title: Text(L10().documentation),
 | 
			
		||||
                subtitle: Text("https://inventree.readthedocs.io"),
 | 
			
		||||
                leading: FaIcon(FontAwesomeIcons.book),
 | 
			
		||||
                leading: FaIcon(FontAwesomeIcons.book, color: COLOR_CLICK),
 | 
			
		||||
                onTap: () {
 | 
			
		||||
                  _openDocs();
 | 
			
		||||
                },
 | 
			
		||||
@@ -74,7 +75,7 @@ class _InvenTreeSettingsState extends State<InvenTreeSettingsWidget> {
 | 
			
		||||
              ListTile(
 | 
			
		||||
                title: Text(L10().translate),
 | 
			
		||||
                subtitle: Text(L10().translateHelp),
 | 
			
		||||
                leading: FaIcon(FontAwesomeIcons.language),
 | 
			
		||||
                leading: FaIcon(FontAwesomeIcons.language, color: COLOR_CLICK),
 | 
			
		||||
                onTap: () {
 | 
			
		||||
                  _translate();
 | 
			
		||||
                }
 | 
			
		||||
@@ -83,7 +84,7 @@ class _InvenTreeSettingsState extends State<InvenTreeSettingsWidget> {
 | 
			
		||||
              ListTile(
 | 
			
		||||
                title: Text(L10().feedback),
 | 
			
		||||
                subtitle: Text(L10().submitFeedback),
 | 
			
		||||
                leading: FaIcon(FontAwesomeIcons.comments),
 | 
			
		||||
                leading: FaIcon(FontAwesomeIcons.comments, color: COLOR_CLICK),
 | 
			
		||||
                onTap: () {
 | 
			
		||||
                  _submitFeedback(context);
 | 
			
		||||
                },
 | 
			
		||||
 
 | 
			
		||||
@@ -8,10 +8,6 @@ import 'package:inventree/widget/progress.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/refreshable_state.dart';
 | 
			
		||||
import 'package:inventree/widget/paginator.dart';
 | 
			
		||||
 
 | 
			
		||||
@@ -8,9 +8,6 @@ import 'package:inventree/inventree/stock.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/snacks.dart';
 | 
			
		||||
import 'package:inventree/widget/stock_detail.dart';
 | 
			
		||||
import 'package:inventree/widget/paginator.dart';
 | 
			
		||||
import 'package:inventree/l10.dart';
 | 
			
		||||
@@ -37,8 +34,6 @@ class _LocationDisplayState extends RefreshableState<LocationDisplayWidget> {
 | 
			
		||||
 | 
			
		||||
  final InvenTreeStockLocation? location;
 | 
			
		||||
 | 
			
		||||
  final _editLocationKey = GlobalKey<FormState>();
 | 
			
		||||
 | 
			
		||||
  @override
 | 
			
		||||
  String getAppBarTitle(BuildContext context) { return L10().stockLocation; }
 | 
			
		||||
 | 
			
		||||
@@ -101,14 +96,6 @@ class _LocationDisplayState extends RefreshableState<LocationDisplayWidget> {
 | 
			
		||||
      modelData: _loc.jsondata,
 | 
			
		||||
      onSuccess: refresh
 | 
			
		||||
    );
 | 
			
		||||
 | 
			
		||||
    // Values which an be edited
 | 
			
		||||
    var _name;
 | 
			
		||||
    var _description;
 | 
			
		||||
 | 
			
		||||
    if (location == null) {
 | 
			
		||||
      return;
 | 
			
		||||
    }
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  _LocationDisplayState(this.location);
 | 
			
		||||
 
 | 
			
		||||
@@ -37,7 +37,6 @@ class PartDetailWidget extends StatefulWidget {
 | 
			
		||||
class _PartDisplayState extends RefreshableState<PartDetailWidget> {
 | 
			
		||||
 | 
			
		||||
  final _editImageKey = GlobalKey<FormState>();
 | 
			
		||||
  final _editPartKey = GlobalKey<FormState>();
 | 
			
		||||
 | 
			
		||||
  @override
 | 
			
		||||
  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.
 | 
			
		||||
   * Show a SnackBar with upload result.
 | 
			
		||||
@@ -411,8 +393,7 @@ class _PartDisplayState extends RefreshableState<PartDetailWidget> {
 | 
			
		||||
      );
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    // Notes field?
 | 
			
		||||
    if (part.notes.isNotEmpty) {
 | 
			
		||||
    // Notes field
 | 
			
		||||
    tiles.add(
 | 
			
		||||
      ListTile(
 | 
			
		||||
        title: Text(L10().notes),
 | 
			
		||||
@@ -426,7 +407,6 @@ class _PartDisplayState extends RefreshableState<PartDetailWidget> {
 | 
			
		||||
        },
 | 
			
		||||
      )
 | 
			
		||||
    );
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    return tiles;
 | 
			
		||||
 | 
			
		||||
 
 | 
			
		||||
@@ -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/widget/refreshable_state.dart';
 | 
			
		||||
import 'package:flutter/cupertino.dart';
 | 
			
		||||
import 'package:flutter_markdown/flutter_markdown.dart';
 | 
			
		||||
import 'package:inventree/l10.dart';
 | 
			
		||||
 | 
			
		||||
import '../api_form.dart';
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
class PartNotesWidget extends StatefulWidget {
 | 
			
		||||
 | 
			
		||||
@@ -22,9 +27,47 @@ class _PartNotesState extends RefreshableState<PartNotesWidget> {
 | 
			
		||||
 | 
			
		||||
  _PartNotesState(this.part);
 | 
			
		||||
 | 
			
		||||
  @override
 | 
			
		||||
  Future<void> request() async {
 | 
			
		||||
    await part.reload();
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  @override
 | 
			
		||||
  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
 | 
			
		||||
  Widget getBody(BuildContext context) {
 | 
			
		||||
    return Markdown(
 | 
			
		||||
 
 | 
			
		||||
@@ -47,7 +47,6 @@ class _StockItemDisplayState extends RefreshableState<StockDetailWidget> {
 | 
			
		||||
  final _removeStockKey = GlobalKey<FormState>();
 | 
			
		||||
  final _countStockKey = GlobalKey<FormState>();
 | 
			
		||||
  final _moveStockKey = GlobalKey<FormState>();
 | 
			
		||||
  final _editStockKey = GlobalKey<FormState>();
 | 
			
		||||
 | 
			
		||||
  _StockItemDisplayState(this.item);
 | 
			
		||||
 | 
			
		||||
@@ -291,9 +290,6 @@ class _StockItemDisplayState extends RefreshableState<StockDetailWidget> {
 | 
			
		||||
 | 
			
		||||
  void _transferStockDialog(BuildContext context) async {
 | 
			
		||||
 | 
			
		||||
    var locations = await InvenTreeStockLocation().list();
 | 
			
		||||
    final _selectedController = TextEditingController();
 | 
			
		||||
 | 
			
		||||
    int? location_pk;
 | 
			
		||||
 | 
			
		||||
    _quantityController.text = "${item.quantityString}";
 | 
			
		||||
@@ -563,7 +559,7 @@ class _StockItemDisplayState extends RefreshableState<StockDetailWidget> {
 | 
			
		||||
      );
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    if (item.notes.isNotEmpty) {
 | 
			
		||||
    // Notes field
 | 
			
		||||
    tiles.add(
 | 
			
		||||
      ListTile(
 | 
			
		||||
        title: Text(L10().notes),
 | 
			
		||||
@@ -578,7 +574,6 @@ class _StockItemDisplayState extends RefreshableState<StockDetailWidget> {
 | 
			
		||||
        }
 | 
			
		||||
      )
 | 
			
		||||
    );
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    return tiles;
 | 
			
		||||
  }
 | 
			
		||||
 
 | 
			
		||||
@@ -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/widget/refreshable_state.dart';
 | 
			
		||||
import 'package:flutter/cupertino.dart';
 | 
			
		||||
import 'package:flutter_markdown/flutter_markdown.dart';
 | 
			
		||||
import 'package:inventree/l10.dart';
 | 
			
		||||
 | 
			
		||||
import '../api.dart';
 | 
			
		||||
import '../api_form.dart';
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
class StockNotesWidget extends StatefulWidget {
 | 
			
		||||
 | 
			
		||||
@@ -26,6 +31,43 @@ class _StockNotesState extends RefreshableState<StockNotesWidget> {
 | 
			
		||||
  @override
 | 
			
		||||
  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
 | 
			
		||||
  Widget getBody(BuildContext context) {
 | 
			
		||||
    return Markdown(
 | 
			
		||||
 
 | 
			
		||||
		Reference in New Issue
	
	Block a user