media attachments

This commit is contained in:
zoe 2022-09-25 18:21:26 +02:00
parent 4ac218b065
commit 8aa49dc2ea
4 changed files with 205 additions and 33 deletions

View File

@ -29,11 +29,13 @@ class InstanceConfiguration {
class StatusConfiguration { class StatusConfiguration {
final int maxChars; final int maxChars;
final int maxMediaAttachments;
StatusConfiguration(this.maxChars); StatusConfiguration(this.maxChars, this.maxMediaAttachments);
static StatusConfiguration fromJson(Map<String, dynamic> json) { static StatusConfiguration fromJson(Map<String, dynamic> json) {
return StatusConfiguration( return StatusConfiguration(
json["max_characters"], json["max_characters"],
json["max_media_attachments"],
); );
} }
} }

View File

@ -1,11 +1,14 @@
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/instance/instance.dart'; import 'package:loris/business_logic/instance/instance.dart';
import 'package:loris/business_logic/network_tools/get_post_from_url.dart'; import 'package:loris/business_logic/network_tools/get_post_from_url.dart';
import 'package:loris/business_logic/timeline/timeline.dart' as tl; import 'package:loris/business_logic/timeline/timeline.dart' as tl;
import '../business_logic/posting/posting.dart'; import '../business_logic/posting/posting.dart';
import '../partials/post.dart'; 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:mime/mime.dart';
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);
@ -18,26 +21,57 @@ class MakePost extends StatefulWidget {
class _MakePostState extends State<MakePost> { class _MakePostState extends State<MakePost> {
String replyAts = ""; String replyAts = "";
String accountid = global.settings!.activeIdentity; String accountid = global.settings!.activeIdentity;
int? maxLength; InstanceInformation? instanceInfo;
String text = ""; String text = "";
String spoilerText = ""; String spoilerText = "";
// tracks fileuploads and their ids on servers
// if the id is null the file has not been uploaded yet
List<FileUpload> files = [];
// stores all identities and if available the post id you are replying to // stores all identities and if available the post id you are replying to
Map<String, String?> identitiesAvailable = {}; Map<String, String?> identitiesAvailable = {};
tl.Visibility visibility = tl.Visibility.public; tl.Visibility visibility = tl.Visibility.public;
void switchAccount(String acct) { void switchAccount(String acct) {
setState(() { setState(() {
maxLength = null; instanceInfo = null;
accountid = acct; accountid = acct;
}); });
updateMaxChars(); updateMaxChars();
} }
bool tooManyAttachments() {
if (instanceInfo == null) {
return true;
}
if (instanceInfo!.configuration.statusconfig.maxMediaAttachments <
files.length) {
return true;
}
return false;
}
// moves file in list up or down,
// has to be called with file from items list
void moveFile(FileUpload file, {up = true}) {
if (!mounted) {
return;
}
final index = files.indexOf(file);
final newIndex = (index + (up ? -1 : 1)) % files.length;
print(newIndex);
files.remove(file);
setState(() {
files.insert(newIndex, file);
});
}
void updateMaxChars() async { void updateMaxChars() async {
final info = await instanceInformationForIdentity(accountid); final info = await instanceInformationForIdentity(accountid);
if (info.keys.first == 200) { if (info.keys.first == 200 && mounted) {
setState(() { setState(() {
maxLength = info.values.first!.configuration.statusconfig.maxChars; instanceInfo = info.values.first!;
}); });
} }
} }
@ -100,6 +134,7 @@ class _MakePostState extends State<MakePost> {
@override @override
Widget build(BuildContext context) { Widget build(BuildContext context) {
// make list of all different widgets to be displayed
List<Widget> c = []; List<Widget> c = [];
if (widget.inReplyTo != null) { if (widget.inReplyTo != null) {
c.add( c.add(
@ -140,9 +175,6 @@ class _MakePostState extends State<MakePost> {
))); )));
} }
List<Widget> actionButtons = [ List<Widget> actionButtons = [
maxLength == null
? const CircularProgressIndicator()
: SelectableText((maxLength! - text.length).toString()),
DropdownButtonHideUnderline( DropdownButtonHideUnderline(
child: DropdownButton<tl.Visibility>( child: DropdownButton<tl.Visibility>(
alignment: Alignment.center, alignment: Alignment.center,
@ -156,6 +188,31 @@ class _MakePostState extends State<MakePost> {
}, },
), ),
), ),
// media attachment button
TextButton.icon(
onPressed: () async {
// open filepicker
FilePickerResult? result = await FilePicker.platform.pickFiles(
withData: false,
allowMultiple: true,
);
if (result != null) {
// if there are any files
// then parse them
for (var path in result.paths) {
if (path != null && mounted) {
print(path);
final FileUpload f = await FileUpload.fromPath(path, "");
setState(() {
files.add(f);
});
}
}
}
},
icon: const Icon(Icons.attachment),
label: Text(
"${"add-file".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,
@ -173,19 +230,24 @@ class _MakePostState extends State<MakePost> {
"send-post".i18n(), "send-post".i18n(),
), ),
// send the post!!! // send the post!!!
onPressed: () async { onPressed: ((spoilerText.isEmpty && text.isEmpty && files.isEmpty) ||
final model = MakePostModel( tooManyAttachments())
spoilerText: spoilerText.trim(), ? null
identity: accountid, : () async {
status: text, final model = MakePostModel(
visibility: visibility, spoilerText: spoilerText.trim(),
inReplyToId: widget.inReplyTo == null identity: accountid,
? null status: text,
: identitiesAvailable[accountid]!, visibility: visibility,
); inReplyToId: widget.inReplyTo == null
model.sendPost(); ? null
Navigator.of(context).pop(); : identitiesAvailable[accountid]!,
}, );
final result = await model.sendPost();
if (result == 200) {
Navigator.of(context).pop();
}
},
icon: const Icon(Icons.send), icon: const Icon(Icons.send),
), ),
]; ];
@ -194,6 +256,12 @@ class _MakePostState extends State<MakePost> {
style: Theme.of(context).textTheme.bodyMedium, style: Theme.of(context).textTheme.bodyMedium,
initialValue: spoilerText, initialValue: spoilerText,
decoration: InputDecoration( decoration: InputDecoration(
counterText: instanceInfo == null
? "loading...".i18n()
: (instanceInfo!.configuration.statusconfig.maxChars -
text.length -
spoilerText.length)
.toString(),
prefixIcon: Icon( prefixIcon: Icon(
Icons.warning, Icons.warning,
color: Theme.of(context).colorScheme.onSurface, color: Theme.of(context).colorScheme.onSurface,
@ -221,6 +289,10 @@ class _MakePostState extends State<MakePost> {
const SizedBox( const SizedBox(
height: 24, height: 24,
), ),
]);
// these are the action buttons
c.add(
Wrap( Wrap(
runSpacing: 8, runSpacing: 8,
spacing: 24, spacing: 24,
@ -229,7 +301,52 @@ class _MakePostState extends State<MakePost> {
alignment: WrapAlignment.spaceAround, alignment: WrapAlignment.spaceAround,
children: actionButtons, children: actionButtons,
), ),
]); );
// display media attachments
for (var file in files) {
c.add(Column(
children: [
FileUploadDisplay(file: file),
TextField(
onChanged: (value) => file.description = value,
style: Theme.of(context).textTheme.bodyMedium,
minLines: 1,
maxLines: null,
decoration: InputDecoration(
hintText: "file-description".i18n(),
),
),
Wrap(
children: [
TextButton.icon(
onPressed: () {
moveFile(file, up: false);
},
icon: const Icon(Icons.move_down),
label: Text("move-down".i18n()),
),
TextButton.icon(
onPressed: () => setState(() {
files.remove(file);
}),
icon: const Icon(Icons.delete),
label: Text(
"remove-attached-file".i18n(),
),
),
TextButton.icon(
onPressed: () {
moveFile(file);
},
icon: const Icon(Icons.move_up),
label: Text("move-up".i18n()),
),
],
),
],
));
}
return SimpleDialog( return SimpleDialog(
alignment: Alignment.center, alignment: Alignment.center,
@ -244,10 +361,7 @@ class _MakePostState extends State<MakePost> {
width: (MediaQuery.of(context).size.width * width: (MediaQuery.of(context).size.width *
global.settings!.postWidth) - global.settings!.postWidth) -
56, 56,
constraints: BoxConstraints( constraints: global.getConstraints(context),
maxWidth: global.settings!.maxPostWidth,
minWidth: 375,
),
child: Column( child: Column(
mainAxisSize: MainAxisSize.min, mainAxisSize: MainAxisSize.min,
crossAxisAlignment: CrossAxisAlignment.stretch, crossAxisAlignment: CrossAxisAlignment.stretch,
@ -259,3 +373,36 @@ class _MakePostState extends State<MakePost> {
); );
} }
} }
class FileUploadDisplay extends StatelessWidget {
const FileUploadDisplay({super.key, required this.file});
final FileUpload file;
@override
Widget build(BuildContext context) {
return Column(
crossAxisAlignment: CrossAxisAlignment.stretch,
children: [
Divider(
color: Theme.of(context).hintColor,
),
Row(
crossAxisAlignment: CrossAxisAlignment.center,
children: [
const Icon(Icons.attachment, size: 32),
Expanded(
child: SelectableText(
"${file.path} (${(file.data.length / 1024).toStringAsFixed(2)}kb)",
style: Theme.of(context).textTheme.displaySmall),
),
],
),
if (lookupMimeType(file.path)!.startsWith("image/"))
Image.asset(
file.path,
fit: BoxFit.fitWidth,
),
],
);
}
}

View File

@ -99,6 +99,13 @@ packages:
url: "https://pub.dartlang.org" url: "https://pub.dartlang.org"
source: hosted source: hosted
version: "6.1.4" version: "6.1.4"
file_picker:
dependency: "direct main"
description:
name: file_picker
url: "https://pub.dartlang.org"
source: hosted
version: "5.2.0"
flutter: flutter:
dependency: "direct main" dependency: "direct main"
description: flutter description: flutter
@ -122,7 +129,14 @@ packages:
name: flutter_markdown name: flutter_markdown
url: "https://pub.dartlang.org" url: "https://pub.dartlang.org"
source: hosted source: hosted
version: "0.6.10+5" version: "0.6.10+6"
flutter_plugin_android_lifecycle:
dependency: transitive
description:
name: flutter_plugin_android_lifecycle
url: "https://pub.dartlang.org"
source: hosted
version: "2.0.7"
flutter_test: flutter_test:
dependency: "direct dev" dependency: "direct dev"
description: flutter description: flutter
@ -217,6 +231,13 @@ packages:
url: "https://pub.dartlang.org" url: "https://pub.dartlang.org"
source: hosted source: hosted
version: "1.8.0" version: "1.8.0"
mime:
dependency: "direct main"
description:
name: mime
url: "https://pub.dartlang.org"
source: hosted
version: "1.0.2"
path: path:
dependency: transitive dependency: transitive
description: description:
@ -244,7 +265,7 @@ packages:
name: path_provider_windows name: path_provider_windows
url: "https://pub.dartlang.org" url: "https://pub.dartlang.org"
source: hosted source: hosted
version: "2.1.2" version: "2.1.3"
platform: platform:
dependency: transitive dependency: transitive
description: description:
@ -258,7 +279,7 @@ packages:
name: plugin_platform_interface name: plugin_platform_interface
url: "https://pub.dartlang.org" url: "https://pub.dartlang.org"
source: hosted source: hosted
version: "2.1.2" version: "2.1.3"
process: process:
dependency: transitive dependency: transitive
description: description:
@ -279,7 +300,7 @@ packages:
name: shared_preferences_android name: shared_preferences_android
url: "https://pub.dartlang.org" url: "https://pub.dartlang.org"
source: hosted source: hosted
version: "2.0.12" version: "2.0.13"
shared_preferences_ios: shared_preferences_ios:
dependency: transitive dependency: transitive
description: description:
@ -328,7 +349,7 @@ packages:
name: shelf name: shelf
url: "https://pub.dartlang.org" url: "https://pub.dartlang.org"
source: hosted source: hosted
version: "1.3.2" version: "1.4.0"
sky_engine: sky_engine:
dependency: transitive dependency: transitive
description: flutter description: flutter
@ -410,7 +431,7 @@ packages:
name: url_launcher_android name: url_launcher_android
url: "https://pub.dartlang.org" url: "https://pub.dartlang.org"
source: hosted source: hosted
version: "6.0.17" version: "6.0.19"
url_launcher_ios: url_launcher_ios:
dependency: transitive dependency: transitive
description: description:
@ -480,7 +501,7 @@ packages:
name: win32 name: win32
url: "https://pub.dartlang.org" url: "https://pub.dartlang.org"
source: hosted source: hosted
version: "2.7.0" version: "3.0.0"
xdg_directories: xdg_directories:
dependency: transitive dependency: transitive
description: description:

View File

@ -51,6 +51,8 @@ dependencies:
url_strategy: ^0.2.0 url_strategy: ^0.2.0
universal_html: ^2.0.8 universal_html: ^2.0.8
universal_io: ^2.0.4 universal_io: ^2.0.4
file_picker: ^5.2.0
mime: ^1.0.2
dev_dependencies: dev_dependencies:
flutter_test: flutter_test: