basic attachments are live
This commit is contained in:
parent
8aa49dc2ea
commit
d0235cac5d
|
@ -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"]);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -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;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -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),
|
||||||
),
|
),
|
||||||
],
|
],
|
||||||
|
|
Loading…
Reference in New Issue