diff --git a/lib/business_logic/fileupload/fileupload.dart b/lib/business_logic/fileupload/fileupload.dart index 60c4fc2..c464366 100644 --- a/lib/business_logic/fileupload/fileupload.dart +++ b/lib/business_logic/fileupload/fileupload.dart @@ -1,7 +1,10 @@ +import 'dart:convert'; + import 'package:http/http.dart'; +import 'package:loris/global.dart' as global; +import 'package:http/http.dart' as http; class FileUpload { - final MultipartFile data; String description; final String path; @@ -10,18 +13,45 @@ class FileUpload { Map ids = {}; FileUpload({ - required this.data, required this.description, required this.path, this.ids = const {}, }); static Future fromPath(String path, String description) async { - final data = await MultipartFile.fromPath("file", path); return FileUpload( - data: data, description: description, path: path, ); } + + /* + sends a media attachments and returns the http status code + if the status code is 200, the String is the media attachments id, + otherwise it's the error response + */ + Future> upload(String identityName) async { + final identity = global.settings!.identities[identityName]!; + final headers = { + ...global.defaultHeaders, + ...identity.getAuthHeaders(), + }; + final uri = + Uri(scheme: "https", host: identity.instanceUrl, path: "/api/v2/media"); + final request = MultipartRequest( + "POST", + uri, + ); + request.headers.addAll(headers); + request.files.add(await MultipartFile.fromPath( + "file", + path, + )); + request.fields.addAll({"description": description}); + final response = await http.Response.fromStream(await request.send()); + if (response.statusCode != 200 && response.statusCode != 202) { + return MapEntry(response.statusCode, jsonDecode(response.body)["error"]); + } + return MapEntry(response.statusCode, jsonDecode(response.body)["id"]); + } } diff --git a/lib/business_logic/posting/posting.dart b/lib/business_logic/posting/posting.dart index 51aa4fd..7106f83 100644 --- a/lib/business_logic/posting/posting.dart +++ b/lib/business_logic/posting/posting.dart @@ -11,17 +11,19 @@ class MakePostModel { final Visibility visibility; final String? scheduledAt; final String? inReplyToId; + List mediaIds; MakePostModel({ required this.identity, required this.status, required this.spoilerText, required this.visibility, + required this.mediaIds, this.scheduledAt, this.inReplyToId, }); - Future sendPost() async { + Future sendPost() async { final headers = global.settings!.identities[identity]!.getAuthHeaders(); headers.addAll(global.defaultHeaders); @@ -29,6 +31,7 @@ class MakePostModel { "status": status, "sensitive": false, "visibility": visibility.queryParam, + "media_ids": mediaIds, }; if (inReplyToId != null) { @@ -55,6 +58,6 @@ class MakePostModel { headers: headers, body: jsonEncode(params), ); - return response.statusCode; + return response; } } diff --git a/lib/dialogues/makepost.dart b/lib/dialogues/makepost.dart index fa3cee8..eda31e3 100644 --- a/lib/dialogues/makepost.dart +++ b/lib/dialogues/makepost.dart @@ -1,3 +1,5 @@ +import 'dart:convert'; + import 'package:flutter/material.dart'; import 'package:localization/localization.dart'; import 'package:loris/business_logic/fileupload/fileupload.dart'; @@ -9,6 +11,7 @@ import '../partials/post.dart'; import 'package:loris/global.dart' as global; import 'package:file_picker/file_picker.dart'; import 'package:mime/mime.dart'; +import 'package:universal_io/io.dart' as io; class MakePost extends StatefulWidget { const MakePost({Key? key, this.inReplyTo}) : super(key: key); @@ -24,6 +27,8 @@ class _MakePostState extends State { InstanceInformation? instanceInfo; String text = ""; String spoilerText = ""; + String? status; + bool sending = false; // tracks fileuploads and their ids on servers // if the id is null the file has not been uploaded yet List files = []; @@ -201,7 +206,7 @@ class _MakePostState extends State { // then parse them for (var path in result.paths) { if (path != null && mounted) { - print(path); + if (await io.Directory(path).exists()) break; final FileUpload f = await FileUpload.fromPath(path, ""); setState(() { files.add(f); @@ -212,7 +217,7 @@ class _MakePostState extends State { }, icon: const Icon(Icons.attachment), label: Text( - "${"add-file".i18n()}${instanceInfo == null ? "(${"loading...".i18n()})" : " (${instanceInfo!.configuration.statusconfig.maxMediaAttachments - files.length} ${"remaining".i18n()})"}")), + "${"add-files".i18n()}${instanceInfo == null ? "(${"loading...".i18n()})" : " (${instanceInfo!.configuration.statusconfig.maxMediaAttachments - files.length} ${"remaining".i18n()})"}")), DropdownButtonHideUnderline( child: DropdownButton( alignment: Alignment.center, @@ -234,7 +239,38 @@ class _MakePostState extends State { tooManyAttachments()) ? null : () async { + setState(() { + sending = true; + status = "sending...".i18n(); + }); + + // upload the media attachments + List mediaIds = []; + for (var file in files) { + if (mounted) { + setState(() { + status = + "${"sending-file".i18n()}${mediaIds.length + 1}/${files.length}"; + }); + } + final response = await file.upload(accountid); + if (response.key == 200 || response.key == 202) { + mediaIds.add(response.value); + break; + } + if (mounted) { + setState(() { + status = response.value; + sending = false; + return; + }); + } + } + print(mediaIds); + + // send the final post final model = MakePostModel( + mediaIds: mediaIds, spoilerText: spoilerText.trim(), identity: accountid, status: text, @@ -244,8 +280,15 @@ class _MakePostState extends State { : identitiesAvailable[accountid]!, ); final result = await model.sendPost(); - if (result == 200) { - Navigator.of(context).pop(); + if (mounted) { + if (result.statusCode == 200) { + Navigator.of(context).pop(); + } + setState(() { + sending = false; + status = + "${"failed-to-send".i18n()}: ${jsonDecode(result.body)["error"].toString()}"; + }); } }, icon: const Icon(Icons.send), @@ -351,10 +394,18 @@ class _MakePostState extends State { return SimpleDialog( alignment: Alignment.center, contentPadding: const EdgeInsets.all(24), - title: SelectableText( - textAlign: TextAlign.center, - widget.inReplyTo == null ? "make-post".i18n() : "make-reply".i18n(), - style: Theme.of(context).textTheme.displayMedium), + title: Column( + children: [ + SelectableText( + textAlign: TextAlign.center, + widget.inReplyTo == null + ? "make-post".i18n() + : "make-reply".i18n(), + style: Theme.of(context).textTheme.displayMedium), + if (status != null) SelectableText(status!), + if (sending) const LinearProgressIndicator(), + ], + ), children: [ SingleChildScrollView( child: Container( @@ -391,8 +442,7 @@ class FileUploadDisplay extends StatelessWidget { children: [ const Icon(Icons.attachment, size: 32), Expanded( - child: SelectableText( - "${file.path} (${(file.data.length / 1024).toStringAsFixed(2)}kb)", + child: SelectableText(file.path, style: Theme.of(context).textTheme.displaySmall), ), ],