♻️ Refactored using controller as post editing
This commit is contained in:
parent
1ff4dc2a4b
commit
f23ffe61f5
@ -37,6 +37,7 @@
|
|||||||
"report": "Report",
|
"report": "Report",
|
||||||
"repost": "Repost",
|
"repost": "Repost",
|
||||||
"reply": "Reply",
|
"reply": "Reply",
|
||||||
|
"unset": "Unset",
|
||||||
"untitled": "Untitled",
|
"untitled": "Untitled",
|
||||||
"postDetail": "Post detail",
|
"postDetail": "Post detail",
|
||||||
"postNoun": "Post",
|
"postNoun": "Post",
|
||||||
@ -82,6 +83,8 @@
|
|||||||
"fieldPostTitle": "Title",
|
"fieldPostTitle": "Title",
|
||||||
"fieldPostDescription": "Description",
|
"fieldPostDescription": "Description",
|
||||||
"postPublish": "Publish",
|
"postPublish": "Publish",
|
||||||
|
"postPublishedAt": "Published At",
|
||||||
|
"postPublishedUntil": "Published Until",
|
||||||
"postEditingNotice": "You're about to editing a post that posted {}.",
|
"postEditingNotice": "You're about to editing a post that posted {}.",
|
||||||
"postReplyingNotice": "You're about to reply to a post that posted {}.",
|
"postReplyingNotice": "You're about to reply to a post that posted {}.",
|
||||||
"postRepostingNotice": "You're about to repost a post that posted {}.",
|
"postRepostingNotice": "You're about to repost a post that posted {}.",
|
||||||
|
@ -37,6 +37,7 @@
|
|||||||
"report": "检举",
|
"report": "检举",
|
||||||
"repost": "转帖",
|
"repost": "转帖",
|
||||||
"reply": "回贴",
|
"reply": "回贴",
|
||||||
|
"unset": "未设置",
|
||||||
"untitled": "无题",
|
"untitled": "无题",
|
||||||
"postDetail": "帖子详情",
|
"postDetail": "帖子详情",
|
||||||
"postNoun": "帖子",
|
"postNoun": "帖子",
|
||||||
@ -82,6 +83,8 @@
|
|||||||
"fieldPostTitle": "标题",
|
"fieldPostTitle": "标题",
|
||||||
"fieldPostDescription": "描述",
|
"fieldPostDescription": "描述",
|
||||||
"postPublish": "发布",
|
"postPublish": "发布",
|
||||||
|
"postPublishedAt": "发布于",
|
||||||
|
"postPublishedUntil": "取消发布于",
|
||||||
"postEditingNotice": "你正在修改由 {} 发布的帖子。",
|
"postEditingNotice": "你正在修改由 {} 发布的帖子。",
|
||||||
"postReplyingNotice": "你正在回复由 {} 发布的帖子。",
|
"postReplyingNotice": "你正在回复由 {} 发布的帖子。",
|
||||||
"postRepostingNotice": "你正在转发由 {} 发布的帖子。",
|
"postRepostingNotice": "你正在转发由 {} 发布的帖子。",
|
||||||
|
354
lib/controllers/post_write_controller.dart
Normal file
354
lib/controllers/post_write_controller.dart
Normal file
@ -0,0 +1,354 @@
|
|||||||
|
import 'dart:io';
|
||||||
|
|
||||||
|
import 'package:dio/dio.dart';
|
||||||
|
import 'package:flutter/foundation.dart';
|
||||||
|
import 'package:flutter/material.dart';
|
||||||
|
import 'package:image_picker/image_picker.dart';
|
||||||
|
import 'package:provider/provider.dart';
|
||||||
|
import 'package:surface/providers/sn_attachment.dart';
|
||||||
|
import 'package:surface/providers/sn_network.dart';
|
||||||
|
import 'package:surface/types/attachment.dart';
|
||||||
|
import 'package:surface/types/post.dart';
|
||||||
|
import 'package:surface/widgets/dialog.dart';
|
||||||
|
import 'package:surface/widgets/universal_image.dart';
|
||||||
|
|
||||||
|
enum PostWriteMediaType {
|
||||||
|
image,
|
||||||
|
video,
|
||||||
|
audio,
|
||||||
|
file,
|
||||||
|
}
|
||||||
|
|
||||||
|
class PostWriteMedia {
|
||||||
|
late String name;
|
||||||
|
late PostWriteMediaType type;
|
||||||
|
final SnAttachment? attachment;
|
||||||
|
final XFile? file;
|
||||||
|
final Uint8List? raw;
|
||||||
|
|
||||||
|
PostWriteMedia(this.attachment, {this.file, this.raw}) {
|
||||||
|
name = attachment!.name;
|
||||||
|
|
||||||
|
switch (attachment?.mimetype.split('/').firstOrNull) {
|
||||||
|
case 'image':
|
||||||
|
type = PostWriteMediaType.image;
|
||||||
|
break;
|
||||||
|
case 'video':
|
||||||
|
type = PostWriteMediaType.video;
|
||||||
|
break;
|
||||||
|
case 'audio':
|
||||||
|
type = PostWriteMediaType.audio;
|
||||||
|
break;
|
||||||
|
default:
|
||||||
|
type = PostWriteMediaType.file;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
PostWriteMedia.fromFile(this.file, {this.attachment, this.raw}) {
|
||||||
|
name = file!.name;
|
||||||
|
|
||||||
|
switch (file?.mimeType?.split('/').firstOrNull) {
|
||||||
|
case 'image':
|
||||||
|
type = PostWriteMediaType.image;
|
||||||
|
break;
|
||||||
|
case 'video':
|
||||||
|
type = PostWriteMediaType.video;
|
||||||
|
break;
|
||||||
|
case 'audio':
|
||||||
|
type = PostWriteMediaType.audio;
|
||||||
|
break;
|
||||||
|
default:
|
||||||
|
type = PostWriteMediaType.file;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
PostWriteMedia.fromBytes(this.raw, this.name, this.type,
|
||||||
|
{this.attachment, this.file});
|
||||||
|
|
||||||
|
bool get isEmpty => attachment == null && file == null && raw == null;
|
||||||
|
|
||||||
|
Future<int?> length() async {
|
||||||
|
if (attachment != null) {
|
||||||
|
return attachment!.size;
|
||||||
|
} else if (file != null) {
|
||||||
|
return await file!.length();
|
||||||
|
} else if (raw != null) {
|
||||||
|
return raw!.length;
|
||||||
|
}
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
XFile? toFile() {
|
||||||
|
if (file != null) {
|
||||||
|
return file!;
|
||||||
|
} else if (raw != null) {
|
||||||
|
return XFile.fromData(raw!, name: name);
|
||||||
|
}
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
ImageProvider? getImageProvider(
|
||||||
|
BuildContext context, {
|
||||||
|
int? width,
|
||||||
|
int? height,
|
||||||
|
}) {
|
||||||
|
if (attachment != null) {
|
||||||
|
final sn = context.read<SnNetworkProvider>();
|
||||||
|
return UniversalImage.provider(sn.getAttachmentUrl(attachment!.rid));
|
||||||
|
} else if (file != null) {
|
||||||
|
final ImageProvider provider =
|
||||||
|
kIsWeb ? NetworkImage(file!.path) : FileImage(File(file!.path));
|
||||||
|
if (width != null && height != null) {
|
||||||
|
return ResizeImage(
|
||||||
|
provider,
|
||||||
|
width: width,
|
||||||
|
height: height,
|
||||||
|
policy: ResizeImagePolicy.fit,
|
||||||
|
);
|
||||||
|
}
|
||||||
|
return provider;
|
||||||
|
} else if (raw != null) {
|
||||||
|
final provider = MemoryImage(raw!);
|
||||||
|
if (width != null && height != null) {
|
||||||
|
return ResizeImage(
|
||||||
|
provider,
|
||||||
|
width: width,
|
||||||
|
height: height,
|
||||||
|
policy: ResizeImagePolicy.fit,
|
||||||
|
);
|
||||||
|
}
|
||||||
|
return provider;
|
||||||
|
}
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
class PostWriteController extends ChangeNotifier {
|
||||||
|
static const Map<String, String> kTitleMap = {
|
||||||
|
'stories': 'writePostTypeStory',
|
||||||
|
'articles': 'writePostTypeArticle',
|
||||||
|
};
|
||||||
|
|
||||||
|
static const kAttachmentProgressWeight = 0.9;
|
||||||
|
static const kPostingProgressWeight = 0.1;
|
||||||
|
|
||||||
|
final TextEditingController contentController = TextEditingController();
|
||||||
|
final TextEditingController titleController = TextEditingController();
|
||||||
|
final TextEditingController descriptionController = TextEditingController();
|
||||||
|
|
||||||
|
PostWriteController() {
|
||||||
|
titleController.addListener(() => notifyListeners());
|
||||||
|
descriptionController.addListener(() => notifyListeners());
|
||||||
|
}
|
||||||
|
|
||||||
|
String mode = kTitleMap.keys.first;
|
||||||
|
|
||||||
|
String get title => titleController.text;
|
||||||
|
String get description => descriptionController.text;
|
||||||
|
bool get isRelatedNull =>
|
||||||
|
![editingPost, repostingPost, replyingPost].any((ele) => ele != null);
|
||||||
|
|
||||||
|
bool isLoading = false, isBusy = false;
|
||||||
|
double? progress;
|
||||||
|
|
||||||
|
SnPublisher? publisher;
|
||||||
|
SnPost? editingPost, repostingPost, replyingPost;
|
||||||
|
|
||||||
|
List<PostWriteMedia> attachments = List.empty(growable: true);
|
||||||
|
DateTime? publishedAt, publishedUntil;
|
||||||
|
|
||||||
|
Future<void> fetchRelatedPost(
|
||||||
|
BuildContext context, {
|
||||||
|
int? editing,
|
||||||
|
int? reposting,
|
||||||
|
int? replying,
|
||||||
|
}) async {
|
||||||
|
final sn = context.read<SnNetworkProvider>();
|
||||||
|
final attach = context.read<SnAttachmentProvider>();
|
||||||
|
|
||||||
|
isLoading = true;
|
||||||
|
notifyListeners();
|
||||||
|
|
||||||
|
try {
|
||||||
|
if (editing != null) {
|
||||||
|
final resp = await sn.client.get('/cgi/co/posts/$editing');
|
||||||
|
final post = SnPost.fromJson(resp.data);
|
||||||
|
final alts = await attach
|
||||||
|
.getMultiple(post.body['attachments']?.cast<String>() ?? []);
|
||||||
|
publisher = post.publisher;
|
||||||
|
titleController.text = post.body['title'] ?? '';
|
||||||
|
descriptionController.text = post.body['description'] ?? '';
|
||||||
|
contentController.text = post.body['content'] ?? '';
|
||||||
|
publishedAt = post.publishedAt;
|
||||||
|
publishedUntil = post.publishedUntil;
|
||||||
|
attachments.addAll(alts.map((ele) => PostWriteMedia(ele)));
|
||||||
|
|
||||||
|
editingPost = post.copyWith(
|
||||||
|
preload: SnPostPreload(
|
||||||
|
attachments: alts,
|
||||||
|
),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (replying != null) {
|
||||||
|
final resp = await sn.client.get('/cgi/co/posts/$replying');
|
||||||
|
final post = SnPost.fromJson(resp.data);
|
||||||
|
replyingPost = post.copyWith(
|
||||||
|
preload: SnPostPreload(
|
||||||
|
attachments: await attach
|
||||||
|
.getMultiple(post.body['attachments']?.cast<String>() ?? []),
|
||||||
|
),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (reposting != null) {
|
||||||
|
final resp = await sn.client.get('/cgi/co/posts/$reposting');
|
||||||
|
final post = SnPost.fromJson(resp.data);
|
||||||
|
repostingPost = post.copyWith(
|
||||||
|
preload: SnPostPreload(
|
||||||
|
attachments: await attach
|
||||||
|
.getMultiple(post.body['attachments']?.cast<String>() ?? []),
|
||||||
|
),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
} catch (err) {
|
||||||
|
if (!context.mounted) return;
|
||||||
|
context.showErrorDialog(err);
|
||||||
|
} finally {
|
||||||
|
isLoading = false;
|
||||||
|
notifyListeners();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
void post(BuildContext context) async {
|
||||||
|
if (isBusy || publisher == null) return;
|
||||||
|
|
||||||
|
final sn = context.read<SnNetworkProvider>();
|
||||||
|
final attach = context.read<SnAttachmentProvider>();
|
||||||
|
|
||||||
|
progress = 0;
|
||||||
|
isBusy = true;
|
||||||
|
notifyListeners();
|
||||||
|
|
||||||
|
// Uploading attachments
|
||||||
|
try {
|
||||||
|
for (int i = 0; i < attachments.length; i++) {
|
||||||
|
final media = attachments[i];
|
||||||
|
if (media.attachment != null) continue; // Already uploaded, skip
|
||||||
|
if (media.isEmpty) continue; // Nothing to do, skip
|
||||||
|
|
||||||
|
final place = await attach.chunkedUploadInitialize(
|
||||||
|
(await media.length())!,
|
||||||
|
media.name,
|
||||||
|
'interactive',
|
||||||
|
null,
|
||||||
|
);
|
||||||
|
|
||||||
|
final item = await attach.chunkedUploadParts(
|
||||||
|
media.toFile()!,
|
||||||
|
place.$1,
|
||||||
|
place.$2,
|
||||||
|
onProgress: (progress) {
|
||||||
|
// Calculate overall progress for attachments
|
||||||
|
progress = ((i + progress) / attachments.length) *
|
||||||
|
kAttachmentProgressWeight;
|
||||||
|
notifyListeners();
|
||||||
|
},
|
||||||
|
);
|
||||||
|
|
||||||
|
attachments[i] = PostWriteMedia(item);
|
||||||
|
}
|
||||||
|
} catch (err) {
|
||||||
|
isBusy = false;
|
||||||
|
notifyListeners();
|
||||||
|
if (!context.mounted) return;
|
||||||
|
context.showErrorDialog(err);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
progress = kAttachmentProgressWeight;
|
||||||
|
notifyListeners();
|
||||||
|
|
||||||
|
// Posting the content
|
||||||
|
try {
|
||||||
|
final baseProgressVal = progress!;
|
||||||
|
await sn.client.request(
|
||||||
|
[
|
||||||
|
'/cgi/co/$mode',
|
||||||
|
if (editingPost != null) '${editingPost!.id}',
|
||||||
|
].join('/'),
|
||||||
|
data: {
|
||||||
|
'publisher': publisher!.id,
|
||||||
|
'content': contentController.text,
|
||||||
|
'title': titleController.text,
|
||||||
|
'description': descriptionController.text,
|
||||||
|
'attachments': attachments
|
||||||
|
.where((e) => e.attachment != null)
|
||||||
|
.map((e) => e.attachment!.rid)
|
||||||
|
.toList(),
|
||||||
|
if (publishedAt != null)
|
||||||
|
'published_at': publishedAt!.toUtc().toIso8601String(),
|
||||||
|
if (publishedUntil != null)
|
||||||
|
'published_until': publishedAt!.toUtc().toIso8601String(),
|
||||||
|
if (replyingPost != null) 'reply_to': replyingPost!.id,
|
||||||
|
if (repostingPost != null) 'repost_to': repostingPost!.id,
|
||||||
|
},
|
||||||
|
onSendProgress: (count, total) {
|
||||||
|
progress =
|
||||||
|
baseProgressVal + (count / total) * (kPostingProgressWeight / 2);
|
||||||
|
notifyListeners();
|
||||||
|
},
|
||||||
|
onReceiveProgress: (count, total) {
|
||||||
|
progress = baseProgressVal +
|
||||||
|
(kPostingProgressWeight / 2) +
|
||||||
|
(count / total) * (kPostingProgressWeight / 2);
|
||||||
|
notifyListeners();
|
||||||
|
},
|
||||||
|
options: Options(
|
||||||
|
method: editingPost != null ? 'PUT' : 'POST',
|
||||||
|
),
|
||||||
|
);
|
||||||
|
if (!context.mounted) return;
|
||||||
|
Navigator.pop(context, true);
|
||||||
|
} catch (err) {
|
||||||
|
if (!context.mounted) return;
|
||||||
|
context.showErrorDialog(err);
|
||||||
|
} finally {
|
||||||
|
isBusy = false;
|
||||||
|
notifyListeners();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
void addAttachments(Iterable<PostWriteMedia> items) {
|
||||||
|
attachments.addAll(items);
|
||||||
|
notifyListeners();
|
||||||
|
}
|
||||||
|
|
||||||
|
void removeAttachmentAt(int idx) {
|
||||||
|
attachments.removeAt(idx);
|
||||||
|
notifyListeners();
|
||||||
|
}
|
||||||
|
|
||||||
|
void setPublisher(SnPublisher? item) {
|
||||||
|
publisher = item;
|
||||||
|
notifyListeners();
|
||||||
|
}
|
||||||
|
|
||||||
|
void setPublishedAt(DateTime? value) {
|
||||||
|
publishedAt = value;
|
||||||
|
notifyListeners();
|
||||||
|
}
|
||||||
|
|
||||||
|
void setPublishedUntil(DateTime? value) {
|
||||||
|
publishedUntil = value;
|
||||||
|
notifyListeners();
|
||||||
|
}
|
||||||
|
|
||||||
|
@override
|
||||||
|
void dispose() {
|
||||||
|
contentController.dispose();
|
||||||
|
titleController.dispose();
|
||||||
|
descriptionController.dispose();
|
||||||
|
super.dispose();
|
||||||
|
}
|
||||||
|
}
|
@ -1,7 +1,6 @@
|
|||||||
import 'dart:math' as math;
|
import 'dart:math' as math;
|
||||||
|
|
||||||
import 'package:collection/collection.dart';
|
import 'package:collection/collection.dart';
|
||||||
import 'package:dio/dio.dart';
|
|
||||||
import 'package:dropdown_button2/dropdown_button2.dart';
|
import 'package:dropdown_button2/dropdown_button2.dart';
|
||||||
import 'package:easy_localization/easy_localization.dart';
|
import 'package:easy_localization/easy_localization.dart';
|
||||||
import 'package:flutter/gestures.dart';
|
import 'package:flutter/gestures.dart';
|
||||||
@ -11,9 +10,8 @@ import 'package:go_router/go_router.dart';
|
|||||||
import 'package:image_picker/image_picker.dart';
|
import 'package:image_picker/image_picker.dart';
|
||||||
import 'package:material_symbols_icons/symbols.dart';
|
import 'package:material_symbols_icons/symbols.dart';
|
||||||
import 'package:styled_widget/styled_widget.dart';
|
import 'package:styled_widget/styled_widget.dart';
|
||||||
import 'package:surface/providers/sn_attachment.dart';
|
import 'package:surface/controllers/post_write_controller.dart';
|
||||||
import 'package:surface/providers/sn_network.dart';
|
import 'package:surface/providers/sn_network.dart';
|
||||||
import 'package:surface/types/attachment.dart';
|
|
||||||
import 'package:surface/types/post.dart';
|
import 'package:surface/types/post.dart';
|
||||||
import 'package:surface/widgets/account/account_image.dart';
|
import 'package:surface/widgets/account/account_image.dart';
|
||||||
import 'package:surface/widgets/loading_indicator.dart';
|
import 'package:surface/widgets/loading_indicator.dart';
|
||||||
@ -42,203 +40,37 @@ class PostEditorScreen extends StatefulWidget {
|
|||||||
}
|
}
|
||||||
|
|
||||||
class _PostEditorScreenState extends State<PostEditorScreen> {
|
class _PostEditorScreenState extends State<PostEditorScreen> {
|
||||||
static const Map<String, String> _kTitleMap = {
|
final PostWriteController _writeController = PostWriteController();
|
||||||
'stories': 'writePostTypeStory',
|
|
||||||
'articles': 'writePostTypeArticle',
|
|
||||||
};
|
|
||||||
|
|
||||||
bool _isBusy = false;
|
bool _isFetching = false;
|
||||||
bool _isLoading = false;
|
bool get _isLoading => _isFetching || _writeController.isLoading;
|
||||||
|
|
||||||
SnPublisher? _publisher;
|
|
||||||
List<SnPublisher>? _publishers;
|
List<SnPublisher>? _publishers;
|
||||||
|
|
||||||
final List<XFile> _selectedMedia = List.empty(growable: true);
|
|
||||||
final List<SnAttachment> _attachments = List.empty(growable: true);
|
|
||||||
|
|
||||||
Future<void> _fetchPublishers() async {
|
Future<void> _fetchPublishers() async {
|
||||||
|
setState(() => _isFetching = true);
|
||||||
|
|
||||||
|
try {
|
||||||
final sn = context.read<SnNetworkProvider>();
|
final sn = context.read<SnNetworkProvider>();
|
||||||
final resp = await sn.client.get('/cgi/co/publishers');
|
final resp = await sn.client.get('/cgi/co/publishers');
|
||||||
_publishers = List<SnPublisher>.from(
|
_publishers = List<SnPublisher>.from(
|
||||||
resp.data?.map((e) => SnPublisher.fromJson(e)) ?? [],
|
resp.data?.map((e) => SnPublisher.fromJson(e)) ?? [],
|
||||||
);
|
);
|
||||||
setState(() {
|
_writeController.setPublisher(_publishers?.firstOrNull);
|
||||||
_publisher = _publishers?.first;
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
SnPost? _editingOg;
|
|
||||||
SnPost? _replyingTo;
|
|
||||||
SnPost? _repostingTo;
|
|
||||||
|
|
||||||
Future<void> _fetchRelatedPost() async {
|
|
||||||
final sn = context.read<SnNetworkProvider>();
|
|
||||||
final attach = context.read<SnAttachmentProvider>();
|
|
||||||
|
|
||||||
try {
|
|
||||||
setState(() => _isLoading = true);
|
|
||||||
|
|
||||||
if (widget.postEditId != null) {
|
|
||||||
final resp = await sn.client.get('/cgi/co/posts/${widget.postEditId}');
|
|
||||||
final post = SnPost.fromJson(resp.data);
|
|
||||||
final attachments = await attach
|
|
||||||
.getMultiple(post.body['attachments']?.cast<String>() ?? []);
|
|
||||||
_title = post.body['title'];
|
|
||||||
_description = post.body['description'];
|
|
||||||
_contentController.text = post.body['content'] ?? '';
|
|
||||||
_attachments.addAll(attachments);
|
|
||||||
|
|
||||||
_editingOg = post.copyWith(
|
|
||||||
preload: SnPostPreload(
|
|
||||||
attachments: attachments,
|
|
||||||
),
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
if (widget.postReplyId != null) {
|
|
||||||
final resp = await sn.client.get('/cgi/co/posts/${widget.postReplyId}');
|
|
||||||
final post = SnPost.fromJson(resp.data);
|
|
||||||
_replyingTo = post.copyWith(
|
|
||||||
preload: SnPostPreload(
|
|
||||||
attachments: await attach
|
|
||||||
.getMultiple(post.body['attachments']?.cast<String>() ?? []),
|
|
||||||
),
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
if (widget.postRepostId != null) {
|
|
||||||
final resp =
|
|
||||||
await sn.client.get('/cgi/co/posts/${widget.postRepostId}');
|
|
||||||
final post = SnPost.fromJson(resp.data);
|
|
||||||
_repostingTo = post.copyWith(
|
|
||||||
preload: SnPostPreload(
|
|
||||||
attachments: await attach
|
|
||||||
.getMultiple(post.body['attachments']?.cast<String>() ?? []),
|
|
||||||
),
|
|
||||||
);
|
|
||||||
}
|
|
||||||
} catch (err) {
|
|
||||||
context.showErrorDialog(err);
|
|
||||||
} finally {
|
|
||||||
setState(() => _isLoading = false);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
String? _title;
|
|
||||||
String? _description;
|
|
||||||
|
|
||||||
final TextEditingController _contentController = TextEditingController();
|
|
||||||
|
|
||||||
double? _progress;
|
|
||||||
|
|
||||||
static const kAttachmentProgressWeight = 0.9;
|
|
||||||
static const kPostingProgressWeight = 0.1;
|
|
||||||
|
|
||||||
void _performAction() async {
|
|
||||||
if (_isBusy || _publisher == null) return;
|
|
||||||
|
|
||||||
final sn = context.read<SnNetworkProvider>();
|
|
||||||
final attach = context.read<SnAttachmentProvider>();
|
|
||||||
|
|
||||||
setState(() {
|
|
||||||
_progress = 0;
|
|
||||||
_isBusy = true;
|
|
||||||
});
|
|
||||||
|
|
||||||
// Uploading attachments
|
|
||||||
try {
|
|
||||||
for (int i = 0; i < _selectedMedia.length; i++) {
|
|
||||||
final media = _selectedMedia[i];
|
|
||||||
final place = await attach.chunkedUploadInitialize(
|
|
||||||
await media.length(),
|
|
||||||
media.name,
|
|
||||||
'interactive',
|
|
||||||
null,
|
|
||||||
);
|
|
||||||
|
|
||||||
final item = await attach.chunkedUploadParts(
|
|
||||||
media,
|
|
||||||
place.$1,
|
|
||||||
place.$2,
|
|
||||||
onProgress: (progress) {
|
|
||||||
// Calculate overall progress for attachments
|
|
||||||
setState(() {
|
|
||||||
_progress = ((i + progress) / _selectedMedia.length) *
|
|
||||||
kAttachmentProgressWeight;
|
|
||||||
});
|
|
||||||
},
|
|
||||||
);
|
|
||||||
|
|
||||||
_attachments.add(item);
|
|
||||||
}
|
|
||||||
} catch (err) {
|
|
||||||
if (!mounted) return;
|
|
||||||
context.showErrorDialog(err);
|
|
||||||
setState(() => _isBusy = false);
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
setState(() => _progress = kAttachmentProgressWeight);
|
|
||||||
|
|
||||||
// Posting the content
|
|
||||||
try {
|
|
||||||
final baseProgressVal = _progress!;
|
|
||||||
await sn.client.request(
|
|
||||||
[
|
|
||||||
'/cgi/co/${widget.mode}',
|
|
||||||
if (widget.postEditId != null) '${widget.postEditId}',
|
|
||||||
].join('/'),
|
|
||||||
data: {
|
|
||||||
'publisher': _publisher!.id,
|
|
||||||
'content': _contentController.value.text,
|
|
||||||
'title': _title,
|
|
||||||
'description': _description,
|
|
||||||
'attachments': _attachments.map((e) => e.rid).toList(),
|
|
||||||
if (_replyingTo != null) 'reply_to': _replyingTo!.id,
|
|
||||||
if (_repostingTo != null) 'repost_to': _repostingTo!.id,
|
|
||||||
},
|
|
||||||
onSendProgress: (count, total) {
|
|
||||||
setState(() {
|
|
||||||
_progress = baseProgressVal +
|
|
||||||
(count / total) * (kPostingProgressWeight / 2);
|
|
||||||
});
|
|
||||||
},
|
|
||||||
onReceiveProgress: (count, total) {
|
|
||||||
setState(() {
|
|
||||||
_progress = baseProgressVal +
|
|
||||||
(kPostingProgressWeight / 2) +
|
|
||||||
(count / total) * (kPostingProgressWeight / 2);
|
|
||||||
});
|
|
||||||
},
|
|
||||||
options: Options(
|
|
||||||
method: widget.postEditId != null ? 'PUT' : 'POST',
|
|
||||||
),
|
|
||||||
);
|
|
||||||
if (!mounted) return;
|
|
||||||
Navigator.pop(context, true);
|
|
||||||
} catch (err) {
|
} catch (err) {
|
||||||
if (!mounted) return;
|
if (!mounted) return;
|
||||||
context.showErrorDialog(err);
|
context.showErrorDialog(err);
|
||||||
} finally {
|
} finally {
|
||||||
setState(() => _isBusy = false);
|
setState(() => _isFetching = false);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
void _updateMeta() {
|
void _updateMeta() {
|
||||||
showModalBottomSheet<PostMetaResult?>(
|
showModalBottomSheet(
|
||||||
context: context,
|
context: context,
|
||||||
builder: (context) => PostMetaEditor(
|
builder: (context) => PostMetaEditor(controller: _writeController),
|
||||||
initialTitle: _title,
|
|
||||||
initialDescription: _description,
|
|
||||||
),
|
|
||||||
useRootNavigator: true,
|
useRootNavigator: true,
|
||||||
).then((value) {
|
);
|
||||||
if (value is PostMetaResult) {
|
|
||||||
_title = value.title;
|
|
||||||
_description = value.description;
|
|
||||||
setState(() {});
|
|
||||||
}
|
|
||||||
});
|
|
||||||
}
|
}
|
||||||
|
|
||||||
final _imagePicker = ImagePicker();
|
final _imagePicker = ImagePicker();
|
||||||
@ -246,29 +78,39 @@ class _PostEditorScreenState extends State<PostEditorScreen> {
|
|||||||
void _selectMedia() async {
|
void _selectMedia() async {
|
||||||
final result = await _imagePicker.pickMultipleMedia();
|
final result = await _imagePicker.pickMultipleMedia();
|
||||||
if (result.isEmpty) return;
|
if (result.isEmpty) return;
|
||||||
_selectedMedia.addAll(result);
|
_writeController.addAttachments(
|
||||||
|
result.map((e) => PostWriteMedia.fromFile(e)),
|
||||||
|
);
|
||||||
setState(() {});
|
setState(() {});
|
||||||
}
|
}
|
||||||
|
|
||||||
@override
|
@override
|
||||||
void dispose() {
|
void dispose() {
|
||||||
_contentController.dispose();
|
_writeController.dispose();
|
||||||
super.dispose();
|
super.dispose();
|
||||||
}
|
}
|
||||||
|
|
||||||
@override
|
@override
|
||||||
void initState() {
|
void initState() {
|
||||||
super.initState();
|
super.initState();
|
||||||
if (!_kTitleMap.keys.contains(widget.mode)) {
|
if (!PostWriteController.kTitleMap.keys.contains(widget.mode)) {
|
||||||
context.showErrorDialog('Unknown post type');
|
context.showErrorDialog('Unknown post type');
|
||||||
Navigator.pop(context);
|
Navigator.pop(context);
|
||||||
}
|
}
|
||||||
_fetchRelatedPost();
|
|
||||||
_fetchPublishers();
|
_fetchPublishers();
|
||||||
|
_writeController.fetchRelatedPost(
|
||||||
|
context,
|
||||||
|
editing: widget.postEditId,
|
||||||
|
replying: widget.postReplyId,
|
||||||
|
reposting: widget.postRepostId,
|
||||||
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
@override
|
@override
|
||||||
Widget build(BuildContext context) {
|
Widget build(BuildContext context) {
|
||||||
|
return ListenableBuilder(
|
||||||
|
listenable: _writeController,
|
||||||
|
builder: (context, _) {
|
||||||
return AppScaffold(
|
return AppScaffold(
|
||||||
appBar: AppBar(
|
appBar: AppBar(
|
||||||
leading: BackButton(
|
leading: BackButton(
|
||||||
@ -278,10 +120,12 @@ class _PostEditorScreenState extends State<PostEditorScreen> {
|
|||||||
),
|
),
|
||||||
flexibleSpace: Column(
|
flexibleSpace: Column(
|
||||||
children: [
|
children: [
|
||||||
Text(_title ?? 'untitled'.tr())
|
Text(_writeController.title.isNotEmpty
|
||||||
|
? _writeController.title
|
||||||
|
: 'untitled'.tr())
|
||||||
.textStyle(Theme.of(context).textTheme.titleLarge!)
|
.textStyle(Theme.of(context).textTheme.titleLarge!)
|
||||||
.textColor(Colors.white),
|
.textColor(Colors.white),
|
||||||
Text(_kTitleMap[widget.mode]!)
|
Text(PostWriteController.kTitleMap[widget.mode]!)
|
||||||
.tr()
|
.tr()
|
||||||
.textColor(Colors.white.withAlpha((255 * 0.9).round())),
|
.textColor(Colors.white.withAlpha((255 * 0.9).round())),
|
||||||
],
|
],
|
||||||
@ -289,7 +133,7 @@ class _PostEditorScreenState extends State<PostEditorScreen> {
|
|||||||
actions: [
|
actions: [
|
||||||
IconButton(
|
IconButton(
|
||||||
icon: const Icon(Symbols.tune),
|
icon: const Icon(Symbols.tune),
|
||||||
onPressed: _isBusy ? null : _updateMeta,
|
onPressed: _writeController.isBusy ? null : _updateMeta,
|
||||||
),
|
),
|
||||||
],
|
],
|
||||||
),
|
),
|
||||||
@ -308,7 +152,7 @@ class _PostEditorScreenState extends State<PostEditorScreen> {
|
|||||||
items: <DropdownMenuItem<SnPublisher>>[
|
items: <DropdownMenuItem<SnPublisher>>[
|
||||||
...(_publishers?.map(
|
...(_publishers?.map(
|
||||||
(item) => DropdownMenuItem<SnPublisher>(
|
(item) => DropdownMenuItem<SnPublisher>(
|
||||||
enabled: _editingOg == null,
|
enabled: _writeController.editingPost == null,
|
||||||
value: item,
|
value: item,
|
||||||
child: Row(
|
child: Row(
|
||||||
children: [
|
children: [
|
||||||
@ -317,10 +161,13 @@ class _PostEditorScreenState extends State<PostEditorScreen> {
|
|||||||
Expanded(
|
Expanded(
|
||||||
child: Column(
|
child: Column(
|
||||||
mainAxisSize: MainAxisSize.min,
|
mainAxisSize: MainAxisSize.min,
|
||||||
crossAxisAlignment: CrossAxisAlignment.start,
|
crossAxisAlignment:
|
||||||
|
CrossAxisAlignment.start,
|
||||||
children: [
|
children: [
|
||||||
Text(item.nick).textStyle(
|
Text(item.nick).textStyle(
|
||||||
Theme.of(context).textTheme.bodyMedium!),
|
Theme.of(context)
|
||||||
|
.textTheme
|
||||||
|
.bodyMedium!),
|
||||||
Text('@${item.name}')
|
Text('@${item.name}')
|
||||||
.textStyle(Theme.of(context)
|
.textStyle(Theme.of(context)
|
||||||
.textTheme
|
.textTheme
|
||||||
@ -360,22 +207,19 @@ class _PostEditorScreenState extends State<PostEditorScreen> {
|
|||||||
),
|
),
|
||||||
),
|
),
|
||||||
],
|
],
|
||||||
value: _publisher,
|
value: _writeController.publisher,
|
||||||
onChanged: (SnPublisher? value) {
|
onChanged: (SnPublisher? value) {
|
||||||
if (value == null) {
|
if (value == null) {
|
||||||
GoRouter.of(context)
|
GoRouter.of(context)
|
||||||
.pushNamed('accountPublisherNew')
|
.pushNamed('accountPublisherNew')
|
||||||
.then((value) {
|
.then((value) {
|
||||||
if (value == true) {
|
if (value == true) {
|
||||||
_publisher = null;
|
|
||||||
_publishers = null;
|
_publishers = null;
|
||||||
_fetchPublishers();
|
_fetchPublishers();
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
} else {
|
} else {
|
||||||
setState(() {
|
_writeController.setPublisher(value);
|
||||||
_publisher = value;
|
|
||||||
});
|
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
buttonStyleData: const ButtonStyleData(
|
buttonStyleData: const ButtonStyleData(
|
||||||
@ -394,7 +238,7 @@ class _PostEditorScreenState extends State<PostEditorScreen> {
|
|||||||
child: Column(
|
child: Column(
|
||||||
children: [
|
children: [
|
||||||
// Replying Notice
|
// Replying Notice
|
||||||
if (_replyingTo != null)
|
if (_writeController.replyingPost != null)
|
||||||
Column(
|
Column(
|
||||||
children: [
|
children: [
|
||||||
Theme(
|
Theme(
|
||||||
@ -403,18 +247,23 @@ class _PostEditorScreenState extends State<PostEditorScreen> {
|
|||||||
),
|
),
|
||||||
child: ExpansionTile(
|
child: ExpansionTile(
|
||||||
minTileHeight: 48,
|
minTileHeight: 48,
|
||||||
leading: const Icon(Symbols.reply).padding(left: 4),
|
leading:
|
||||||
|
const Icon(Symbols.reply).padding(left: 4),
|
||||||
title: Text('postReplyingNotice')
|
title: Text('postReplyingNotice')
|
||||||
.fontSize(15)
|
.fontSize(15)
|
||||||
.tr(args: ['@${_replyingTo!.publisher.name}']),
|
.tr(args: [
|
||||||
children: <Widget>[PostItem(data: _replyingTo!)],
|
'@${_writeController.replyingPost!.publisher.name}'
|
||||||
|
]),
|
||||||
|
children: <Widget>[
|
||||||
|
PostItem(data: _writeController.replyingPost!)
|
||||||
|
],
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
const Divider(height: 1),
|
const Divider(height: 1),
|
||||||
],
|
],
|
||||||
),
|
),
|
||||||
// Reposting Notice
|
// Reposting Notice
|
||||||
if (_repostingTo != null)
|
if (_writeController.repostingPost != null)
|
||||||
Column(
|
Column(
|
||||||
children: [
|
children: [
|
||||||
Theme(
|
Theme(
|
||||||
@ -423,19 +272,24 @@ class _PostEditorScreenState extends State<PostEditorScreen> {
|
|||||||
),
|
),
|
||||||
child: ExpansionTile(
|
child: ExpansionTile(
|
||||||
minTileHeight: 48,
|
minTileHeight: 48,
|
||||||
leading:
|
leading: const Icon(Symbols.forward)
|
||||||
const Icon(Symbols.forward).padding(left: 4),
|
.padding(left: 4),
|
||||||
title: Text('postRepostingNotice')
|
title: Text('postRepostingNotice')
|
||||||
.fontSize(15)
|
.fontSize(15)
|
||||||
.tr(args: ['@${_repostingTo!.publisher.name}']),
|
.tr(args: [
|
||||||
children: <Widget>[PostItem(data: _repostingTo!)],
|
'@${_writeController.repostingPost!.publisher.name}'
|
||||||
|
]),
|
||||||
|
children: <Widget>[
|
||||||
|
PostItem(
|
||||||
|
data: _writeController.repostingPost!)
|
||||||
|
],
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
const Divider(height: 1),
|
const Divider(height: 1),
|
||||||
],
|
],
|
||||||
),
|
),
|
||||||
// Editing Notice
|
// Editing Notice
|
||||||
if (_editingOg != null)
|
if (_writeController.editingPost != null)
|
||||||
Column(
|
Column(
|
||||||
children: [
|
children: [
|
||||||
Theme(
|
Theme(
|
||||||
@ -444,12 +298,16 @@ class _PostEditorScreenState extends State<PostEditorScreen> {
|
|||||||
),
|
),
|
||||||
child: ExpansionTile(
|
child: ExpansionTile(
|
||||||
minTileHeight: 48,
|
minTileHeight: 48,
|
||||||
leading:
|
leading: const Icon(Symbols.edit_note)
|
||||||
const Icon(Symbols.edit_note).padding(left: 4),
|
.padding(left: 4),
|
||||||
title: Text('postEditingNotice')
|
title: Text('postEditingNotice')
|
||||||
.fontSize(15)
|
.fontSize(15)
|
||||||
.tr(args: ['@${_editingOg!.publisher.name}']),
|
.tr(args: [
|
||||||
children: <Widget>[PostItem(data: _editingOg!)],
|
'@${_writeController.editingPost!.publisher.name}'
|
||||||
|
]),
|
||||||
|
children: <Widget>[
|
||||||
|
PostItem(data: _writeController.editingPost!)
|
||||||
|
],
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
const Divider(height: 1),
|
const Divider(height: 1),
|
||||||
@ -457,7 +315,7 @@ class _PostEditorScreenState extends State<PostEditorScreen> {
|
|||||||
),
|
),
|
||||||
// Content Input Area
|
// Content Input Area
|
||||||
TextField(
|
TextField(
|
||||||
controller: _contentController,
|
controller: _writeController.contentController,
|
||||||
maxLines: null,
|
maxLines: null,
|
||||||
decoration: InputDecoration(
|
decoration: InputDecoration(
|
||||||
hintText: 'fieldPostContent'.tr(),
|
hintText: 'fieldPostContent'.tr(),
|
||||||
@ -474,9 +332,7 @@ class _PostEditorScreenState extends State<PostEditorScreen> {
|
|||||||
]
|
]
|
||||||
.expandIndexed(
|
.expandIndexed(
|
||||||
(idx, ele) => [
|
(idx, ele) => [
|
||||||
if (idx != 0 ||
|
if (idx != 0 || _writeController.isRelatedNull)
|
||||||
![_editingOg, _replyingTo, _repostingTo]
|
|
||||||
.any((x) => x != null))
|
|
||||||
const Gap(8),
|
const Gap(8),
|
||||||
ele,
|
ele,
|
||||||
],
|
],
|
||||||
@ -485,13 +341,11 @@ class _PostEditorScreenState extends State<PostEditorScreen> {
|
|||||||
),
|
),
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
if (_selectedMedia.isNotEmpty)
|
if (_writeController.attachments.isNotEmpty)
|
||||||
PostMediaPendingList(
|
PostMediaPendingList(
|
||||||
data: _selectedMedia,
|
data: _writeController.attachments,
|
||||||
onRemove: (idx) {
|
onRemove: (idx) {
|
||||||
setState(() {
|
_writeController.removeAttachmentAt(idx);
|
||||||
_selectedMedia.removeAt(idx);
|
|
||||||
});
|
|
||||||
},
|
},
|
||||||
).padding(bottom: 8),
|
).padding(bottom: 8),
|
||||||
Material(
|
Material(
|
||||||
@ -500,14 +354,15 @@ class _PostEditorScreenState extends State<PostEditorScreen> {
|
|||||||
crossAxisAlignment: CrossAxisAlignment.start,
|
crossAxisAlignment: CrossAxisAlignment.start,
|
||||||
children: [
|
children: [
|
||||||
LoadingIndicator(isActive: _isLoading),
|
LoadingIndicator(isActive: _isLoading),
|
||||||
if (_isBusy && _progress != null)
|
if (_writeController.isBusy &&
|
||||||
|
_writeController.progress != null)
|
||||||
TweenAnimationBuilder<double>(
|
TweenAnimationBuilder<double>(
|
||||||
tween: Tween(begin: 0, end: 1),
|
tween: Tween(begin: 0, end: _writeController.progress),
|
||||||
duration: Duration(milliseconds: 300),
|
duration: Duration(milliseconds: 300),
|
||||||
builder: (context, value, _) =>
|
builder: (context, value, _) =>
|
||||||
LinearProgressIndicator(value: value, minHeight: 2),
|
LinearProgressIndicator(value: value, minHeight: 2),
|
||||||
)
|
)
|
||||||
else if (_isBusy)
|
else if (_writeController.isBusy)
|
||||||
const LinearProgressIndicator(value: null, minHeight: 2),
|
const LinearProgressIndicator(value: null, minHeight: 2),
|
||||||
Row(
|
Row(
|
||||||
mainAxisAlignment: MainAxisAlignment.spaceBetween,
|
mainAxisAlignment: MainAxisAlignment.spaceBetween,
|
||||||
@ -520,10 +375,13 @@ class _PostEditorScreenState extends State<PostEditorScreen> {
|
|||||||
child: Row(
|
child: Row(
|
||||||
children: [
|
children: [
|
||||||
IconButton(
|
IconButton(
|
||||||
onPressed: _isBusy ? null : _selectMedia,
|
onPressed: _writeController.isBusy
|
||||||
|
? null
|
||||||
|
: _selectMedia,
|
||||||
icon: Icon(
|
icon: Icon(
|
||||||
Symbols.add_photo_alternate,
|
Symbols.add_photo_alternate,
|
||||||
color: Theme.of(context).colorScheme.primary,
|
color:
|
||||||
|
Theme.of(context).colorScheme.primary,
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
],
|
],
|
||||||
@ -532,9 +390,10 @@ class _PostEditorScreenState extends State<PostEditorScreen> {
|
|||||||
),
|
),
|
||||||
),
|
),
|
||||||
TextButton.icon(
|
TextButton.icon(
|
||||||
onPressed: (_isBusy || _publisher == null)
|
onPressed: (_writeController.isBusy ||
|
||||||
|
_writeController.publisher == null)
|
||||||
? null
|
? null
|
||||||
: _performAction,
|
: () => _writeController.post(context),
|
||||||
icon: const Icon(Symbols.send),
|
icon: const Icon(Symbols.send),
|
||||||
label: Text('postPublish').tr(),
|
label: Text('postPublish').tr(),
|
||||||
),
|
),
|
||||||
@ -549,6 +408,8 @@ class _PostEditorScreenState extends State<PostEditorScreen> {
|
|||||||
],
|
],
|
||||||
),
|
),
|
||||||
);
|
);
|
||||||
|
},
|
||||||
|
);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -34,7 +34,7 @@ class SnPost with _$SnPost {
|
|||||||
required DateTime? lockedAt,
|
required DateTime? lockedAt,
|
||||||
required bool isDraft,
|
required bool isDraft,
|
||||||
required DateTime? publishedAt,
|
required DateTime? publishedAt,
|
||||||
required dynamic publishedUntil,
|
required DateTime? publishedUntil,
|
||||||
required int totalUpvote,
|
required int totalUpvote,
|
||||||
required int totalDownvote,
|
required int totalDownvote,
|
||||||
required int? realmId,
|
required int? realmId,
|
||||||
|
@ -45,7 +45,7 @@ mixin _$SnPost {
|
|||||||
DateTime? get lockedAt => throw _privateConstructorUsedError;
|
DateTime? get lockedAt => throw _privateConstructorUsedError;
|
||||||
bool get isDraft => throw _privateConstructorUsedError;
|
bool get isDraft => throw _privateConstructorUsedError;
|
||||||
DateTime? get publishedAt => throw _privateConstructorUsedError;
|
DateTime? get publishedAt => throw _privateConstructorUsedError;
|
||||||
dynamic get publishedUntil => throw _privateConstructorUsedError;
|
DateTime? get publishedUntil => throw _privateConstructorUsedError;
|
||||||
int get totalUpvote => throw _privateConstructorUsedError;
|
int get totalUpvote => throw _privateConstructorUsedError;
|
||||||
int get totalDownvote => throw _privateConstructorUsedError;
|
int get totalDownvote => throw _privateConstructorUsedError;
|
||||||
int? get realmId => throw _privateConstructorUsedError;
|
int? get realmId => throw _privateConstructorUsedError;
|
||||||
@ -95,7 +95,7 @@ abstract class $SnPostCopyWith<$Res> {
|
|||||||
DateTime? lockedAt,
|
DateTime? lockedAt,
|
||||||
bool isDraft,
|
bool isDraft,
|
||||||
DateTime? publishedAt,
|
DateTime? publishedAt,
|
||||||
dynamic publishedUntil,
|
DateTime? publishedUntil,
|
||||||
int totalUpvote,
|
int totalUpvote,
|
||||||
int totalDownvote,
|
int totalDownvote,
|
||||||
int? realmId,
|
int? realmId,
|
||||||
@ -264,7 +264,7 @@ class _$SnPostCopyWithImpl<$Res, $Val extends SnPost>
|
|||||||
publishedUntil: freezed == publishedUntil
|
publishedUntil: freezed == publishedUntil
|
||||||
? _value.publishedUntil
|
? _value.publishedUntil
|
||||||
: publishedUntil // ignore: cast_nullable_to_non_nullable
|
: publishedUntil // ignore: cast_nullable_to_non_nullable
|
||||||
as dynamic,
|
as DateTime?,
|
||||||
totalUpvote: null == totalUpvote
|
totalUpvote: null == totalUpvote
|
||||||
? _value.totalUpvote
|
? _value.totalUpvote
|
||||||
: totalUpvote // ignore: cast_nullable_to_non_nullable
|
: totalUpvote // ignore: cast_nullable_to_non_nullable
|
||||||
@ -368,7 +368,7 @@ abstract class _$$SnPostImplCopyWith<$Res> implements $SnPostCopyWith<$Res> {
|
|||||||
DateTime? lockedAt,
|
DateTime? lockedAt,
|
||||||
bool isDraft,
|
bool isDraft,
|
||||||
DateTime? publishedAt,
|
DateTime? publishedAt,
|
||||||
dynamic publishedUntil,
|
DateTime? publishedUntil,
|
||||||
int totalUpvote,
|
int totalUpvote,
|
||||||
int totalDownvote,
|
int totalDownvote,
|
||||||
int? realmId,
|
int? realmId,
|
||||||
@ -538,7 +538,7 @@ class __$$SnPostImplCopyWithImpl<$Res>
|
|||||||
publishedUntil: freezed == publishedUntil
|
publishedUntil: freezed == publishedUntil
|
||||||
? _value.publishedUntil
|
? _value.publishedUntil
|
||||||
: publishedUntil // ignore: cast_nullable_to_non_nullable
|
: publishedUntil // ignore: cast_nullable_to_non_nullable
|
||||||
as dynamic,
|
as DateTime?,
|
||||||
totalUpvote: null == totalUpvote
|
totalUpvote: null == totalUpvote
|
||||||
? _value.totalUpvote
|
? _value.totalUpvote
|
||||||
: totalUpvote // ignore: cast_nullable_to_non_nullable
|
: totalUpvote // ignore: cast_nullable_to_non_nullable
|
||||||
@ -690,7 +690,7 @@ class _$SnPostImpl extends _SnPost {
|
|||||||
@override
|
@override
|
||||||
final DateTime? publishedAt;
|
final DateTime? publishedAt;
|
||||||
@override
|
@override
|
||||||
final dynamic publishedUntil;
|
final DateTime? publishedUntil;
|
||||||
@override
|
@override
|
||||||
final int totalUpvote;
|
final int totalUpvote;
|
||||||
@override
|
@override
|
||||||
@ -756,8 +756,8 @@ class _$SnPostImpl extends _SnPost {
|
|||||||
(identical(other.isDraft, isDraft) || other.isDraft == isDraft) &&
|
(identical(other.isDraft, isDraft) || other.isDraft == isDraft) &&
|
||||||
(identical(other.publishedAt, publishedAt) ||
|
(identical(other.publishedAt, publishedAt) ||
|
||||||
other.publishedAt == publishedAt) &&
|
other.publishedAt == publishedAt) &&
|
||||||
const DeepCollectionEquality()
|
(identical(other.publishedUntil, publishedUntil) ||
|
||||||
.equals(other.publishedUntil, publishedUntil) &&
|
other.publishedUntil == publishedUntil) &&
|
||||||
(identical(other.totalUpvote, totalUpvote) ||
|
(identical(other.totalUpvote, totalUpvote) ||
|
||||||
other.totalUpvote == totalUpvote) &&
|
other.totalUpvote == totalUpvote) &&
|
||||||
(identical(other.totalDownvote, totalDownvote) ||
|
(identical(other.totalDownvote, totalDownvote) ||
|
||||||
@ -801,7 +801,7 @@ class _$SnPostImpl extends _SnPost {
|
|||||||
lockedAt,
|
lockedAt,
|
||||||
isDraft,
|
isDraft,
|
||||||
publishedAt,
|
publishedAt,
|
||||||
const DeepCollectionEquality().hash(publishedUntil),
|
publishedUntil,
|
||||||
totalUpvote,
|
totalUpvote,
|
||||||
totalDownvote,
|
totalDownvote,
|
||||||
realmId,
|
realmId,
|
||||||
@ -855,7 +855,7 @@ abstract class _SnPost extends SnPost {
|
|||||||
required final DateTime? lockedAt,
|
required final DateTime? lockedAt,
|
||||||
required final bool isDraft,
|
required final bool isDraft,
|
||||||
required final DateTime? publishedAt,
|
required final DateTime? publishedAt,
|
||||||
required final dynamic publishedUntil,
|
required final DateTime? publishedUntil,
|
||||||
required final int totalUpvote,
|
required final int totalUpvote,
|
||||||
required final int totalDownvote,
|
required final int totalDownvote,
|
||||||
required final int? realmId,
|
required final int? realmId,
|
||||||
@ -919,7 +919,7 @@ abstract class _SnPost extends SnPost {
|
|||||||
@override
|
@override
|
||||||
DateTime? get publishedAt;
|
DateTime? get publishedAt;
|
||||||
@override
|
@override
|
||||||
dynamic get publishedUntil;
|
DateTime? get publishedUntil;
|
||||||
@override
|
@override
|
||||||
int get totalUpvote;
|
int get totalUpvote;
|
||||||
@override
|
@override
|
||||||
|
@ -42,7 +42,9 @@ _$SnPostImpl _$$SnPostImplFromJson(Map<String, dynamic> json) => _$SnPostImpl(
|
|||||||
publishedAt: json['published_at'] == null
|
publishedAt: json['published_at'] == null
|
||||||
? null
|
? null
|
||||||
: DateTime.parse(json['published_at'] as String),
|
: DateTime.parse(json['published_at'] as String),
|
||||||
publishedUntil: json['published_until'],
|
publishedUntil: json['published_until'] == null
|
||||||
|
? null
|
||||||
|
: DateTime.parse(json['published_until'] as String),
|
||||||
totalUpvote: (json['total_upvote'] as num).toInt(),
|
totalUpvote: (json['total_upvote'] as num).toInt(),
|
||||||
totalDownvote: (json['total_downvote'] as num).toInt(),
|
totalDownvote: (json['total_downvote'] as num).toInt(),
|
||||||
realmId: (json['realm_id'] as num?)?.toInt(),
|
realmId: (json['realm_id'] as num?)?.toInt(),
|
||||||
@ -83,7 +85,7 @@ Map<String, dynamic> _$$SnPostImplToJson(_$SnPostImpl instance) =>
|
|||||||
'locked_at': instance.lockedAt?.toIso8601String(),
|
'locked_at': instance.lockedAt?.toIso8601String(),
|
||||||
'is_draft': instance.isDraft,
|
'is_draft': instance.isDraft,
|
||||||
'published_at': instance.publishedAt?.toIso8601String(),
|
'published_at': instance.publishedAt?.toIso8601String(),
|
||||||
'published_until': instance.publishedUntil,
|
'published_until': instance.publishedUntil?.toIso8601String(),
|
||||||
'total_upvote': instance.totalUpvote,
|
'total_upvote': instance.totalUpvote,
|
||||||
'total_downvote': instance.totalDownvote,
|
'total_downvote': instance.totalDownvote,
|
||||||
'realm_id': instance.realmId,
|
'realm_id': instance.realmId,
|
||||||
|
@ -35,6 +35,7 @@ class AccountImage extends StatelessWidget {
|
|||||||
UniversalImage.provider(url),
|
UniversalImage.provider(url),
|
||||||
width: ((radius ?? 20) * devicePixelRatio * 2).round(),
|
width: ((radius ?? 20) * devicePixelRatio * 2).round(),
|
||||||
height: ((radius ?? 20) * devicePixelRatio * 2).round(),
|
height: ((radius ?? 20) * devicePixelRatio * 2).round(),
|
||||||
|
policy: ResizeImagePolicy.fit,
|
||||||
)
|
)
|
||||||
: null,
|
: null,
|
||||||
child: (content?.isEmpty ?? true)
|
child: (content?.isEmpty ?? true)
|
||||||
|
@ -1,15 +1,13 @@
|
|||||||
import 'dart:io';
|
|
||||||
|
|
||||||
import 'package:cross_file/cross_file.dart';
|
|
||||||
import 'package:easy_localization/easy_localization.dart';
|
import 'package:easy_localization/easy_localization.dart';
|
||||||
import 'package:flutter/foundation.dart';
|
|
||||||
import 'package:flutter/material.dart';
|
import 'package:flutter/material.dart';
|
||||||
import 'package:flutter_context_menu/flutter_context_menu.dart';
|
import 'package:flutter_context_menu/flutter_context_menu.dart';
|
||||||
import 'package:gap/gap.dart';
|
import 'package:gap/gap.dart';
|
||||||
import 'package:material_symbols_icons/symbols.dart';
|
import 'package:material_symbols_icons/symbols.dart';
|
||||||
|
import 'package:styled_widget/styled_widget.dart';
|
||||||
|
import 'package:surface/controllers/post_write_controller.dart';
|
||||||
|
|
||||||
class PostMediaPendingList extends StatelessWidget {
|
class PostMediaPendingList extends StatelessWidget {
|
||||||
final List<XFile> data;
|
final List<PostWriteMedia> data;
|
||||||
final Function(int idx)? onRemove;
|
final Function(int idx)? onRemove;
|
||||||
const PostMediaPendingList({
|
const PostMediaPendingList({
|
||||||
super.key,
|
super.key,
|
||||||
@ -19,6 +17,8 @@ class PostMediaPendingList extends StatelessWidget {
|
|||||||
|
|
||||||
@override
|
@override
|
||||||
Widget build(BuildContext context) {
|
Widget build(BuildContext context) {
|
||||||
|
final devicePixelRatio = MediaQuery.of(context).devicePixelRatio;
|
||||||
|
|
||||||
return Container(
|
return Container(
|
||||||
constraints: const BoxConstraints(maxHeight: 120),
|
constraints: const BoxConstraints(maxHeight: 120),
|
||||||
child: ListView.separated(
|
child: ListView.separated(
|
||||||
@ -53,9 +53,25 @@ class PostMediaPendingList extends StatelessWidget {
|
|||||||
borderRadius: const BorderRadius.all(Radius.circular(8)),
|
borderRadius: const BorderRadius.all(Radius.circular(8)),
|
||||||
child: AspectRatio(
|
child: AspectRatio(
|
||||||
aspectRatio: 1,
|
aspectRatio: 1,
|
||||||
child: kIsWeb
|
child: switch (file.type) {
|
||||||
? Image.network(file.path, fit: BoxFit.cover)
|
PostWriteMediaType.image =>
|
||||||
: Image.file(File(file.path), fit: BoxFit.cover),
|
LayoutBuilder(builder: (context, constraints) {
|
||||||
|
return Image(
|
||||||
|
image: file.getImageProvider(
|
||||||
|
context,
|
||||||
|
width: (constraints.maxWidth * devicePixelRatio)
|
||||||
|
.round(),
|
||||||
|
height: (constraints.maxHeight * devicePixelRatio)
|
||||||
|
.round(),
|
||||||
|
)!,
|
||||||
|
fit: BoxFit.cover,
|
||||||
|
);
|
||||||
|
}),
|
||||||
|
_ => Container(
|
||||||
|
color: Theme.of(context).colorScheme.surface,
|
||||||
|
child: const Icon(Symbols.docs).center(),
|
||||||
|
),
|
||||||
|
},
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
|
@ -1,87 +1,130 @@
|
|||||||
import 'package:easy_localization/easy_localization.dart';
|
import 'package:easy_localization/easy_localization.dart';
|
||||||
|
import 'package:flutter/cupertino.dart';
|
||||||
import 'package:flutter/material.dart';
|
import 'package:flutter/material.dart';
|
||||||
import 'package:gap/gap.dart';
|
import 'package:gap/gap.dart';
|
||||||
import 'package:material_symbols_icons/symbols.dart';
|
import 'package:material_symbols_icons/symbols.dart';
|
||||||
import 'package:styled_widget/styled_widget.dart';
|
import 'package:styled_widget/styled_widget.dart';
|
||||||
|
import 'package:surface/controllers/post_write_controller.dart';
|
||||||
|
|
||||||
class PostMetaResult {
|
class PostMetaEditor extends StatelessWidget {
|
||||||
final String title;
|
final PostWriteController controller;
|
||||||
final String description;
|
const PostMetaEditor({super.key, required this.controller});
|
||||||
|
|
||||||
PostMetaResult({required this.title, required this.description});
|
Future<DateTime?> _selectDate(
|
||||||
}
|
BuildContext context, {
|
||||||
|
DateTime? initialDateTime,
|
||||||
class PostMetaEditor extends StatefulWidget {
|
}) async {
|
||||||
final String? initialTitle;
|
DateTime? picked;
|
||||||
final String? initialDescription;
|
await showCupertinoModalPopup(
|
||||||
const PostMetaEditor({super.key, this.initialTitle, this.initialDescription});
|
context: context,
|
||||||
|
builder: (BuildContext context) => Container(
|
||||||
@override
|
height: 216,
|
||||||
State<PostMetaEditor> createState() => _PostMetaEditorState();
|
padding: const EdgeInsets.only(top: 6.0),
|
||||||
}
|
margin: EdgeInsets.only(
|
||||||
|
bottom: MediaQuery.of(context).viewInsets.bottom,
|
||||||
class _PostMetaEditorState extends State<PostMetaEditor> {
|
),
|
||||||
final TextEditingController _titleController = TextEditingController();
|
color: Theme.of(context).colorScheme.surface,
|
||||||
final TextEditingController _descriptionController = TextEditingController();
|
child: SafeArea(
|
||||||
|
top: false,
|
||||||
void _applyChanges() {
|
child: CupertinoDatePicker(
|
||||||
Navigator.pop(
|
initialDateTime: initialDateTime,
|
||||||
context,
|
mode: CupertinoDatePickerMode.dateAndTime,
|
||||||
PostMetaResult(
|
use24hFormat: true,
|
||||||
title: _titleController.text,
|
onDateTimeChanged: (DateTime newDate) {
|
||||||
description: _descriptionController.text,
|
picked = newDate;
|
||||||
|
},
|
||||||
|
),
|
||||||
|
),
|
||||||
),
|
),
|
||||||
);
|
);
|
||||||
}
|
return picked;
|
||||||
|
|
||||||
@override
|
|
||||||
void initState() {
|
|
||||||
super.initState();
|
|
||||||
_titleController.text = widget.initialTitle ?? '';
|
|
||||||
_descriptionController.text = widget.initialDescription ?? '';
|
|
||||||
}
|
|
||||||
|
|
||||||
@override
|
|
||||||
void dispose() {
|
|
||||||
_titleController.dispose();
|
|
||||||
_descriptionController.dispose();
|
|
||||||
super.dispose();
|
|
||||||
}
|
}
|
||||||
|
|
||||||
@override
|
@override
|
||||||
Widget build(BuildContext context) {
|
Widget build(BuildContext context) {
|
||||||
|
final dateFormatter = DateFormat('y/M/d HH:mm:ss');
|
||||||
|
return ListenableBuilder(
|
||||||
|
listenable: controller,
|
||||||
|
builder: (context, _) {
|
||||||
return Column(
|
return Column(
|
||||||
children: [
|
children: [
|
||||||
TextField(
|
TextField(
|
||||||
controller: _titleController,
|
controller: controller.titleController,
|
||||||
decoration: InputDecoration(
|
decoration: InputDecoration(
|
||||||
labelText: 'fieldPostTitle'.tr(),
|
labelText: 'fieldPostTitle'.tr(),
|
||||||
border: UnderlineInputBorder(),
|
border: UnderlineInputBorder(),
|
||||||
),
|
),
|
||||||
onTapOutside: (_) => FocusManager.instance.primaryFocus?.unfocus(),
|
onTapOutside: (_) =>
|
||||||
),
|
FocusManager.instance.primaryFocus?.unfocus(),
|
||||||
const Gap(4),
|
).padding(horizontal: 24),
|
||||||
|
if (controller.mode == 'article') const Gap(4),
|
||||||
|
if (controller.mode == 'article')
|
||||||
TextField(
|
TextField(
|
||||||
controller: _descriptionController,
|
controller: controller.descriptionController,
|
||||||
maxLines: null,
|
maxLines: null,
|
||||||
decoration: InputDecoration(
|
decoration: InputDecoration(
|
||||||
labelText: 'fieldPostDescription'.tr(),
|
labelText: 'fieldPostDescription'.tr(),
|
||||||
border: UnderlineInputBorder(),
|
border: UnderlineInputBorder(),
|
||||||
),
|
),
|
||||||
onTapOutside: (_) => FocusManager.instance.primaryFocus?.unfocus(),
|
onTapOutside: (_) =>
|
||||||
),
|
FocusManager.instance.primaryFocus?.unfocus(),
|
||||||
|
).padding(horizontal: 24),
|
||||||
const Gap(12),
|
const Gap(12),
|
||||||
Row(
|
ListTile(
|
||||||
mainAxisAlignment: MainAxisAlignment.end,
|
leading: const Icon(Symbols.event_available),
|
||||||
children: [
|
title: Text('postPublishedAt').tr(),
|
||||||
ElevatedButton.icon(
|
subtitle: Text(
|
||||||
onPressed: _applyChanges,
|
controller.publishedAt != null
|
||||||
icon: const Icon(Symbols.save),
|
? dateFormatter.format(controller.publishedAt!)
|
||||||
label: Text('apply').tr(),
|
: 'unset'.tr(),
|
||||||
|
),
|
||||||
|
trailing: controller.publishedAt != null
|
||||||
|
? IconButton(
|
||||||
|
icon: const Icon(Symbols.cancel),
|
||||||
|
onPressed: () {
|
||||||
|
controller.setPublishedAt(null);
|
||||||
|
},
|
||||||
|
)
|
||||||
|
: null,
|
||||||
|
contentPadding: const EdgeInsets.only(left: 24, right: 18),
|
||||||
|
onTap: () {
|
||||||
|
_selectDate(
|
||||||
|
context,
|
||||||
|
initialDateTime: controller.publishedAt,
|
||||||
|
).then((value) {
|
||||||
|
controller.setPublishedAt(value);
|
||||||
|
});
|
||||||
|
},
|
||||||
|
),
|
||||||
|
ListTile(
|
||||||
|
leading: const Icon(Symbols.event_busy),
|
||||||
|
title: Text('postPublishedUntil').tr(),
|
||||||
|
subtitle: Text(
|
||||||
|
controller.publishedUntil != null
|
||||||
|
? dateFormatter.format(controller.publishedUntil!)
|
||||||
|
: 'unset'.tr(),
|
||||||
|
),
|
||||||
|
trailing: controller.publishedUntil != null
|
||||||
|
? IconButton(
|
||||||
|
icon: const Icon(Symbols.cancel),
|
||||||
|
onPressed: () {
|
||||||
|
controller.setPublishedUntil(null);
|
||||||
|
},
|
||||||
|
)
|
||||||
|
: null,
|
||||||
|
contentPadding: const EdgeInsets.only(left: 24, right: 18),
|
||||||
|
onTap: () {
|
||||||
|
_selectDate(
|
||||||
|
context,
|
||||||
|
initialDateTime: controller.publishedUntil,
|
||||||
|
).then((value) {
|
||||||
|
controller.setPublishedUntil(value);
|
||||||
|
});
|
||||||
|
},
|
||||||
),
|
),
|
||||||
],
|
],
|
||||||
)
|
).padding(vertical: 8);
|
||||||
],
|
},
|
||||||
).padding(horizontal: 24, vertical: 8);
|
);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
10
lib/widgets/post/post_mini_editor.dart
Normal file
10
lib/widgets/post/post_mini_editor.dart
Normal file
@ -0,0 +1,10 @@
|
|||||||
|
import 'package:flutter/widgets.dart';
|
||||||
|
|
||||||
|
class PostMiniEditor extends StatelessWidget {
|
||||||
|
const PostMiniEditor({super.key});
|
||||||
|
|
||||||
|
@override
|
||||||
|
Widget build(BuildContext context) {
|
||||||
|
return const Placeholder();
|
||||||
|
}
|
||||||
|
}
|
44
pubspec.lock
44
pubspec.lock
@ -5,23 +5,23 @@ packages:
|
|||||||
dependency: transitive
|
dependency: transitive
|
||||||
description:
|
description:
|
||||||
name: _fe_analyzer_shared
|
name: _fe_analyzer_shared
|
||||||
sha256: "16e298750b6d0af7ce8a3ba7c18c69c3785d11b15ec83f6dcd0ad2a0009b3cab"
|
sha256: f256b0c0ba6c7577c15e2e4e114755640a875e885099367bf6e012b19314c834
|
||||||
url: "https://pub.dev"
|
url: "https://pub.dev"
|
||||||
source: hosted
|
source: hosted
|
||||||
version: "76.0.0"
|
version: "72.0.0"
|
||||||
_macros:
|
_macros:
|
||||||
dependency: transitive
|
dependency: transitive
|
||||||
description: dart
|
description: dart
|
||||||
source: sdk
|
source: sdk
|
||||||
version: "0.3.3"
|
version: "0.3.2"
|
||||||
analyzer:
|
analyzer:
|
||||||
dependency: transitive
|
dependency: transitive
|
||||||
description:
|
description:
|
||||||
name: analyzer
|
name: analyzer
|
||||||
sha256: "1f14db053a8c23e260789e9b0980fa27f2680dd640932cae5e1137cce0e46e1e"
|
sha256: b652861553cd3990d8ed361f7979dc6d7053a9ac8843fa73820ab68ce5410139
|
||||||
url: "https://pub.dev"
|
url: "https://pub.dev"
|
||||||
source: hosted
|
source: hosted
|
||||||
version: "6.11.0"
|
version: "6.7.0"
|
||||||
animations:
|
animations:
|
||||||
dependency: "direct main"
|
dependency: "direct main"
|
||||||
description:
|
description:
|
||||||
@ -202,10 +202,10 @@ packages:
|
|||||||
dependency: "direct main"
|
dependency: "direct main"
|
||||||
description:
|
description:
|
||||||
name: collection
|
name: collection
|
||||||
sha256: a1ace0a119f20aabc852d165077c036cd864315bd99b7eaa10a60100341941bf
|
sha256: ee67cb0715911d28db6bf4af1026078bd6f0128b07a5f66fb2ed94ec6783c09a
|
||||||
url: "https://pub.dev"
|
url: "https://pub.dev"
|
||||||
source: hosted
|
source: hosted
|
||||||
version: "1.19.0"
|
version: "1.18.0"
|
||||||
connectivity_plus:
|
connectivity_plus:
|
||||||
dependency: transitive
|
dependency: transitive
|
||||||
description:
|
description:
|
||||||
@ -790,18 +790,18 @@ packages:
|
|||||||
dependency: transitive
|
dependency: transitive
|
||||||
description:
|
description:
|
||||||
name: leak_tracker
|
name: leak_tracker
|
||||||
sha256: c35baad643ba394b40aac41080300150a4f08fd0fd6a10378f8f7c6bc161acec
|
sha256: "3f87a60e8c63aecc975dda1ceedbc8f24de75f09e4856ea27daf8958f2f0ce05"
|
||||||
url: "https://pub.dev"
|
url: "https://pub.dev"
|
||||||
source: hosted
|
source: hosted
|
||||||
version: "10.0.8"
|
version: "10.0.5"
|
||||||
leak_tracker_flutter_testing:
|
leak_tracker_flutter_testing:
|
||||||
dependency: transitive
|
dependency: transitive
|
||||||
description:
|
description:
|
||||||
name: leak_tracker_flutter_testing
|
name: leak_tracker_flutter_testing
|
||||||
sha256: f8b613e7e6a13ec79cfdc0e97638fddb3ab848452eff057653abd3edba760573
|
sha256: "932549fb305594d82d7183ecd9fa93463e9914e1b67cacc34bc40906594a1806"
|
||||||
url: "https://pub.dev"
|
url: "https://pub.dev"
|
||||||
source: hosted
|
source: hosted
|
||||||
version: "3.0.9"
|
version: "3.0.5"
|
||||||
leak_tracker_testing:
|
leak_tracker_testing:
|
||||||
dependency: transitive
|
dependency: transitive
|
||||||
description:
|
description:
|
||||||
@ -838,10 +838,10 @@ packages:
|
|||||||
dependency: transitive
|
dependency: transitive
|
||||||
description:
|
description:
|
||||||
name: macros
|
name: macros
|
||||||
sha256: "1d9e801cd66f7ea3663c45fc708450db1fa57f988142c64289142c9b7ee80656"
|
sha256: "0acaed5d6b7eab89f63350bccd82119e6c602df0f391260d0e32b5e23db79536"
|
||||||
url: "https://pub.dev"
|
url: "https://pub.dev"
|
||||||
source: hosted
|
source: hosted
|
||||||
version: "0.1.3-main.0"
|
version: "0.1.2-main.4"
|
||||||
markdown:
|
markdown:
|
||||||
dependency: "direct main"
|
dependency: "direct main"
|
||||||
description:
|
description:
|
||||||
@ -1150,7 +1150,7 @@ packages:
|
|||||||
dependency: transitive
|
dependency: transitive
|
||||||
description: flutter
|
description: flutter
|
||||||
source: sdk
|
source: sdk
|
||||||
version: "0.0.0"
|
version: "0.0.99"
|
||||||
source_gen:
|
source_gen:
|
||||||
dependency: transitive
|
dependency: transitive
|
||||||
description:
|
description:
|
||||||
@ -1227,10 +1227,10 @@ packages:
|
|||||||
dependency: transitive
|
dependency: transitive
|
||||||
description:
|
description:
|
||||||
name: stack_trace
|
name: stack_trace
|
||||||
sha256: "9f47fd3630d76be3ab26f0ee06d213679aa425996925ff3feffdec504931c377"
|
sha256: "73713990125a6d93122541237550ee3352a2d84baad52d375a4cad2eb9b7ce0b"
|
||||||
url: "https://pub.dev"
|
url: "https://pub.dev"
|
||||||
source: hosted
|
source: hosted
|
||||||
version: "1.12.0"
|
version: "1.11.1"
|
||||||
stream_channel:
|
stream_channel:
|
||||||
dependency: transitive
|
dependency: transitive
|
||||||
description:
|
description:
|
||||||
@ -1251,10 +1251,10 @@ packages:
|
|||||||
dependency: transitive
|
dependency: transitive
|
||||||
description:
|
description:
|
||||||
name: string_scanner
|
name: string_scanner
|
||||||
sha256: "688af5ed3402a4bde5b3a6c15fd768dbf2621a614950b17f04626c431ab3c4c3"
|
sha256: "556692adab6cfa87322a115640c11f13cb77b3f076ddcc5d6ae3c20242bedcde"
|
||||||
url: "https://pub.dev"
|
url: "https://pub.dev"
|
||||||
source: hosted
|
source: hosted
|
||||||
version: "1.3.0"
|
version: "1.2.0"
|
||||||
styled_widget:
|
styled_widget:
|
||||||
dependency: "direct main"
|
dependency: "direct main"
|
||||||
description:
|
description:
|
||||||
@ -1291,10 +1291,10 @@ packages:
|
|||||||
dependency: transitive
|
dependency: transitive
|
||||||
description:
|
description:
|
||||||
name: test_api
|
name: test_api
|
||||||
sha256: "664d3a9a64782fcdeb83ce9c6b39e78fd2971d4e37827b9b06c3aa1edc5e760c"
|
sha256: "5b8a98dafc4d5c4c9c72d8b31ab2b23fc13422348d2997120294d3bac86b4ddb"
|
||||||
url: "https://pub.dev"
|
url: "https://pub.dev"
|
||||||
source: hosted
|
source: hosted
|
||||||
version: "0.7.3"
|
version: "0.7.2"
|
||||||
timing:
|
timing:
|
||||||
dependency: transitive
|
dependency: transitive
|
||||||
description:
|
description:
|
||||||
@ -1411,10 +1411,10 @@ packages:
|
|||||||
dependency: transitive
|
dependency: transitive
|
||||||
description:
|
description:
|
||||||
name: vm_service
|
name: vm_service
|
||||||
sha256: f6be3ed8bd01289b34d679c2b62226f63c0e69f9fd2e50a6b3c1c729a961041b
|
sha256: "5c5f338a667b4c644744b661f309fb8080bb94b18a7e91ef1dbd343bed00ed6d"
|
||||||
url: "https://pub.dev"
|
url: "https://pub.dev"
|
||||||
source: hosted
|
source: hosted
|
||||||
version: "14.3.0"
|
version: "14.2.5"
|
||||||
watcher:
|
watcher:
|
||||||
dependency: transitive
|
dependency: transitive
|
||||||
description:
|
description:
|
||||||
|
Loading…
Reference in New Issue
Block a user