kopia lustrzana https://gitlab.com/mysocialportal/relatica
Initial post editing capability
rodzic
41c712ee84
commit
d83c6a65a0
|
@ -134,11 +134,13 @@ class _InteractionsBarControlState extends State<InteractionsBarControl> {
|
||||||
}
|
}
|
||||||
|
|
||||||
Future<void> openAction(BuildContext context) async {
|
Future<void> openAction(BuildContext context) async {
|
||||||
|
const editStatus = 'Edit Post';
|
||||||
const goToPost = 'Go to Post';
|
const goToPost = 'Go to Post';
|
||||||
const copyUrl = 'Copy URL';
|
const copyUrl = 'Copy URL';
|
||||||
const openExternal = 'Open In Browser';
|
const openExternal = 'Open In Browser';
|
||||||
final options = [
|
final options = [
|
||||||
if (widget.showOpenControl && !widget.openRemote) goToPost,
|
if (widget.showOpenControl && !widget.openRemote) goToPost,
|
||||||
|
if (widget.isMine) editStatus,
|
||||||
openExternal,
|
openExternal,
|
||||||
copyUrl,
|
copyUrl,
|
||||||
'Cancel'
|
'Cancel'
|
||||||
|
@ -154,6 +156,13 @@ class _InteractionsBarControlState extends State<InteractionsBarControl> {
|
||||||
case goToPost:
|
case goToPost:
|
||||||
context.push('/post/view/${widget.entry.id}/${widget.entry.id}');
|
context.push('/post/view/${widget.entry.id}/${widget.entry.id}');
|
||||||
break;
|
break;
|
||||||
|
case editStatus:
|
||||||
|
if (widget.entry.parentId.isEmpty) {
|
||||||
|
context.push('/post/edit/${widget.entry.id}');
|
||||||
|
} else {
|
||||||
|
context.push('/comment/edit/${widget.entry.id}');
|
||||||
|
}
|
||||||
|
break;
|
||||||
case openExternal:
|
case openExternal:
|
||||||
await openUrlStringInSystembrowser(
|
await openUrlStringInSystembrowser(
|
||||||
context,
|
context,
|
||||||
|
|
|
@ -604,6 +604,9 @@ class StatusesClient extends FriendicaClient {
|
||||||
if (spoilerText.isNotEmpty) 'spoiler_text': spoilerText,
|
if (spoilerText.isNotEmpty) 'spoiler_text': spoilerText,
|
||||||
if (inReplyToId.isNotEmpty) 'in_reply_to_id': inReplyToId,
|
if (inReplyToId.isNotEmpty) 'in_reply_to_id': inReplyToId,
|
||||||
if (mediaIds.isNotEmpty) 'media_ids': mediaIds,
|
if (mediaIds.isNotEmpty) 'media_ids': mediaIds,
|
||||||
|
'friendica': {
|
||||||
|
'title': '',
|
||||||
|
},
|
||||||
};
|
};
|
||||||
final result = await _postUrl(url, body);
|
final result = await _postUrl(url, body);
|
||||||
if (result.isFailure) {
|
if (result.isFailure) {
|
||||||
|
@ -620,6 +623,37 @@ class StatusesClient extends FriendicaClient {
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
FutureResult<TimelineEntry, ExecError> editStatus({
|
||||||
|
required String id,
|
||||||
|
required String text,
|
||||||
|
String spoilerText = '',
|
||||||
|
List<String> mediaIds = const [],
|
||||||
|
}) async {
|
||||||
|
_logger.finest(() => 'Updating status $id');
|
||||||
|
final url = Uri.parse('https://$serverName/api/v1/statuses/$id');
|
||||||
|
final body = {
|
||||||
|
'status': text,
|
||||||
|
if (spoilerText.isNotEmpty) 'spoiler_text': spoilerText,
|
||||||
|
if (mediaIds.isNotEmpty) 'media_ids': mediaIds,
|
||||||
|
'friendica': {
|
||||||
|
'title': '',
|
||||||
|
},
|
||||||
|
};
|
||||||
|
final result = await _putUrl(url, body);
|
||||||
|
if (result.isFailure) {
|
||||||
|
return result.errorCast();
|
||||||
|
}
|
||||||
|
|
||||||
|
final responseText = result.value;
|
||||||
|
|
||||||
|
return runCatching<TimelineEntry>(() {
|
||||||
|
final json = jsonDecode(responseText);
|
||||||
|
return Result.ok(TimelineEntryMastodonExtensions.fromJson(json));
|
||||||
|
}).mapError((error) {
|
||||||
|
return ExecError(type: ErrorType.parsingError, message: error.toString());
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
FutureResult<TimelineEntry, ExecError> resharePost(String id) async {
|
FutureResult<TimelineEntry, ExecError> resharePost(String id) async {
|
||||||
_logger.finest(() => 'Reshare post $id');
|
_logger.finest(() => 'Reshare post $id');
|
||||||
final url = Uri.parse('https://$serverName/api/v1/statuses/$id/reblog');
|
final url = Uri.parse('https://$serverName/api/v1/statuses/$id/reblog');
|
||||||
|
@ -806,6 +840,31 @@ abstract class FriendicaClient {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
FutureResult<String, ExecError> _putUrl(
|
||||||
|
Uri url, Map<String, dynamic> body) async {
|
||||||
|
_logger.finer('PUT: $url \n Body: $body');
|
||||||
|
try {
|
||||||
|
final response = await http.put(
|
||||||
|
url,
|
||||||
|
headers: {
|
||||||
|
'Authorization': _profile.credentials.authHeaderValue,
|
||||||
|
'Content-Type': 'application/json; charset=UTF-8'
|
||||||
|
},
|
||||||
|
body: jsonEncode(body),
|
||||||
|
);
|
||||||
|
|
||||||
|
if (response.statusCode != 200) {
|
||||||
|
return Result.error(ExecError(
|
||||||
|
type: ErrorType.authentication,
|
||||||
|
message: '${response.statusCode}: ${response.reasonPhrase}'));
|
||||||
|
}
|
||||||
|
return Result.ok(utf8.decode(response.bodyBytes));
|
||||||
|
} catch (e) {
|
||||||
|
return Result.error(
|
||||||
|
ExecError(type: ErrorType.localError, message: e.toString()));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
FutureResult<String, ExecError> _deleteUrl(
|
FutureResult<String, ExecError> _deleteUrl(
|
||||||
Uri url, Map<String, dynamic> body) async {
|
Uri url, Map<String, dynamic> body) async {
|
||||||
_logger.finer('DELETE: $url');
|
_logger.finer('DELETE: $url');
|
||||||
|
|
|
@ -1,4 +1,5 @@
|
||||||
import 'package:path/path.dart' as p;
|
import 'package:path/path.dart' as p;
|
||||||
|
import 'package:relatica/models/image_entry.dart';
|
||||||
|
|
||||||
import '../globals.dart';
|
import '../globals.dart';
|
||||||
import 'attachment_media_type_enum.dart';
|
import 'attachment_media_type_enum.dart';
|
||||||
|
@ -7,6 +8,8 @@ class MediaAttachment {
|
||||||
static final _graphicsExtensions = ['jpg', 'png', 'gif', 'tif'];
|
static final _graphicsExtensions = ['jpg', 'png', 'gif', 'tif'];
|
||||||
static final _movieExtensions = ['avi', 'mp4', 'mpg', 'wmv'];
|
static final _movieExtensions = ['avi', 'mp4', 'mpg', 'wmv'];
|
||||||
|
|
||||||
|
final String id;
|
||||||
|
|
||||||
final Uri uri;
|
final Uri uri;
|
||||||
|
|
||||||
final int creationTimestamp;
|
final int creationTimestamp;
|
||||||
|
@ -24,7 +27,8 @@ class MediaAttachment {
|
||||||
final String description;
|
final String description;
|
||||||
|
|
||||||
MediaAttachment(
|
MediaAttachment(
|
||||||
{required this.uri,
|
{required this.id,
|
||||||
|
required this.uri,
|
||||||
required this.creationTimestamp,
|
required this.creationTimestamp,
|
||||||
required this.metadata,
|
required this.metadata,
|
||||||
required this.thumbnailUri,
|
required this.thumbnailUri,
|
||||||
|
@ -34,7 +38,8 @@ class MediaAttachment {
|
||||||
required this.description});
|
required this.description});
|
||||||
|
|
||||||
MediaAttachment.randomBuilt()
|
MediaAttachment.randomBuilt()
|
||||||
: uri = Uri.parse('http://localhost/${randomId()}'),
|
: id = randomId(),
|
||||||
|
uri = Uri.parse('http://localhost/${randomId()}'),
|
||||||
creationTimestamp = DateTime.now().millisecondsSinceEpoch,
|
creationTimestamp = DateTime.now().millisecondsSinceEpoch,
|
||||||
fullFileUri = Uri.parse(''),
|
fullFileUri = Uri.parse(''),
|
||||||
title = 'Random title ${randomId()}',
|
title = 'Random title ${randomId()}',
|
||||||
|
@ -44,7 +49,8 @@ class MediaAttachment {
|
||||||
metadata = {'value1': randomId(), 'value2': randomId()};
|
metadata = {'value1': randomId(), 'value2': randomId()};
|
||||||
|
|
||||||
MediaAttachment.blank()
|
MediaAttachment.blank()
|
||||||
: uri = Uri(),
|
: id = '',
|
||||||
|
uri = Uri(),
|
||||||
creationTimestamp = 0,
|
creationTimestamp = 0,
|
||||||
thumbnailUri = Uri(),
|
thumbnailUri = Uri(),
|
||||||
explicitType = AttachmentMediaType.unknown,
|
explicitType = AttachmentMediaType.unknown,
|
||||||
|
@ -55,6 +61,7 @@ class MediaAttachment {
|
||||||
|
|
||||||
factory MediaAttachment.fromMastodonJson(Map<String, dynamic> json) =>
|
factory MediaAttachment.fromMastodonJson(Map<String, dynamic> json) =>
|
||||||
MediaAttachment(
|
MediaAttachment(
|
||||||
|
id: json['id'] ?? '',
|
||||||
uri: Uri.parse(json['url'] ?? 'http://localhost'),
|
uri: Uri.parse(json['url'] ?? 'http://localhost'),
|
||||||
creationTimestamp: 0,
|
creationTimestamp: 0,
|
||||||
metadata: {},
|
metadata: {},
|
||||||
|
@ -66,7 +73,20 @@ class MediaAttachment {
|
||||||
|
|
||||||
@override
|
@override
|
||||||
String toString() {
|
String toString() {
|
||||||
return 'FriendicaMediaAttachment{uri: $uri, creationTimestamp: $creationTimestamp, type: $explicitType, metadata: $metadata, title: $title, description: $description}';
|
return 'FriendicaMediaAttachment{id: $id, uri: $uri, creationTimestamp: $creationTimestamp, type: $explicitType, metadata: $metadata, title: $title, description: $description}';
|
||||||
|
}
|
||||||
|
|
||||||
|
ImageEntry toImageEntry() {
|
||||||
|
return ImageEntry(
|
||||||
|
id: id,
|
||||||
|
album: '',
|
||||||
|
filename: '',
|
||||||
|
description: description,
|
||||||
|
thumbnailUrl: thumbnailUri.toString(),
|
||||||
|
created: DateTime.fromMillisecondsSinceEpoch(creationTimestamp),
|
||||||
|
height: 0,
|
||||||
|
width: 0,
|
||||||
|
scales: []);
|
||||||
}
|
}
|
||||||
|
|
||||||
Map<String, dynamic> toJson() => {
|
Map<String, dynamic> toJson() => {
|
||||||
|
|
|
@ -164,12 +164,16 @@ final appRouter = GoRouter(
|
||||||
routes: [
|
routes: [
|
||||||
GoRoute(
|
GoRoute(
|
||||||
path: 'new',
|
path: 'new',
|
||||||
builder: (context, state) => EditorScreen(),
|
builder: (context, state) => EditorScreen(
|
||||||
|
forEditing: false,
|
||||||
|
),
|
||||||
),
|
),
|
||||||
GoRoute(
|
GoRoute(
|
||||||
path: 'edit/:id',
|
path: 'edit/:id',
|
||||||
builder: (context, state) =>
|
builder: (context, state) => EditorScreen(
|
||||||
EditorScreen(id: state.params['id'] ?? 'Not Found'),
|
id: state.params['id'] ?? 'Not Found',
|
||||||
|
forEditing: true,
|
||||||
|
),
|
||||||
),
|
),
|
||||||
GoRoute(
|
GoRoute(
|
||||||
path: 'view/:id/:goto_id',
|
path: 'view/:id/:goto_id',
|
||||||
|
@ -193,12 +197,15 @@ final appRouter = GoRouter(
|
||||||
path: 'new',
|
path: 'new',
|
||||||
builder: (context, state) => EditorScreen(
|
builder: (context, state) => EditorScreen(
|
||||||
parentId: state.queryParams['parent_id'] ?? '',
|
parentId: state.queryParams['parent_id'] ?? '',
|
||||||
|
forEditing: false,
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
GoRoute(
|
GoRoute(
|
||||||
path: 'edit/:id',
|
path: 'edit/:id',
|
||||||
builder: (context, state) =>
|
builder: (context, state) => EditorScreen(
|
||||||
EditorScreen(id: state.params['id'] ?? 'Not Found'),
|
id: state.params['id'] ?? 'Not Found',
|
||||||
|
forEditing: true,
|
||||||
|
),
|
||||||
),
|
),
|
||||||
]),
|
]),
|
||||||
GoRoute(
|
GoRoute(
|
||||||
|
|
|
@ -13,6 +13,7 @@ import '../controls/entry_media_attachments/media_uploads_control.dart';
|
||||||
import '../controls/padding.dart';
|
import '../controls/padding.dart';
|
||||||
import '../controls/standard_appbar.dart';
|
import '../controls/standard_appbar.dart';
|
||||||
import '../controls/timeline/status_header_control.dart';
|
import '../controls/timeline/status_header_control.dart';
|
||||||
|
import '../globals.dart';
|
||||||
import '../models/image_entry.dart';
|
import '../models/image_entry.dart';
|
||||||
import '../models/media_attachment_uploads/new_entry_media_items.dart';
|
import '../models/media_attachment_uploads/new_entry_media_items.dart';
|
||||||
import '../models/timeline_entry.dart';
|
import '../models/timeline_entry.dart';
|
||||||
|
@ -23,8 +24,10 @@ import '../utils/snackbar_builder.dart';
|
||||||
class EditorScreen extends StatefulWidget {
|
class EditorScreen extends StatefulWidget {
|
||||||
final String id;
|
final String id;
|
||||||
final String parentId;
|
final String parentId;
|
||||||
|
final bool forEditing;
|
||||||
|
|
||||||
const EditorScreen({super.key, this.id = '', this.parentId = ''});
|
const EditorScreen(
|
||||||
|
{super.key, this.id = '', this.parentId = '', required this.forEditing});
|
||||||
|
|
||||||
@override
|
@override
|
||||||
State<EditorScreen> createState() => _EditorScreenState();
|
State<EditorScreen> createState() => _EditorScreenState();
|
||||||
|
@ -49,21 +52,53 @@ class _EditorScreenState extends State<EditorScreen> {
|
||||||
String get localEntryId =>
|
String get localEntryId =>
|
||||||
widget.id.isNotEmpty ? widget.id : localEntryTemporaryId;
|
widget.id.isNotEmpty ? widget.id : localEntryTemporaryId;
|
||||||
|
|
||||||
|
bool loaded = false;
|
||||||
|
|
||||||
@override
|
@override
|
||||||
void initState() {
|
void initState() {
|
||||||
if (!isComment) {
|
if (isComment) {
|
||||||
return;
|
final manager = context
|
||||||
|
.read<ActiveProfileSelector<TimelineManager>>()
|
||||||
|
.activeEntry
|
||||||
|
.value;
|
||||||
|
manager.getEntryById(widget.parentId).match(onSuccess: (entry) {
|
||||||
|
spoilerController.text = entry.spoilerText;
|
||||||
|
parentEntry = entry;
|
||||||
|
}, onError: (error) {
|
||||||
|
_logger.finest('Error trying to get parent entry: $error');
|
||||||
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
final manager = context
|
if (widget.forEditing) {
|
||||||
.read<ActiveProfileSelector<TimelineManager>>()
|
restoreStatusData();
|
||||||
|
} else {
|
||||||
|
loaded = true;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
void restoreStatusData() async {
|
||||||
|
_logger.fine('Attempting to load status for editing');
|
||||||
|
loaded = false;
|
||||||
|
final result = await getIt<ActiveProfileSelector<TimelineManager>>()
|
||||||
.activeEntry
|
.activeEntry
|
||||||
.value;
|
.andThenAsync((manager) async => await manager.getEntryById(widget.id));
|
||||||
manager.getEntryById(widget.parentId).match(onSuccess: (entry) {
|
result.match(onSuccess: (entry) {
|
||||||
|
_logger.fine('Loading status ${widget.id} information into fields');
|
||||||
|
contentController.text = entry.body;
|
||||||
spoilerController.text = entry.spoilerText;
|
spoilerController.text = entry.spoilerText;
|
||||||
parentEntry = entry;
|
existingMediaItems
|
||||||
|
.addAll(entry.mediaAttachments.map((e) => e.toImageEntry()));
|
||||||
|
setState(() {
|
||||||
|
loaded = true;
|
||||||
|
});
|
||||||
}, onError: (error) {
|
}, onError: (error) {
|
||||||
_logger.finest('Error trying to get parent entry: $error');
|
if (context.mounted) {
|
||||||
|
buildSnackbar(
|
||||||
|
context,
|
||||||
|
'Error getting post for editing: $error',
|
||||||
|
);
|
||||||
|
}
|
||||||
|
_logger.severe('Error getting post for editing: $error');
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -99,6 +134,38 @@ class _EditorScreenState extends State<EditorScreen> {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
Future<void> editStatus(BuildContext context, TimelineManager manager) async {
|
||||||
|
if (contentController.text.isEmpty) {
|
||||||
|
buildSnackbar(context, "Can't submit an empty post/comment");
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
setState(() {
|
||||||
|
isSubmitting = true;
|
||||||
|
});
|
||||||
|
|
||||||
|
final result = await manager.editStatus(
|
||||||
|
widget.id,
|
||||||
|
contentController.text,
|
||||||
|
spoilerText: spoilerController.text,
|
||||||
|
inReplyToId: widget.parentId,
|
||||||
|
newMediaItems: newMediaItems,
|
||||||
|
existingMediaItems: existingMediaItems,
|
||||||
|
);
|
||||||
|
setState(() {
|
||||||
|
isSubmitting = false;
|
||||||
|
});
|
||||||
|
|
||||||
|
if (result.isFailure) {
|
||||||
|
buildSnackbar(context, 'Error Updating $statusType: ${result.error}');
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (mounted && context.canPop()) {
|
||||||
|
context.pop();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
@override
|
@override
|
||||||
Widget build(BuildContext context) {
|
Widget build(BuildContext context) {
|
||||||
_logger.finest('Build editor $isComment $parentEntry');
|
_logger.finest('Build editor $isComment $parentEntry');
|
||||||
|
@ -146,34 +213,20 @@ class _EditorScreenState extends State<EditorScreen> {
|
||||||
),
|
),
|
||||||
);
|
);
|
||||||
|
|
||||||
final submittingBody = Stack(
|
final Widget body;
|
||||||
children: [
|
if (widget.forEditing && !loaded) {
|
||||||
mainBody,
|
body = buildBusyBody(context, mainBody, 'Loading status');
|
||||||
Card(
|
} else if (isSubmitting) {
|
||||||
color: Theme.of(context).canvasColor.withOpacity(0.8),
|
body = buildBusyBody(context, mainBody, 'Submitting $statusType');
|
||||||
child: SizedBox(
|
} else {
|
||||||
width: MediaQuery.of(context).size.width,
|
body = mainBody;
|
||||||
height: MediaQuery.of(context).size.height,
|
}
|
||||||
child: Center(
|
|
||||||
child: Column(
|
|
||||||
mainAxisAlignment: MainAxisAlignment.center,
|
|
||||||
children: [
|
|
||||||
const CircularProgressIndicator(),
|
|
||||||
const VerticalPadding(),
|
|
||||||
Text('Submitting New $statusType'),
|
|
||||||
],
|
|
||||||
),
|
|
||||||
),
|
|
||||||
),
|
|
||||||
),
|
|
||||||
],
|
|
||||||
);
|
|
||||||
|
|
||||||
return Scaffold(
|
return Scaffold(
|
||||||
appBar: StandardAppBar.build(
|
appBar: StandardAppBar.build(
|
||||||
context, widget.id.isEmpty ? 'New $statusType' : 'Edit $statusType',
|
context, widget.id.isEmpty ? 'New $statusType' : 'Edit $statusType',
|
||||||
withDrawer: true),
|
withDrawer: true),
|
||||||
body: isSubmitting ? submittingBody : mainBody,
|
body: body,
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -270,10 +323,17 @@ class _EditorScreenState extends State<EditorScreen> {
|
||||||
return Row(
|
return Row(
|
||||||
mainAxisAlignment: MainAxisAlignment.center,
|
mainAxisAlignment: MainAxisAlignment.center,
|
||||||
children: [
|
children: [
|
||||||
ElevatedButton(
|
if (!widget.forEditing)
|
||||||
onPressed: isSubmitting ? null : () => createStatus(context, manager),
|
ElevatedButton(
|
||||||
child: const Text('Submit'),
|
onPressed:
|
||||||
),
|
isSubmitting ? null : () => createStatus(context, manager),
|
||||||
|
child: const Text('Submit'),
|
||||||
|
),
|
||||||
|
if (widget.forEditing)
|
||||||
|
ElevatedButton(
|
||||||
|
onPressed: isSubmitting ? null : () => editStatus(context, manager),
|
||||||
|
child: const Text('Edit'),
|
||||||
|
),
|
||||||
const HorizontalPadding(),
|
const HorizontalPadding(),
|
||||||
ElevatedButton(
|
ElevatedButton(
|
||||||
onPressed: isSubmitting
|
onPressed: isSubmitting
|
||||||
|
@ -286,4 +346,29 @@ class _EditorScreenState extends State<EditorScreen> {
|
||||||
],
|
],
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
Widget buildBusyBody(BuildContext context, Widget mainBody, String status) {
|
||||||
|
return Stack(
|
||||||
|
children: [
|
||||||
|
mainBody,
|
||||||
|
Card(
|
||||||
|
color: Theme.of(context).canvasColor.withOpacity(0.8),
|
||||||
|
child: SizedBox(
|
||||||
|
width: MediaQuery.of(context).size.width,
|
||||||
|
height: MediaQuery.of(context).size.height,
|
||||||
|
child: Center(
|
||||||
|
child: Column(
|
||||||
|
mainAxisAlignment: MainAxisAlignment.center,
|
||||||
|
children: [
|
||||||
|
const CircularProgressIndicator(),
|
||||||
|
const VerticalPadding(),
|
||||||
|
Text(status),
|
||||||
|
],
|
||||||
|
),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
],
|
||||||
|
);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -30,6 +30,7 @@ extension ImageEntryFriendicaExtension on ImageEntry {
|
||||||
final thumbUri = Uri.parse(thumbnailUrl);
|
final thumbUri = Uri.parse(thumbnailUrl);
|
||||||
final fullFileUri = scales.first.link;
|
final fullFileUri = scales.first.link;
|
||||||
return MediaAttachment(
|
return MediaAttachment(
|
||||||
|
id: id,
|
||||||
uri: fullFileUri,
|
uri: fullFileUri,
|
||||||
fullFileUri: fullFileUri,
|
fullFileUri: fullFileUri,
|
||||||
creationTimestamp: created.millisecondsSinceEpoch,
|
creationTimestamp: created.millisecondsSinceEpoch,
|
||||||
|
|
|
@ -3,6 +3,7 @@ import '../../models/media_attachment.dart';
|
||||||
|
|
||||||
extension MediaAttachmentFriendicaExtensions on MediaAttachment {
|
extension MediaAttachmentFriendicaExtensions on MediaAttachment {
|
||||||
static MediaAttachment fromJson(Map<String, dynamic> json) {
|
static MediaAttachment fromJson(Map<String, dynamic> json) {
|
||||||
|
final id = json['id'];
|
||||||
final uri = Uri.parse(json['url']);
|
final uri = Uri.parse(json['url']);
|
||||||
const creationTimestamp = 0;
|
const creationTimestamp = 0;
|
||||||
final metadata = (json['metadata'] as Map<String, dynamic>? ?? {})
|
final metadata = (json['metadata'] as Map<String, dynamic>? ?? {})
|
||||||
|
@ -17,6 +18,7 @@ extension MediaAttachmentFriendicaExtensions on MediaAttachment {
|
||||||
const description = '';
|
const description = '';
|
||||||
|
|
||||||
return MediaAttachment(
|
return MediaAttachment(
|
||||||
|
id: id,
|
||||||
uri: uri,
|
uri: uri,
|
||||||
fullFileUri: uri,
|
fullFileUri: uri,
|
||||||
creationTimestamp: creationTimestamp,
|
creationTimestamp: creationTimestamp,
|
||||||
|
|
|
@ -166,6 +166,93 @@ class EntryManagerService extends ChangeNotifier {
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
FutureResult<bool, ExecError> editStatus(
|
||||||
|
String id,
|
||||||
|
String text, {
|
||||||
|
String spoilerText = '',
|
||||||
|
required NewEntryMediaItems mediaItems,
|
||||||
|
required List<ImageEntry> existingMediaItems,
|
||||||
|
}) async {
|
||||||
|
_logger.finest('Editing post: $text');
|
||||||
|
final mediaIds = existingMediaItems
|
||||||
|
.map((m) => m.scales.isEmpty ? m.id : m.scales.first.id)
|
||||||
|
.toList();
|
||||||
|
for (final item in mediaItems.attachments) {
|
||||||
|
if (item.isExistingServerItem) {
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
final String extension = p.extension(item.localFilePath);
|
||||||
|
late final String filename;
|
||||||
|
if (item.remoteFilename.isEmpty) {
|
||||||
|
filename = p.basename(item.localFilePath);
|
||||||
|
} else {
|
||||||
|
if (item.remoteFilename
|
||||||
|
.toLowerCase()
|
||||||
|
.endsWith(extension.toLowerCase())) {
|
||||||
|
filename = item.remoteFilename;
|
||||||
|
} else {
|
||||||
|
filename = "${item.remoteFilename}$extension";
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
final uploadResult =
|
||||||
|
await MediaUploadAttachmentHelper.getUploadableImageBytes(
|
||||||
|
item.localFilePath,
|
||||||
|
).andThenAsync(
|
||||||
|
(imageBytes) async =>
|
||||||
|
await RemoteFileClient(getIt<AccountsService>().currentProfile)
|
||||||
|
.uploadFileAsAttachment(
|
||||||
|
bytes: imageBytes,
|
||||||
|
album: mediaItems.albumName,
|
||||||
|
description: item.description,
|
||||||
|
fileName: filename,
|
||||||
|
),
|
||||||
|
);
|
||||||
|
if (uploadResult.isSuccess) {
|
||||||
|
mediaIds.add(uploadResult.value.scales.first.id);
|
||||||
|
} else {
|
||||||
|
return Result.error(ExecError(
|
||||||
|
type: ErrorType.localError,
|
||||||
|
message: 'Error uploading image: ${uploadResult.error}'));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
final result = await StatusesClient(getIt<AccountsService>().currentProfile)
|
||||||
|
.editStatus(
|
||||||
|
id: id, text: text, spoilerText: spoilerText, mediaIds: mediaIds)
|
||||||
|
.andThenSuccessAsync((item) async {
|
||||||
|
await processNewItems(
|
||||||
|
[item], getIt<AccountsService>().currentProfile.username, null);
|
||||||
|
return item;
|
||||||
|
}).andThenSuccessAsync((item) async {
|
||||||
|
final inReplyToId = item.parentId;
|
||||||
|
if (inReplyToId.isNotEmpty) {
|
||||||
|
late final rootPostId;
|
||||||
|
if (_postNodes.containsKey(inReplyToId)) {
|
||||||
|
rootPostId = inReplyToId;
|
||||||
|
} else {
|
||||||
|
rootPostId = _parentPostIds[inReplyToId];
|
||||||
|
}
|
||||||
|
await refreshStatusChain(rootPostId);
|
||||||
|
}
|
||||||
|
return item;
|
||||||
|
});
|
||||||
|
|
||||||
|
return result.mapValue((status) {
|
||||||
|
_logger.finest('${status.id} status created');
|
||||||
|
return true;
|
||||||
|
}).mapError(
|
||||||
|
(error) {
|
||||||
|
_logger.finest('Error creating post: $error');
|
||||||
|
return ExecError(
|
||||||
|
type: ErrorType.localError,
|
||||||
|
message: error.toString(),
|
||||||
|
);
|
||||||
|
},
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
FutureResult<List<EntryTreeItem>, ExecError> updateTimeline(
|
FutureResult<List<EntryTreeItem>, ExecError> updateTimeline(
|
||||||
TimelineIdentifiers type, int maxId, int sinceId) async {
|
TimelineIdentifiers type, int maxId, int sinceId) async {
|
||||||
_logger.fine(() => 'Updating timeline');
|
_logger.fine(() => 'Updating timeline');
|
||||||
|
|
|
@ -84,6 +84,28 @@ class TimelineManager extends ChangeNotifier {
|
||||||
return result;
|
return result;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
FutureResult<bool, ExecError> editStatus(
|
||||||
|
String id,
|
||||||
|
String text, {
|
||||||
|
String spoilerText = '',
|
||||||
|
String inReplyToId = '',
|
||||||
|
required NewEntryMediaItems newMediaItems,
|
||||||
|
required List<ImageEntry> existingMediaItems,
|
||||||
|
}) async {
|
||||||
|
final result = await entryManagerService.editStatus(
|
||||||
|
id,
|
||||||
|
text,
|
||||||
|
spoilerText: spoilerText,
|
||||||
|
mediaItems: newMediaItems,
|
||||||
|
existingMediaItems: existingMediaItems,
|
||||||
|
);
|
||||||
|
if (result.isSuccess) {
|
||||||
|
_logger.finest('Notifying listeners of updated status');
|
||||||
|
notifyListeners();
|
||||||
|
}
|
||||||
|
return result;
|
||||||
|
}
|
||||||
|
|
||||||
Result<TimelineEntry, ExecError> getEntryById(String id) {
|
Result<TimelineEntry, ExecError> getEntryById(String id) {
|
||||||
_logger.finest('Getting entry for $id');
|
_logger.finest('Getting entry for $id');
|
||||||
return entryManagerService.getEntryById(id);
|
return entryManagerService.getEntryById(id);
|
||||||
|
|
Ładowanie…
Reference in New Issue