diff --git a/android/app/src/main/AndroidManifest.xml b/android/app/src/main/AndroidManifest.xml
index 20c7e6c3..c6d9f2ce 100644
--- a/android/app/src/main/AndroidManifest.xml
+++ b/android/app/src/main/AndroidManifest.xml
@@ -52,6 +52,7 @@
-
+
+
\ No newline at end of file
diff --git a/lib/api.dart b/lib/api.dart
index 62d49168..e79d181f 100644
--- a/lib/api.dart
+++ b/lib/api.dart
@@ -2,9 +2,11 @@ import 'dart:async';
import 'dart:convert';
import 'dart:io';
+import 'package:flutter/foundation.dart';
import 'package:http/http.dart' as http;
import 'package:intl/intl.dart';
+import 'package:open_file/open_file.dart';
import 'package:flutter/cupertino.dart';
import 'package:cached_network_image/cached_network_image.dart';
import 'package:flutter/material.dart';
@@ -16,6 +18,7 @@ import 'package:inventree/l10.dart';
import 'package:inventree/inventree/sentry.dart';
import 'package:inventree/user_profile.dart';
import 'package:inventree/widget/snacks.dart';
+import 'package:path_provider/path_provider.dart';
/*
@@ -119,6 +122,8 @@ class InvenTreeAPI {
}
String _makeUrl(String url) {
+
+ // Strip leading slash
if (url.startsWith('/')) {
url = url.substring(1, url.length);
}
@@ -469,6 +474,92 @@ class InvenTreeAPI {
);
}
+ /*
+ * Download a file from the given URL
+ */
+ Future downloadFile(String url, {bool openOnDownload = true}) async {
+
+ showSnackIcon(
+ L10().downloading,
+ icon: FontAwesomeIcons.download,
+ success: true
+ );
+
+ // Find the local downlods directory
+ final Directory dir = await getTemporaryDirectory();
+
+ String filename = url.split("/").last;
+
+ String local_path = dir.path + "/" + filename;
+
+ Uri? _uri = Uri.tryParse(makeUrl(url));
+
+ if (_uri == null) {
+ showServerError(L10().invalidHost, L10().invalidHostDetails);
+ return;
+ }
+
+ if (_uri.host.isEmpty) {
+ showServerError(L10().invalidHost, L10().invalidHostDetails);
+ return;
+ }
+
+ HttpClientRequest? _request;
+
+ var client = createClient(true);
+
+ // Attempt to open a connection to the server
+ try {
+ _request = await client.openUrl("GET", _uri).timeout(Duration(seconds: 10));
+
+ // Set headers
+ _request.headers.set(HttpHeaders.authorizationHeader, _authorizationHeader());
+ _request.headers.set(HttpHeaders.acceptHeader, 'application/json');
+ _request.headers.set(HttpHeaders.contentTypeHeader, 'application/json');
+ _request.headers.set(HttpHeaders.acceptLanguageHeader, Intl.getCurrentLocale());
+
+ } on SocketException catch (error) {
+ print("SocketException at ${url}: ${error.toString()}");
+ showServerError(L10().connectionRefused, error.toString());
+ return;
+ } on TimeoutException {
+ print("TimeoutException at ${url}");
+ showTimeoutError();
+ return;
+ } catch (error, stackTrace) {
+ print("Server error at ${url}: ${error.toString()}");
+ showServerError(L10().serverError, error.toString());
+ sentryReportError(error, stackTrace);
+ return;
+ }
+
+ try {
+ final response = await _request.close();
+
+ if (response.statusCode == 200) {
+ var bytes = await consolidateHttpClientResponseBytes(response);
+
+ File localFile = File(local_path);
+
+ await localFile.writeAsBytes(bytes);
+
+ if (openOnDownload) {
+ OpenFile.open(local_path);
+ }
+ } else {
+ showStatusCodeError(response.statusCode);
+ }
+ } on SocketException catch (error) {
+ showServerError(L10().connectionRefused, error.toString());
+ } on TimeoutException {
+ showTimeoutError();
+ } catch (error, stackTrace) {
+ print("Error downloading image:");
+ print(error.toString());
+ showServerError(L10().downloadError, error.toString());
+ }
+ }
+
/*
* Upload a file to the given URL
*/
diff --git a/lib/inventree/model.dart b/lib/inventree/model.dart
index 7dcfa3cf..8f9f3f5b 100644
--- a/lib/inventree/model.dart
+++ b/lib/inventree/model.dart
@@ -526,6 +526,12 @@ class InvenTreeAttachment extends InvenTreeModel {
}
}
+ Future downloadAttachment() async {
+
+ await InvenTreeAPI().downloadFile(attachment);
+
+ }
+
}
diff --git a/lib/l10n b/lib/l10n
index faec9712..e6b7dd53 160000
--- a/lib/l10n
+++ b/lib/l10n
@@ -1 +1 @@
-Subproject commit faec97124b652ca219d6c0899dd0234cf42e4fa7
+Subproject commit e6b7dd53bc43e084325c2bb414436b559d18931a
diff --git a/lib/widget/category_display.dart b/lib/widget/category_display.dart
index a86981e3..ad644978 100644
--- a/lib/widget/category_display.dart
+++ b/lib/widget/category_display.dart
@@ -510,8 +510,6 @@ class _PaginatedPartListState extends State {
void updateSearchTerm() {
- print("Search Term: '${_searchTerm}'");
-
_searchTerm = searchController.text;
_pagingController.refresh();
}
diff --git a/lib/widget/part_attachments_widget.dart b/lib/widget/part_attachments_widget.dart
index 5e16085e..26e5fe22 100644
--- a/lib/widget/part_attachments_widget.dart
+++ b/lib/widget/part_attachments_widget.dart
@@ -161,6 +161,9 @@ class _PartAttachmentDisplayState extends RefreshableState