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

Refactoring / fixing

This commit is contained in:
Oliver Walters 2021-02-09 19:40:31 +11:00
parent 7373805ea7
commit 4080b4177b
5 changed files with 220 additions and 170 deletions

View File

@ -177,6 +177,8 @@ class InvenTreeAPI {
* e.g. http://127.0.0.1:8000 * e.g. http://127.0.0.1:8000
*/ */
if (profile == null) return false;
String address = profile.server.trim(); String address = profile.server.trim();
String username = profile.username.trim(); String username = profile.username.trim();
String password = profile.password.trim(); String password = profile.password.trim();
@ -212,10 +214,13 @@ class InvenTreeAPI {
print("Error connecting to server: ${error.toString()}"); print("Error connecting to server: ${error.toString()}");
if (error is SocketException) { if (error is SocketException) {
showServerError(context, "Connection Refused"); print("Error: socket exception: ${error.toString()}");
// TODO - Display error dialog!!
//showServerError(context, "Connection Refused");
return null; return null;
} else if (error is TimeoutException) { } else if (error is TimeoutException) {
showTimeoutDialog(context); // TODO - Display timeout dialog here
//showTimeoutDialog(context);
return null; return null;
} else { } else {
// Unknown error type - re-throw the error and Sentry will catch it // Unknown error type - re-throw the error and Sentry will catch it
@ -303,18 +308,25 @@ class InvenTreeAPI {
}; };
} }
Future<bool> connectToServer(BuildContext context) async { bool disconnectFromServer() {
print("InvenTreeAPI().connectToServer()"); print("InvenTreeAPI().disconnectFromServer()");
// Clear connection flag
_connected = false; _connected = false;
_connecting = false;
// Clear token
_token = ''; _token = '';
profile = null;
}
Future<bool> connectToServer(BuildContext context) async {
// Ensure server is first disconnected
disconnectFromServer();
// Load selected profile // Load selected profile
profile = await UserProfileDBManager().getSelectedProfile(); profile = await UserProfileDBManager().getSelectedProfile();
print("API Profile: ${profile.toString()}");
if (profile == null) { if (profile == null) {
await showErrorDialog( await showErrorDialog(
context, context,
@ -326,13 +338,13 @@ class InvenTreeAPI {
_connecting = true; _connecting = true;
_connect(context).then((result) { bool result = await _connect(context);
print("_connect() returned result: ${result}"); print("_connect() returned result: ${result}");
_connecting = false;
return result; _connecting = false;
});
return result;
} }
// Perform a PATCH request // Perform a PATCH request

View File

@ -1,5 +1,7 @@
import 'package:InvenTree/widget/dialogs.dart'; import 'package:InvenTree/widget/dialogs.dart';
import 'package:InvenTree/widget/fields.dart'; import 'package:InvenTree/widget/fields.dart';
import 'package:InvenTree/widget/spinner.dart';
import 'package:flutter/material.dart'; import 'package:flutter/material.dart';
import 'package:flutter_gen/gen_l10n/app_localizations.dart'; import 'package:flutter_gen/gen_l10n/app_localizations.dart';
import 'package:font_awesome_flutter/font_awesome_flutter.dart'; import 'package:font_awesome_flutter/font_awesome_flutter.dart';
@ -166,21 +168,20 @@ class _InvenTreeLoginSettingsState extends State<InvenTreeLoginSettingsWidget> {
void _selectProfile(BuildContext context, UserProfile profile) async { void _selectProfile(BuildContext context, UserProfile profile) async {
// Mark currently selected profile as unselected // Disconnect InvenTree
final selected = await UserProfileDBManager().getSelectedProfile(); InvenTreeAPI().disconnectFromServer();
selected.selected = false; await UserProfileDBManager().selectProfile(profile.key);
await UserProfileDBManager().updateProfile(selected);
profile.selected = true;
await UserProfileDBManager().updateProfile(profile);
_reload(); _reload();
print("CONNECT FROM A");
// Attempt server login (this will load the newly selected profile // Attempt server login (this will load the newly selected profile
InvenTreeAPI().connectToServer(context); InvenTreeAPI().connectToServer(context).then((result) {
_reload();
});
_reload();
} }
void _deleteProfile(UserProfile profile) async { void _deleteProfile(UserProfile profile) async {
@ -191,6 +192,10 @@ class _InvenTreeLoginSettingsState extends State<InvenTreeLoginSettingsWidget> {
Navigator.of(context).pop(); Navigator.of(context).pop();
_reload(); _reload();
if (InvenTreeAPI().isConnected() && profile.key == InvenTreeAPI().profile.key) {
InvenTreeAPI().disconnectFromServer();
}
} }
void _updateProfile(UserProfile profile) async { void _updateProfile(UserProfile profile) async {
@ -201,6 +206,15 @@ class _InvenTreeLoginSettingsState extends State<InvenTreeLoginSettingsWidget> {
Navigator.of(context).pop(); Navigator.of(context).pop();
_reload(); _reload();
if (InvenTreeAPI().isConnected() && profile.key == InvenTreeAPI().profile.key) {
// Attempt server login (this will load the newly selected profile
print("Connect froM A");
InvenTreeAPI().connectToServer(context).then((result) {
_reload();
});
}
} }
void _addProfile(UserProfile profile) async { void _addProfile(UserProfile profile) async {
@ -213,11 +227,45 @@ class _InvenTreeLoginSettingsState extends State<InvenTreeLoginSettingsWidget> {
_reload(); _reload();
} }
Widget _getProfileIcon(UserProfile profile) {
// Not selected? No icon for you!
if (profile == null || !profile.selected) return null;
// Selected, but (for some reason) not the same as the API...
if (InvenTreeAPI().profile == null || InvenTreeAPI().profile.key != profile.key) {
return FaIcon(
FontAwesomeIcons.questionCircle,
color: Color.fromRGBO(250, 150, 50, 1)
);
}
// Reflect the connection status of the server
if (InvenTreeAPI().isConnected()) {
return FaIcon(
FontAwesomeIcons.checkCircle,
color: Color.fromRGBO(50, 250, 50, 1)
);
} else if (InvenTreeAPI().isConnecting()) {
return Spinner(
icon: FontAwesomeIcons.spinner,
color: Color.fromRGBO(50, 50, 250, 1),
);
} else {
return FaIcon(
FontAwesomeIcons.timesCircle,
color: Color.fromRGBO(250, 50, 50, 1),
);
}
}
@override @override
Widget build(BuildContext context) { Widget build(BuildContext context) {
final Size screenSize = MediaQuery.of(context).size; final Size screenSize = MediaQuery.of(context).size;
print("Building!");
List<Widget> children = []; List<Widget> children = [];
if (profiles != null && profiles.length > 0) { if (profiles != null && profiles.length > 0) {
@ -228,10 +276,12 @@ class _InvenTreeLoginSettingsState extends State<InvenTreeLoginSettingsWidget> {
title: Text( title: Text(
profile.name, profile.name,
), ),
subtitle: Text(profile.server), tileColor: profile.selected ? Color.fromRGBO(0, 0, 0, 0.05) : null,
trailing: profile.selected subtitle: Text("${profile.server}"),
? FaIcon(FontAwesomeIcons.checkCircle) trailing: _getProfileIcon(profile),
: null, onTap: () {
_selectProfile(context, profile);
},
onLongPress: () { onLongPress: () {
showDialog( showDialog(
context: context, context: context,
@ -239,50 +289,40 @@ class _InvenTreeLoginSettingsState extends State<InvenTreeLoginSettingsWidget> {
return SimpleDialog( return SimpleDialog(
title: Text(profile.name), title: Text(profile.name),
children: <Widget>[ children: <Widget>[
Divider(),
SimpleDialogOption( SimpleDialogOption(
onPressed: () { onPressed: () {
Navigator.of(context).pop(); Navigator.of(context).pop();
_selectProfile(context, profile); _selectProfile(context, profile);
}, },
child: Text(I18N child: Text(I18N.of(context).profileSelect),
.of(context)
.profileSelect),
), ),
SimpleDialogOption( SimpleDialogOption(
onPressed: () { onPressed: () {
Navigator.of(context).pop(); Navigator.of(context).pop();
_editProfile(context, userProfile: profile); _editProfile(context, userProfile: profile);
}, },
child: Text(I18N child: Text(I18N.of(context).profileEdit),
.of(context)
.profileEdit),
), ),
SimpleDialogOption( SimpleDialogOption(
onPressed: () { onPressed: () {
// Navigator.of(context, rootNavigator: true).pop(); // Navigator.of(context, rootNavigator: true).pop();
confirmationDialog( confirmationDialog(
context, context,
I18N I18N.of(context).delete,
.of(context)
.delete,
"Delete this profile?", "Delete this profile?",
onAccept: () { onAccept: () {
_deleteProfile(profile); _deleteProfile(profile);
} }
); );
}, },
child: Text(I18N child: Text(I18N.of(context).profileDelete),
.of(context)
.profileDelete),
) )
], ],
); );
} }
); );
}, },
onTap: () {
},
)); ));
} }
} else { } else {

View File

@ -36,13 +36,13 @@ class UserProfile {
// User ID (will be provided by the server on log-in) // User ID (will be provided by the server on log-in)
int user_id; int user_id;
factory UserProfile.fromJson(int key, Map<String, dynamic> json) => UserProfile( factory UserProfile.fromJson(int key, Map<String, dynamic> json, bool isSelected) => UserProfile(
key: key, key: key,
name: json['name'], name: json['name'],
server: json['server'], server: json['server'],
username: json['username'], username: json['username'],
password: json['password'], password: json['password'],
selected: json['selected'] ?? false, selected: isSelected,
); );
Map<String, dynamic> toJson() => { Map<String, dynamic> toJson() => {
@ -50,33 +50,40 @@ class UserProfile {
"server": server, "server": server,
"username": username, "username": username,
"password": password, "password": password,
"selected": selected,
}; };
@override @override
String toString() { String toString() {
return "${server} - ${username}:${password}"; return "<${key}> ${name} : ${server} - ${username}:${password}";
} }
} }
class UserProfileDBManager { class UserProfileDBManager {
static const String folder_name = "profiles"; final store = StoreRef("profiles");
final _folder = intMapStoreFactory.store(folder_name);
Future<Database> get _db async => await InvenTreePreferencesDB.instance.database; Future<Database> get _db async => await InvenTreePreferencesDB.instance.database;
Future<bool> profileNameExists(String name) async {
final finder = Finder(filter: Filter.equals("name", name));
final profiles = await store.find(await _db, finder: finder);
return profiles.length > 0;
}
Future addProfile(UserProfile profile) async { Future addProfile(UserProfile profile) async {
UserProfile existingProfile = await getProfile(profile.name); // Check if a profile already exists with the name
final bool exists = await profileNameExists(profile.name);
if (existingProfile != null) { if (exists) {
print("UserProfile '${profile.name}' already exists"); print("UserProfile '${profile.name}' already exists");
return; return;
} }
int key = await _folder.add(await _db, profile.toJson()); int key = await store.add(await _db, profile.toJson());
print("Added user profile <${key}> - '${profile.name}'"); print("Added user profile <${key}> - '${profile.name}'");
@ -84,23 +91,34 @@ class UserProfileDBManager {
profile.key = key; profile.key = key;
} }
Future selectProfile(int key) async {
/*
* Mark the particular profile as selected
*/
final result = await store.record("selected").put(await _db, key);
return result;
}
Future updateProfile(UserProfile profile) async { Future updateProfile(UserProfile profile) async {
if (profile.key == null) { if (profile.key == null) {
addProfile(profile); await addProfile(profile);
return; return;
} }
final finder = Finder(filter: Filter.byKey(profile.key)); final result = await store.record(profile.key).update(await _db, profile.toJson());
await _folder.update(await _db, profile.toJson(), finder: finder);
print("Updated user profile <${profile.key}> - '${profile.name}"); print("Updated user profile <${profile.key}> - '${profile.name}'");
return result;
} }
Future deleteProfile(UserProfile profile) async { Future deleteProfile(UserProfile profile) async {
final finder = Finder(filter: Filter.equals("name", profile.name)); final finder = Finder(filter: Filter.equals("name", profile.name));
await _folder.delete(await _db, finder: finder);
await store.record(profile.key).delete(await _db);
print("Deleted user profile <${profile.key}> - '${profile.name}'"); print("Deleted user profile <${profile.key}> - '${profile.name}'");
} }
@ -108,78 +126,50 @@ class UserProfileDBManager {
/* /*
* Return the currently selected profile. * Return the currently selected profile.
* *
* If multiple profiles are selected, * key should match the "selected" property
* mark all but the first as unselected
*
* If no profile is currently selected,
* then force the first profile to be selected.
*/ */
final selected_finder = Finder(filter: Filter.equals("selected", true)); final selected = await store.record("selected").get(await _db);
final selected_profiles = await _folder.find(await _db, finder: selected_finder); final profiles = await store.find(await _db);
if (selected_profiles.length == 1) { List<UserProfile> profileList = new List<UserProfile>();
// A single profile is selected
return UserProfile.fromJson(selected_profiles[0].key, selected_profiles[0].value);
} else if (selected_profiles.length > 1) {
// Multiple selected profiles - de-select others
for (int idx = 1; idx < selected_profiles.length; idx++) {
UserProfile profile = UserProfile.fromJson(selected_profiles[idx].key, selected_profiles[idx].value);
profile.selected = false; for (int idx = 0; idx < profiles.length; idx++) {
updateProfile(profile);
}
// And return the first profile if (profiles[idx].key is int && profiles[idx].key == selected) {
return UserProfile.fromJson(selected_profiles[0].key, selected_profiles[0].value); return UserProfile.fromJson(
} else { profiles[idx].key,
// No profiles selected! profiles[idx].value,
profiles[idx].key == selected,
final all_profiles = await getAllProfiles(); );
if (all_profiles.length == 0) {
// No profiles available
return null;
} else {
UserProfile prf = all_profiles[0];
prf.selected = true;
updateProfile(prf);
// Return the selected profile
return prf;
} }
} }
}
Future<UserProfile> getProfile(String name) async { return null;
print("Looking for user profile '${name}'");
// Lookup profile by name (or return null if does not exist)
final finder = Finder(filter: Filter.equals("name", name));
final profiles = await _folder.find(await _db, finder: finder);
if (profiles.length == 0) {
print("No matching profiles found");
return null;
}
// Return the first matching profile object
return UserProfile.fromJson(profiles[0].key, profiles[0].value);
} }
/* /*
* Return all user profile objects * Return all user profile objects
*/ */
Future<List<UserProfile>> getAllProfiles() async { Future<List<UserProfile>> getAllProfiles() async {
final profiles = await _folder.find(await _db);
final selected = await store.record("selected").get(await _db);
final profiles = await store.find(await _db);
List<UserProfile> profileList = new List<UserProfile>(); List<UserProfile> profileList = new List<UserProfile>();
for (int idx = 0; idx < profiles.length; idx++) { for (int idx = 0; idx < profiles.length; idx++) {
profileList.add(UserProfile.fromJson(profiles[idx].key, profiles[idx].value));
if (profiles[idx].key is int) {
profileList.add(
UserProfile.fromJson(
profiles[idx].key,
profiles[idx].value,
profiles[idx].key == selected,
));
}
} }
return profileList; return profileList;

View File

@ -15,6 +15,7 @@ import 'package:InvenTree/widget/category_display.dart';
import 'package:InvenTree/widget/company_list.dart'; import 'package:InvenTree/widget/company_list.dart';
import 'package:InvenTree/widget/location_display.dart'; import 'package:InvenTree/widget/location_display.dart';
import 'package:InvenTree/widget/search.dart'; import 'package:InvenTree/widget/search.dart';
import 'package:InvenTree/widget/spinner.dart';
import 'package:InvenTree/widget/drawer.dart'; import 'package:InvenTree/widget/drawer.dart';
class InvenTreeHomePage extends StatefulWidget { class InvenTreeHomePage extends StatefulWidget {
@ -28,19 +29,10 @@ class _InvenTreeHomePageState extends State<InvenTreeHomePage> {
_InvenTreeHomePageState() : super() { _InvenTreeHomePageState() : super() {
// Initially load the profile and attempt server connection
_loadProfile(); _loadProfile();
} }
String _serverStatus = "Connecting to server";
String _serverMessage = "";
bool _serverConnection = false;
FaIcon _serverIcon = new FaIcon(FontAwesomeIcons.spinner);
Color _serverStatusColor = Color.fromARGB(255, 50, 50, 250);
// Selected user profile // Selected user profile
UserProfile _profile; UserProfile _profile;
@ -111,30 +103,20 @@ class _InvenTreeHomePageState extends State<InvenTreeHomePage> {
void _loadProfile() async { void _loadProfile() async {
final profile = await UserProfileDBManager().getSelectedProfile(); _profile = await UserProfileDBManager().getSelectedProfile();
// If a different profile is selected, re-connect // A valid profile was loaded!
if (_profile == null || (_profile.key != profile.key)) { if (_profile != null) {
if (!InvenTreeAPI().isConnected() && !InvenTreeAPI().isConnecting()) {
if (_context != null) {
print("Connecting Profile: ${profile.name} - ${profile.server}");
print("Connect from C");
InvenTreeAPI().connectToServer(_context).then((result) { InvenTreeAPI().connectToServer(_context).then((result) {
setState(() { setState(() {});
});
});
setState(() {
}); });
} }
} }
_profile = profile; setState(() {});
setState(() {
});
} }
ListTile _serverTile() { ListTile _serverTile() {
@ -162,10 +144,13 @@ class _InvenTreeHomePageState extends State<InvenTreeHomePage> {
title: Text("Connecting to server..."), title: Text("Connecting to server..."),
subtitle: Text("${InvenTreeAPI().baseUrl}"), subtitle: Text("${InvenTreeAPI().baseUrl}"),
leading: FaIcon(FontAwesomeIcons.server), leading: FaIcon(FontAwesomeIcons.server),
trailing: FaIcon( trailing: Spinner(
FontAwesomeIcons.spinner, icon: FontAwesomeIcons.spinner,
color: Color.fromRGBO(50, 50, 250, 1), color: Color.fromRGBO(50, 50, 250, 1),
) ),
onTap: () {
_selectProfile();
}
); );
} else if (InvenTreeAPI().isConnected()) { } else if (InvenTreeAPI().isConnected()) {
return ListTile( return ListTile(
@ -196,42 +181,11 @@ class _InvenTreeHomePageState extends State<InvenTreeHomePage> {
} }
} }
void onConnectSuccess(String msg) async {
final profile = await UserProfileDBManager().getSelectedProfile();
String address = profile?.server ?? 'unknown server address';
_serverConnection = true;
_serverMessage = msg;
_serverStatus = "Connected to ${address}";
_serverStatusColor = Color.fromARGB(255, 50, 250, 50);
_serverIcon = new FaIcon(FontAwesomeIcons.checkCircle, color: _serverStatusColor);
setState(() {});
}
void onConnectFailure(String msg) async {
final profile = await UserProfileDBManager().getSelectedProfile();
_serverConnection = false;
_serverMessage = msg;
_serverStatus = "Could not connect to ${profile?.server}";
_serverStatusColor = Color.fromARGB(255, 250, 50, 50);
_serverIcon = new FaIcon(FontAwesomeIcons.timesCircle, color: _serverStatusColor);
setState(() {});
}
@override @override
Widget build(BuildContext context) { Widget build(BuildContext context) {
_context = context; _context = context;
_loadProfile();
// This method is rerun every time setState is called, for instance as done // This method is rerun every time setState is called, for instance as done
// by the _incrementCounter method above. // by the _incrementCounter method above.
// //

54
lib/widget/spinner.dart Normal file
View File

@ -0,0 +1,54 @@
import 'package:flutter/material.dart';
import 'package:flutter/cupertino.dart';
import 'package:font_awesome_flutter/font_awesome_flutter.dart';
class Spinner extends StatefulWidget {
final IconData icon;
final Duration duration;
final Color color;
const Spinner({
this.color = const Color.fromRGBO(150, 150, 150, 1),
Key key,
@required this.icon,
this.duration = const Duration(milliseconds: 1800),
}) : super(key: key);
@override
_SpinnerState createState() => _SpinnerState();
}
class _SpinnerState extends State<Spinner> with SingleTickerProviderStateMixin {
AnimationController _controller;
Widget _child;
@override
void initState() {
_controller = AnimationController(
vsync: this,
duration: Duration(milliseconds: 2000),
)
..repeat();
_child = FaIcon(
widget.icon,
color: widget.color
);
super.initState();
}
@override
void dispose() {
_controller.dispose();
super.dispose();
}
@override
Widget build(BuildContext context) {
return RotationTransition(
turns: _controller,
child: _child,
);
}
}