mirror of
https://github.com/inventree/inventree-app.git
synced 2026-06-10 08:27:15 +00:00
User info (#826)
* Enhanced user information in "about" view * Add user info to nav drawer * Update release notes
This commit is contained in:
@@ -1,6 +1,7 @@
|
|||||||
## 0.24.2 - May 2026
|
## 0.24.2 - May 2026
|
||||||
---
|
---
|
||||||
|
|
||||||
|
- Display user information in the app drawer
|
||||||
- Support "creation_date" field for stock items
|
- Support "creation_date" field for stock items
|
||||||
- Moves notifications to the top of the screen
|
- Moves notifications to the top of the screen
|
||||||
- Updated translations
|
- Updated translations
|
||||||
|
|||||||
@@ -277,6 +277,9 @@ class InvenTreeAPI {
|
|||||||
Map<String, dynamic> userInfo = {};
|
Map<String, dynamic> userInfo = {};
|
||||||
|
|
||||||
String get username => (userInfo["username"] ?? "") as String;
|
String get username => (userInfo["username"] ?? "") as String;
|
||||||
|
String get userEmail => (userInfo["email"] ?? "") as String;
|
||||||
|
String get userFirstName => (userInfo["first_name"] ?? "") as String;
|
||||||
|
String get userLastName => (userInfo["last_name"] ?? "") as String;
|
||||||
|
|
||||||
int get userId => (userInfo["pk"] ?? -1) as int;
|
int get userId => (userInfo["pk"] ?? -1) as int;
|
||||||
|
|
||||||
|
|||||||
@@ -1852,6 +1852,9 @@
|
|||||||
"usernameEmpty": "Username cannot be empty",
|
"usernameEmpty": "Username cannot be empty",
|
||||||
"@usernameEmpty": {},
|
"@usernameEmpty": {},
|
||||||
|
|
||||||
|
"userDetails": "User Details",
|
||||||
|
"@userDetails": {},
|
||||||
|
|
||||||
"value": "Value",
|
"value": "Value",
|
||||||
"@value": {
|
"@value": {
|
||||||
"description": "value"
|
"description": "value"
|
||||||
|
|||||||
+46
-13
@@ -93,19 +93,7 @@ class InvenTreeAboutWidget extends StatelessWidget {
|
|||||||
: L10().notConnected,
|
: L10().notConnected,
|
||||||
),
|
),
|
||||||
leading: Icon(TablerIcons.globe),
|
leading: Icon(TablerIcons.globe),
|
||||||
trailing: InvenTreeAPI().isConnected()
|
trailing: Icon(TablerIcons.circle_check, color: COLOR_SUCCESS),
|
||||||
? Icon(TablerIcons.circle_check, color: COLOR_SUCCESS)
|
|
||||||
: Icon(TablerIcons.circle_x, color: COLOR_DANGER),
|
|
||||||
),
|
|
||||||
);
|
|
||||||
|
|
||||||
tiles.add(
|
|
||||||
ListTile(
|
|
||||||
title: Text(L10().username),
|
|
||||||
subtitle: Text(InvenTreeAPI().username),
|
|
||||||
leading: InvenTreeAPI().username.isNotEmpty
|
|
||||||
? Icon(TablerIcons.user)
|
|
||||||
: Icon(TablerIcons.user_cancel, color: COLOR_DANGER),
|
|
||||||
),
|
),
|
||||||
);
|
);
|
||||||
|
|
||||||
@@ -141,6 +129,51 @@ class InvenTreeAboutWidget extends StatelessWidget {
|
|||||||
leading: Icon(TablerIcons.plug),
|
leading: Icon(TablerIcons.plug),
|
||||||
),
|
),
|
||||||
);
|
);
|
||||||
|
|
||||||
|
tiles.add(
|
||||||
|
ListTile(
|
||||||
|
title: Text(
|
||||||
|
L10().userDetails,
|
||||||
|
style: TextStyle(fontWeight: FontWeight.bold),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
);
|
||||||
|
|
||||||
|
tiles.add(
|
||||||
|
ListTile(
|
||||||
|
title: Text(L10().username),
|
||||||
|
subtitle: Text(InvenTreeAPI().username),
|
||||||
|
leading: InvenTreeAPI().username.isNotEmpty
|
||||||
|
? Icon(TablerIcons.user)
|
||||||
|
: Icon(TablerIcons.user_cancel, color: COLOR_DANGER),
|
||||||
|
),
|
||||||
|
);
|
||||||
|
|
||||||
|
final String email = InvenTreeAPI().userEmail;
|
||||||
|
|
||||||
|
if (email.isNotEmpty) {
|
||||||
|
tiles.add(
|
||||||
|
ListTile(
|
||||||
|
title: Text(L10().email),
|
||||||
|
subtitle: Text(email),
|
||||||
|
leading: Icon(TablerIcons.at),
|
||||||
|
),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
final String firstName = InvenTreeAPI().userFirstName;
|
||||||
|
final String lastName = InvenTreeAPI().userLastName;
|
||||||
|
final String fullName = "$firstName $lastName".trim();
|
||||||
|
|
||||||
|
if (fullName.isNotEmpty) {
|
||||||
|
tiles.add(
|
||||||
|
ListTile(
|
||||||
|
title: Text(L10().name),
|
||||||
|
subtitle: Text(fullName),
|
||||||
|
leading: Icon(TablerIcons.id_badge),
|
||||||
|
),
|
||||||
|
);
|
||||||
|
}
|
||||||
} else {
|
} else {
|
||||||
tiles.add(
|
tiles.add(
|
||||||
ListTile(
|
ListTile(
|
||||||
|
|||||||
+53
-38
@@ -1,6 +1,7 @@
|
|||||||
import "package:adaptive_theme/adaptive_theme.dart";
|
import "package:adaptive_theme/adaptive_theme.dart";
|
||||||
import "package:flutter/material.dart";
|
import "package:flutter/material.dart";
|
||||||
import "package:flutter_tabler_icons/flutter_tabler_icons.dart";
|
import "package:flutter_tabler_icons/flutter_tabler_icons.dart";
|
||||||
|
import "package:package_info_plus/package_info_plus.dart";
|
||||||
|
|
||||||
import "package:inventree/api.dart";
|
import "package:inventree/api.dart";
|
||||||
import "package:inventree/app_colors.dart";
|
import "package:inventree/app_colors.dart";
|
||||||
@@ -10,6 +11,7 @@ import "package:inventree/inventree/purchase_order.dart";
|
|||||||
import "package:inventree/inventree/sales_order.dart";
|
import "package:inventree/inventree/sales_order.dart";
|
||||||
import "package:inventree/inventree/stock.dart";
|
import "package:inventree/inventree/stock.dart";
|
||||||
import "package:inventree/l10.dart";
|
import "package:inventree/l10.dart";
|
||||||
|
import "package:inventree/settings/about.dart";
|
||||||
import "package:inventree/settings/settings.dart";
|
import "package:inventree/settings/settings.dart";
|
||||||
import "package:inventree/widget/build/build_list.dart";
|
import "package:inventree/widget/build/build_list.dart";
|
||||||
import "package:inventree/widget/order/sales_order_list.dart";
|
import "package:inventree/widget/order/sales_order_list.dart";
|
||||||
@@ -79,63 +81,56 @@ class ThemeSelectionDialog extends StatelessWidget {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
class InvenTreeDrawer extends StatelessWidget {
|
class InvenTreeDrawer extends StatefulWidget {
|
||||||
const InvenTreeDrawer(this.context);
|
const InvenTreeDrawer(this.parentContext);
|
||||||
|
|
||||||
final BuildContext context;
|
final BuildContext parentContext;
|
||||||
|
|
||||||
|
@override
|
||||||
|
State<InvenTreeDrawer> createState() => _InvenTreeDrawerState();
|
||||||
|
}
|
||||||
|
|
||||||
|
class _InvenTreeDrawerState extends State<InvenTreeDrawer> {
|
||||||
void _closeDrawer() {
|
void _closeDrawer() {
|
||||||
// Close the drawer
|
Navigator.of(widget.parentContext).pop();
|
||||||
Navigator.of(context).pop();
|
|
||||||
}
|
}
|
||||||
|
|
||||||
bool _checkConnection() {
|
bool _checkConnection() {
|
||||||
return InvenTreeAPI().checkConnection();
|
return InvenTreeAPI().checkConnection();
|
||||||
}
|
}
|
||||||
|
|
||||||
/*
|
|
||||||
* Return to the 'home' screen.
|
|
||||||
* This will empty the navigation stack.
|
|
||||||
*/
|
|
||||||
void _home() {
|
void _home() {
|
||||||
_closeDrawer();
|
_closeDrawer();
|
||||||
|
while (Navigator.of(widget.parentContext).canPop()) {
|
||||||
while (Navigator.of(context).canPop()) {
|
Navigator.of(widget.parentContext).pop();
|
||||||
Navigator.of(context).pop();
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// Load "parts" page
|
|
||||||
void _parts() {
|
void _parts() {
|
||||||
_closeDrawer();
|
_closeDrawer();
|
||||||
|
|
||||||
if (_checkConnection()) {
|
if (_checkConnection()) {
|
||||||
Navigator.push(
|
Navigator.push(
|
||||||
context,
|
widget.parentContext,
|
||||||
MaterialPageRoute(builder: (context) => CategoryDisplayWidget(null)),
|
MaterialPageRoute(builder: (context) => CategoryDisplayWidget(null)),
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// Load "stock" page
|
|
||||||
void _stock() {
|
void _stock() {
|
||||||
_closeDrawer();
|
_closeDrawer();
|
||||||
|
|
||||||
if (_checkConnection()) {
|
if (_checkConnection()) {
|
||||||
Navigator.push(
|
Navigator.push(
|
||||||
context,
|
widget.parentContext,
|
||||||
MaterialPageRoute(builder: (context) => LocationDisplayWidget(null)),
|
MaterialPageRoute(builder: (context) => LocationDisplayWidget(null)),
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// Load "sales orders" page
|
|
||||||
void _salesOrders() {
|
void _salesOrders() {
|
||||||
_closeDrawer();
|
_closeDrawer();
|
||||||
|
|
||||||
if (_checkConnection()) {
|
if (_checkConnection()) {
|
||||||
Navigator.push(
|
Navigator.push(
|
||||||
context,
|
widget.parentContext,
|
||||||
MaterialPageRoute(
|
MaterialPageRoute(
|
||||||
builder: (context) => SalesOrderListWidget(filters: {}),
|
builder: (context) => SalesOrderListWidget(filters: {}),
|
||||||
),
|
),
|
||||||
@@ -143,13 +138,11 @@ class InvenTreeDrawer extends StatelessWidget {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// Load "purchase orders" page
|
|
||||||
void _purchaseOrders() {
|
void _purchaseOrders() {
|
||||||
_closeDrawer();
|
_closeDrawer();
|
||||||
|
|
||||||
if (_checkConnection()) {
|
if (_checkConnection()) {
|
||||||
Navigator.push(
|
Navigator.push(
|
||||||
context,
|
widget.parentContext,
|
||||||
MaterialPageRoute(
|
MaterialPageRoute(
|
||||||
builder: (context) => PurchaseOrderListWidget(filters: {}),
|
builder: (context) => PurchaseOrderListWidget(filters: {}),
|
||||||
),
|
),
|
||||||
@@ -157,13 +150,11 @@ class InvenTreeDrawer extends StatelessWidget {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// Load "build orders" page
|
|
||||||
void _buildOrders() {
|
void _buildOrders() {
|
||||||
_closeDrawer();
|
_closeDrawer();
|
||||||
|
|
||||||
if (_checkConnection()) {
|
if (_checkConnection()) {
|
||||||
Navigator.push(
|
Navigator.push(
|
||||||
context,
|
widget.parentContext,
|
||||||
MaterialPageRoute(
|
MaterialPageRoute(
|
||||||
builder: (context) => BuildOrderListWidget(filters: {}),
|
builder: (context) => BuildOrderListWidget(filters: {}),
|
||||||
),
|
),
|
||||||
@@ -171,28 +162,34 @@ class InvenTreeDrawer extends StatelessWidget {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// Load notifications screen
|
|
||||||
void _notifications() {
|
void _notifications() {
|
||||||
_closeDrawer();
|
_closeDrawer();
|
||||||
|
|
||||||
if (_checkConnection()) {
|
if (_checkConnection()) {
|
||||||
Navigator.push(
|
Navigator.push(
|
||||||
context,
|
widget.parentContext,
|
||||||
MaterialPageRoute(builder: (context) => NotificationWidget()),
|
MaterialPageRoute(builder: (context) => NotificationWidget()),
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// Load settings widget
|
|
||||||
void _settings() {
|
void _settings() {
|
||||||
_closeDrawer();
|
_closeDrawer();
|
||||||
Navigator.push(
|
Navigator.push(
|
||||||
context,
|
widget.parentContext,
|
||||||
MaterialPageRoute(builder: (context) => InvenTreeSettingsWidget()),
|
MaterialPageRoute(builder: (context) => InvenTreeSettingsWidget()),
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
// Return an icon representing the current theme mode
|
void _about() {
|
||||||
|
_closeDrawer();
|
||||||
|
PackageInfo.fromPlatform().then((PackageInfo info) {
|
||||||
|
Navigator.push(
|
||||||
|
widget.parentContext,
|
||||||
|
MaterialPageRoute(builder: (context) => InvenTreeAboutWidget(info)),
|
||||||
|
);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
Widget _getThemeModeIcon(AdaptiveThemeMode mode) {
|
Widget _getThemeModeIcon(AdaptiveThemeMode mode) {
|
||||||
switch (mode) {
|
switch (mode) {
|
||||||
case AdaptiveThemeMode.dark:
|
case AdaptiveThemeMode.dark:
|
||||||
@@ -204,11 +201,23 @@ class InvenTreeDrawer extends StatelessWidget {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// Construct list of tiles to display in the "drawer" menu
|
Widget? _buildUserTile() {
|
||||||
|
if (!InvenTreeAPI().isConnected()) return null;
|
||||||
|
|
||||||
|
final String username = InvenTreeAPI().username;
|
||||||
|
final String email = InvenTreeAPI().userEmail;
|
||||||
|
|
||||||
|
return ListTile(
|
||||||
|
leading: Icon(TablerIcons.user_circle, color: COLOR_ACTION),
|
||||||
|
title: Text(username),
|
||||||
|
subtitle: email.isNotEmpty ? Text(email) : null,
|
||||||
|
onTap: _about,
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
List<Widget> drawerTiles(BuildContext context) {
|
List<Widget> drawerTiles(BuildContext context) {
|
||||||
List<Widget> tiles = [];
|
List<Widget> tiles = [];
|
||||||
|
|
||||||
// "Home" access
|
|
||||||
tiles.add(
|
tiles.add(
|
||||||
ListTile(
|
ListTile(
|
||||||
leading: Image.asset("assets/image/logo_transparent.png", height: 24),
|
leading: Image.asset("assets/image/logo_transparent.png", height: 24),
|
||||||
@@ -276,13 +285,13 @@ class InvenTreeDrawer extends StatelessWidget {
|
|||||||
tiles.add(Divider());
|
tiles.add(Divider());
|
||||||
}
|
}
|
||||||
|
|
||||||
int notification_count = InvenTreeAPI().notification_counter;
|
final int notificationCount = InvenTreeAPI().notification_counter;
|
||||||
|
|
||||||
tiles.add(
|
tiles.add(
|
||||||
ListTile(
|
ListTile(
|
||||||
leading: Icon(TablerIcons.bell, color: COLOR_ACTION),
|
leading: Icon(TablerIcons.bell, color: COLOR_ACTION),
|
||||||
trailing: notification_count > 0
|
trailing: notificationCount > 0
|
||||||
? Text(notification_count.toString())
|
? Text(notificationCount.toString())
|
||||||
: null,
|
: null,
|
||||||
title: Text(L10().notifications),
|
title: Text(L10().notifications),
|
||||||
onTap: _notifications,
|
onTap: _notifications,
|
||||||
@@ -321,6 +330,12 @@ class InvenTreeDrawer extends StatelessWidget {
|
|||||||
),
|
),
|
||||||
);
|
);
|
||||||
|
|
||||||
|
final Widget? userTile = _buildUserTile();
|
||||||
|
if (userTile != null) {
|
||||||
|
tiles.add(Divider());
|
||||||
|
tiles.add(userTile);
|
||||||
|
}
|
||||||
|
|
||||||
return tiles;
|
return tiles;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
Reference in New Issue
Block a user