loris/lib/dialogues/makepost.dart

409 lines
12 KiB
Dart

import 'package:flutter/material.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/network_tools/get_post_from_url.dart';
import 'package:loris/business_logic/timeline/timeline.dart' as tl;
import '../business_logic/posting/posting.dart';
import '../partials/post.dart';
import 'package:loris/global.dart' as global;
import 'package:file_picker/file_picker.dart';
import 'package:mime/mime.dart';
class MakePost extends StatefulWidget {
const MakePost({Key? key, this.inReplyTo}) : super(key: key);
final tl.PostModel? inReplyTo;
@override
State<MakePost> createState() => _MakePostState();
}
class _MakePostState extends State<MakePost> {
String replyAts = "";
String accountid = global.settings!.activeIdentity;
InstanceInformation? instanceInfo;
String text = "";
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
Map<String, String?> identitiesAvailable = {};
tl.Visibility visibility = tl.Visibility.public;
void switchAccount(String acct) {
setState(() {
instanceInfo = null;
accountid = acct;
});
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 {
final info = await instanceInformationForIdentity(accountid);
if (info.keys.first == 200 && mounted) {
setState(() {
instanceInfo = info.values.first!;
});
}
}
void updateAvailableIds() async {
if (widget.inReplyTo != null) {
global.settings!.identities.forEach((key, value) async {
final post = await getPostFromUrl(key, widget.inReplyTo!.uri);
if (mounted) {
if (post.keys.first == 200) {
setState(() {
identitiesAvailable.addAll({key: post.values.first!.id});
});
}
}
});
}
}
void addAt(String at) {
if (!at.contains("@")) {
at = "$at${widget.inReplyTo!.getReceiverInstance()}";
}
if (global.settings!.identities.keys.contains(at)) {
return;
}
replyAts = "$replyAts@$at ";
}
@override
void initState() {
if (widget.inReplyTo != null) {
visibility = widget.inReplyTo!.visibility;
addAt(widget.inReplyTo!.account.acct);
for (var element in widget.inReplyTo!.mentions) {
addAt(element.acct);
}
accountid = widget.inReplyTo!.identity;
identitiesAvailable.addAll({
widget.inReplyTo!.identity: widget.inReplyTo!.id,
});
} else {
global.settings!.identities.forEach((key, value) {
if (!identitiesAvailable.containsKey(DropdownMenuItem(
value: key,
child: Text(key),
))) {
identitiesAvailable.addAll({key: null});
}
});
}
if (widget.inReplyTo != null) {
spoilerText = widget.inReplyTo!.spoilerText;
}
super.initState();
updateMaxChars();
updateAvailableIds();
}
@override
Widget build(BuildContext context) {
// make list of all different widgets to be displayed
List<Widget> c = [];
if (widget.inReplyTo != null) {
c.add(
Post(
model: widget.inReplyTo!,
reblogVisible: false,
hideActionBar: true,
),
);
}
List<DropdownMenuItem<String>> idDropdownItems = [];
identitiesAvailable.forEach((key, value) {
idDropdownItems.add(DropdownMenuItem(
value: key,
alignment: Alignment.center,
child: Text(
key,
style: Theme.of(context).textTheme.bodyMedium,
textAlign: TextAlign.center,
),
));
});
List<DropdownMenuItem<tl.Visibility>> visibilityDropdowns = [];
for (var value in tl.Visibility.values) {
visibilityDropdowns.add(DropdownMenuItem(
value: value,
child: Row(
mainAxisAlignment: MainAxisAlignment.center,
children: [
Icon(value.icon),
Text(
value.name,
style: Theme.of(context).textTheme.bodyMedium,
),
],
)));
}
List<Widget> actionButtons = [
DropdownButtonHideUnderline(
child: DropdownButton<tl.Visibility>(
alignment: Alignment.center,
iconEnabledColor: Theme.of(context).colorScheme.onSurface,
items: visibilityDropdowns,
value: visibility,
onChanged: (value) {
setState(() {
visibility = value ?? tl.Visibility.private;
});
},
),
),
// 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(
child: DropdownButton<String>(
alignment: Alignment.center,
borderRadius: BorderRadius.circular(8),
iconEnabledColor: Theme.of(context).colorScheme.onSurface,
items: idDropdownItems,
value: accountid,
onChanged: (value) {
switchAccount(value as String);
},
),
),
ElevatedButton.icon(
label: Text(
"send-post".i18n(),
),
// send the post!!!
onPressed: ((spoilerText.isEmpty && text.isEmpty && files.isEmpty) ||
tooManyAttachments())
? null
: () async {
final model = MakePostModel(
spoilerText: spoilerText.trim(),
identity: accountid,
status: text,
visibility: visibility,
inReplyToId: widget.inReplyTo == null
? null
: identitiesAvailable[accountid]!,
);
final result = await model.sendPost();
if (result == 200) {
Navigator.of(context).pop();
}
},
icon: const Icon(Icons.send),
),
];
c.addAll([
TextFormField(
style: Theme.of(context).textTheme.bodyMedium,
initialValue: spoilerText,
decoration: InputDecoration(
counterText: instanceInfo == null
? "loading...".i18n()
: (instanceInfo!.configuration.statusconfig.maxChars -
text.length -
spoilerText.length)
.toString(),
prefixIcon: Icon(
Icons.warning,
color: Theme.of(context).colorScheme.onSurface,
),
labelText: "content-warning".i18n(),
),
onChanged: ((value) => setState(() {
spoilerText = value;
})),
),
SizedBox(
height: MediaQuery.of(context).size.height * 0.6,
child: TextFormField(
initialValue: replyAts,
style: Theme.of(context).textTheme.bodyMedium,
maxLines: null,
expands: true,
onChanged: (value) {
setState(() {
text = value;
});
},
),
),
const SizedBox(
height: 24,
),
]);
// these are the action buttons
c.add(
Wrap(
runSpacing: 8,
spacing: 24,
direction: Axis.horizontal,
crossAxisAlignment: WrapCrossAlignment.center,
alignment: WrapAlignment.spaceAround,
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(
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),
children: [
SingleChildScrollView(
child: Container(
width: (MediaQuery.of(context).size.width *
global.settings!.postWidth) -
56,
constraints: global.getConstraints(context),
child: Column(
mainAxisSize: MainAxisSize.min,
crossAxisAlignment: CrossAxisAlignment.stretch,
children: c,
),
),
),
],
);
}
}
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,
),
],
);
}
}