basic attachments are live

This commit is contained in:
zoe 2022-09-25 20:07:47 +02:00
parent 8aa49dc2ea
commit d0235cac5d
3 changed files with 99 additions and 16 deletions

View File

@ -1,7 +1,10 @@
import 'dart:convert';
import 'package:http/http.dart'; import 'package:http/http.dart';
import 'package:loris/global.dart' as global;
import 'package:http/http.dart' as http;
class FileUpload { class FileUpload {
final MultipartFile data;
String description; String description;
final String path; final String path;
@ -10,18 +13,45 @@ class FileUpload {
Map<String, String> ids = {}; Map<String, String> ids = {};
FileUpload({ FileUpload({
required this.data,
required this.description, required this.description,
required this.path, required this.path,
this.ids = const {}, this.ids = const {},
}); });
static Future<FileUpload> fromPath(String path, String description) async { static Future<FileUpload> fromPath(String path, String description) async {
final data = await MultipartFile.fromPath("file", path);
return FileUpload( return FileUpload(
data: data,
description: description, description: description,
path: path, 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<MapEntry<int, String>> 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"]);
}
} }

View File

@ -11,17 +11,19 @@ class MakePostModel {
final Visibility visibility; final Visibility visibility;
final String? scheduledAt; final String? scheduledAt;
final String? inReplyToId; final String? inReplyToId;
List<String> mediaIds;
MakePostModel({ MakePostModel({
required this.identity, required this.identity,
required this.status, required this.status,
required this.spoilerText, required this.spoilerText,
required this.visibility, required this.visibility,
required this.mediaIds,
this.scheduledAt, this.scheduledAt,
this.inReplyToId, this.inReplyToId,
}); });
Future<int> sendPost() async { Future<http.Response> sendPost() async {
final headers = global.settings!.identities[identity]!.getAuthHeaders(); final headers = global.settings!.identities[identity]!.getAuthHeaders();
headers.addAll(global.defaultHeaders); headers.addAll(global.defaultHeaders);
@ -29,6 +31,7 @@ class MakePostModel {
"status": status, "status": status,
"sensitive": false, "sensitive": false,
"visibility": visibility.queryParam, "visibility": visibility.queryParam,
"media_ids": mediaIds,
}; };
if (inReplyToId != null) { if (inReplyToId != null) {
@ -55,6 +58,6 @@ class MakePostModel {
headers: headers, headers: headers,
body: jsonEncode(params), body: jsonEncode(params),
); );
return response.statusCode; return response;
} }
} }

View File

@ -1,3 +1,5 @@
import 'dart:convert';
import 'package:flutter/material.dart'; import 'package:flutter/material.dart';
import 'package:localization/localization.dart'; import 'package:localization/localization.dart';
import 'package:loris/business_logic/fileupload/fileupload.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:loris/global.dart' as global;
import 'package:file_picker/file_picker.dart'; import 'package:file_picker/file_picker.dart';
import 'package:mime/mime.dart'; import 'package:mime/mime.dart';
import 'package:universal_io/io.dart' as io;
class MakePost extends StatefulWidget { class MakePost extends StatefulWidget {
const MakePost({Key? key, this.inReplyTo}) : super(key: key); const MakePost({Key? key, this.inReplyTo}) : super(key: key);
@ -24,6 +27,8 @@ class _MakePostState extends State<MakePost> {
InstanceInformation? instanceInfo; InstanceInformation? instanceInfo;
String text = ""; String text = "";
String spoilerText = ""; String spoilerText = "";
String? status;
bool sending = false;
// tracks fileuploads and their ids on servers // tracks fileuploads and their ids on servers
// if the id is null the file has not been uploaded yet // if the id is null the file has not been uploaded yet
List<FileUpload> files = []; List<FileUpload> files = [];
@ -201,7 +206,7 @@ class _MakePostState extends State<MakePost> {
// then parse them // then parse them
for (var path in result.paths) { for (var path in result.paths) {
if (path != null && mounted) { if (path != null && mounted) {
print(path); if (await io.Directory(path).exists()) break;
final FileUpload f = await FileUpload.fromPath(path, ""); final FileUpload f = await FileUpload.fromPath(path, "");
setState(() { setState(() {
files.add(f); files.add(f);
@ -212,7 +217,7 @@ class _MakePostState extends State<MakePost> {
}, },
icon: const Icon(Icons.attachment), icon: const Icon(Icons.attachment),
label: Text( 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( DropdownButtonHideUnderline(
child: DropdownButton<String>( child: DropdownButton<String>(
alignment: Alignment.center, alignment: Alignment.center,
@ -234,7 +239,38 @@ class _MakePostState extends State<MakePost> {
tooManyAttachments()) tooManyAttachments())
? null ? null
: () async { : () async {
setState(() {
sending = true;
status = "sending...".i18n();
});
// upload the media attachments
List<String> 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( final model = MakePostModel(
mediaIds: mediaIds,
spoilerText: spoilerText.trim(), spoilerText: spoilerText.trim(),
identity: accountid, identity: accountid,
status: text, status: text,
@ -244,8 +280,15 @@ class _MakePostState extends State<MakePost> {
: identitiesAvailable[accountid]!, : identitiesAvailable[accountid]!,
); );
final result = await model.sendPost(); final result = await model.sendPost();
if (result == 200) { if (mounted) {
Navigator.of(context).pop(); 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), icon: const Icon(Icons.send),
@ -351,10 +394,18 @@ class _MakePostState extends State<MakePost> {
return SimpleDialog( return SimpleDialog(
alignment: Alignment.center, alignment: Alignment.center,
contentPadding: const EdgeInsets.all(24), contentPadding: const EdgeInsets.all(24),
title: SelectableText( title: Column(
textAlign: TextAlign.center, children: [
widget.inReplyTo == null ? "make-post".i18n() : "make-reply".i18n(), SelectableText(
style: Theme.of(context).textTheme.displayMedium), 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: [ children: [
SingleChildScrollView( SingleChildScrollView(
child: Container( child: Container(
@ -391,8 +442,7 @@ class FileUploadDisplay extends StatelessWidget {
children: [ children: [
const Icon(Icons.attachment, size: 32), const Icon(Icons.attachment, size: 32),
Expanded( Expanded(
child: SelectableText( child: SelectableText(file.path,
"${file.path} (${(file.data.length / 1024).toStringAsFixed(2)}kb)",
style: Theme.of(context).textTheme.displaySmall), style: Theme.of(context).textTheme.displaySmall),
), ),
], ],