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