Compare commits
No commits in common. "ce3d19fb7bd89006ff7a960a0728f3d7013fe505" and "aa50561247a45f218a0bf4b21e16ae4b3e78d4dd" have entirely different histories.
ce3d19fb7b
...
aa50561247
@ -543,7 +543,6 @@
|
|||||||
"attachmentSaved": "Saved to album",
|
"attachmentSaved": "Saved to album",
|
||||||
"attachmentSavedDesktop": "Saved to Downloads folder",
|
"attachmentSavedDesktop": "Saved to Downloads folder",
|
||||||
"openInAlbum": "Open in album",
|
"openInAlbum": "Open in album",
|
||||||
"openInBrowser": "Open in browser",
|
|
||||||
"postAbuseReport": "Report Post",
|
"postAbuseReport": "Report Post",
|
||||||
"postAbuseReportDescription": "Report posts that violate our user agreement and community guidelines to help us improve the content on Solar Network. Please describe how this post violates the relevant rules. Do not include any sensitive information. We will process your report within 24 hours.",
|
"postAbuseReportDescription": "Report posts that violate our user agreement and community guidelines to help us improve the content on Solar Network. Please describe how this post violates the relevant rules. Do not include any sensitive information. We will process your report within 24 hours.",
|
||||||
"abuseReport": "Abuse Report",
|
"abuseReport": "Abuse Report",
|
||||||
@ -955,15 +954,5 @@
|
|||||||
"attachmentEditorUploadHint": "This attachment is uploaded.",
|
"attachmentEditorUploadHint": "This attachment is uploaded.",
|
||||||
"attachmentRating": "Rating",
|
"attachmentRating": "Rating",
|
||||||
"fieldAttachmentRating": "Content Rating",
|
"fieldAttachmentRating": "Content Rating",
|
||||||
"fieldAttachmentQuality": "Quality Rating",
|
"fieldAttachmentQuality": "Quality Rating"
|
||||||
"attachmentReferenceLink": "Use external attachment",
|
|
||||||
"fieldAttachmentReferenceLink": "Reference Link",
|
|
||||||
"attachmentReferenceLinkDescription": "It will be used as the source file of the attachment. The link needs to allow cross-origin access.",
|
|
||||||
"fieldAttachmentMimetype": "Mimetype",
|
|
||||||
"postVideoLive": "Live Stream",
|
|
||||||
"postVideoLiveDescription": "This is a live video, you can embed the source site by yourself.",
|
|
||||||
"postVideoRendererWeb": "WebView Rendering",
|
|
||||||
"postVideoRendererWebDescription": "Use WebView to render the content",
|
|
||||||
"fieldPostVideoStreamUrl": "Live Stream URL",
|
|
||||||
"fieldPostVideoStreamUrlDescription": "Live stream URL, can be HLS or webpage; use webpage renderer if you want to embed the webpage."
|
|
||||||
}
|
}
|
||||||
|
@ -541,7 +541,6 @@
|
|||||||
"attachmentSaved": "已保存到相册",
|
"attachmentSaved": "已保存到相册",
|
||||||
"attachmentSavedDesktop": "已保存到下载目录",
|
"attachmentSavedDesktop": "已保存到下载目录",
|
||||||
"openInAlbum": "在相册中打开",
|
"openInAlbum": "在相册中打开",
|
||||||
"openInBrowser": "在浏览器中打开",
|
|
||||||
"postAbuseReport": "检举帖子",
|
"postAbuseReport": "检举帖子",
|
||||||
"postAbuseReportDescription": "检举不符合我们用户协议以及社区准则的帖子,来帮助我们更好的维护 Solar Network 上的内容。请在下面描述该帖子如何违反我么的相关规定。请勿填写任何敏感信息。我们将会在 24 小时内处理您的检举。",
|
"postAbuseReportDescription": "检举不符合我们用户协议以及社区准则的帖子,来帮助我们更好的维护 Solar Network 上的内容。请在下面描述该帖子如何违反我么的相关规定。请勿填写任何敏感信息。我们将会在 24 小时内处理您的检举。",
|
||||||
"abuseReport": "检举",
|
"abuseReport": "检举",
|
||||||
@ -952,15 +951,5 @@
|
|||||||
"attachmentEditorUploadHint": "该附件已上传。",
|
"attachmentEditorUploadHint": "该附件已上传。",
|
||||||
"attachmentRating": "评级",
|
"attachmentRating": "评级",
|
||||||
"fieldAttachmentRating": "内容分级",
|
"fieldAttachmentRating": "内容分级",
|
||||||
"fieldAttachmentQuality": "质量评分",
|
"fieldAttachmentQuality": "质量评分"
|
||||||
"attachmentReferenceLink": "引用外部附件",
|
|
||||||
"fieldAttachmentReferenceLink": "引用连接",
|
|
||||||
"attachmentReferenceLinkDescription": "作为附件的源文件。需要链接允许跨域访问。",
|
|
||||||
"fieldAttachmentMimetype": "文件类型",
|
|
||||||
"postVideoLive": "直播",
|
|
||||||
"postVideoLiveDescription": "这是一条直播影片,允许用户自行嵌入源站。",
|
|
||||||
"postVideoRendererWeb": "网页渲染器",
|
|
||||||
"postVideoRendererWebDescription": "使用 WebView 渲染内容。",
|
|
||||||
"fieldPostVideoStreamUrl": "直播流地址",
|
|
||||||
"fieldPostVideoStreamUrlDescription": "直播流地址,可以为 HLS,或者网页;使用网页嵌入请启用网页渲染器。"
|
|
||||||
}
|
}
|
||||||
|
@ -219,8 +219,6 @@ class PostWriteController extends ChangeNotifier {
|
|||||||
List<PostWriteMedia> attachments = List.empty(growable: true);
|
List<PostWriteMedia> attachments = List.empty(growable: true);
|
||||||
DateTime? publishedAt, publishedUntil;
|
DateTime? publishedAt, publishedUntil;
|
||||||
SnAttachment? videoAttachment;
|
SnAttachment? videoAttachment;
|
||||||
String videoUrl = '';
|
|
||||||
bool videoLive = false;
|
|
||||||
SnPoll? poll;
|
SnPoll? poll;
|
||||||
|
|
||||||
Future<void> fetchRelatedPost(
|
Future<void> fetchRelatedPost(
|
||||||
@ -447,9 +445,8 @@ class PostWriteController extends ChangeNotifier {
|
|||||||
titleController.text = data['title'] ?? '';
|
titleController.text = data['title'] ?? '';
|
||||||
descriptionController.text = data['description'] ?? '';
|
descriptionController.text = data['description'] ?? '';
|
||||||
rewardController.text = data['reward']?.toString() ?? '';
|
rewardController.text = data['reward']?.toString() ?? '';
|
||||||
if (data['thumbnail'] != null) {
|
if (data['thumbnail'] != null)
|
||||||
thumbnail = PostWriteMedia(SnAttachment.fromJson(data['thumbnail']));
|
thumbnail = PostWriteMedia(SnAttachment.fromJson(data['thumbnail']));
|
||||||
}
|
|
||||||
attachments.addAll(data['attachments']
|
attachments.addAll(data['attachments']
|
||||||
.map((ele) => PostWriteMedia(SnAttachment.fromJson(ele)))
|
.map((ele) => PostWriteMedia(SnAttachment.fromJson(ele)))
|
||||||
.cast<PostWriteMedia>());
|
.cast<PostWriteMedia>());
|
||||||
@ -458,12 +455,10 @@ class PostWriteController extends ChangeNotifier {
|
|||||||
visibility = data['visibility'];
|
visibility = data['visibility'];
|
||||||
visibleUsers = List.from(data['visible_users_list'] ?? []);
|
visibleUsers = List.from(data['visible_users_list'] ?? []);
|
||||||
invisibleUsers = List.from(data['invisible_users_list'] ?? []);
|
invisibleUsers = List.from(data['invisible_users_list'] ?? []);
|
||||||
if (data['published_at'] != null) {
|
if (data['published_at'] != null)
|
||||||
publishedAt = DateTime.tryParse(data['published_at'])?.toLocal();
|
publishedAt = DateTime.tryParse(data['published_at'])?.toLocal();
|
||||||
}
|
if (data['published_until'] != null)
|
||||||
if (data['published_until'] != null) {
|
|
||||||
publishedUntil = DateTime.tryParse(data['published_until'])?.toLocal();
|
publishedUntil = DateTime.tryParse(data['published_until'])?.toLocal();
|
||||||
}
|
|
||||||
replyingPost =
|
replyingPost =
|
||||||
data['reply_to'] != null ? SnPost.fromJson(data['reply_to']) : null;
|
data['reply_to'] != null ? SnPost.fromJson(data['reply_to']) : null;
|
||||||
repostingPost =
|
repostingPost =
|
||||||
@ -600,8 +595,7 @@ class PostWriteController extends ChangeNotifier {
|
|||||||
if (replyingPost != null) 'reply_to': replyingPost!.id,
|
if (replyingPost != null) 'reply_to': replyingPost!.id,
|
||||||
if (repostingPost != null) 'repost_to': repostingPost!.id,
|
if (repostingPost != null) 'repost_to': repostingPost!.id,
|
||||||
if (reward != null) 'reward': reward,
|
if (reward != null) 'reward': reward,
|
||||||
if (videoAttachment != null || videoUrl.isNotEmpty)
|
if (videoAttachment != null) 'video': videoAttachment!.rid,
|
||||||
'video': videoUrl.isNotEmpty ? videoUrl : videoAttachment!.rid,
|
|
||||||
if (poll != null) 'poll': poll!.id,
|
if (poll != null) 'poll': poll!.id,
|
||||||
if (realm != null) 'realm': realm!.id,
|
if (realm != null) 'realm': realm!.id,
|
||||||
'is_draft': saveAsDraft,
|
'is_draft': saveAsDraft,
|
||||||
@ -744,15 +738,6 @@ class PostWriteController extends ChangeNotifier {
|
|||||||
notifyListeners();
|
notifyListeners();
|
||||||
}
|
}
|
||||||
|
|
||||||
void setVideoUrl(String value) {
|
|
||||||
videoUrl = value;
|
|
||||||
}
|
|
||||||
|
|
||||||
void setVideoLive(bool value) {
|
|
||||||
videoLive = value;
|
|
||||||
notifyListeners();
|
|
||||||
}
|
|
||||||
|
|
||||||
void setPoll(SnPoll? value) {
|
void setPoll(SnPoll? value) {
|
||||||
poll = value;
|
poll = value;
|
||||||
notifyListeners();
|
notifyListeners();
|
||||||
|
@ -120,25 +120,6 @@ class SnAttachmentProvider {
|
|||||||
'webp': 'image/webp',
|
'webp': 'image/webp',
|
||||||
};
|
};
|
||||||
|
|
||||||
Future<SnAttachment> createWithReferenceLink(
|
|
||||||
String url,
|
|
||||||
String pool,
|
|
||||||
Map<String, dynamic>? metadata, {
|
|
||||||
String? mimetype,
|
|
||||||
}) async {
|
|
||||||
final resp = await _sn.client.post(
|
|
||||||
'/cgi/uc/attachments/referenced',
|
|
||||||
data: {
|
|
||||||
'url': url,
|
|
||||||
'pool': pool,
|
|
||||||
'metadata': metadata,
|
|
||||||
if (mimetype != null) 'mimetype': mimetype,
|
|
||||||
},
|
|
||||||
);
|
|
||||||
|
|
||||||
return SnAttachment.fromJson(resp.data);
|
|
||||||
}
|
|
||||||
|
|
||||||
Future<SnAttachment> directUploadOne(
|
Future<SnAttachment> directUploadOne(
|
||||||
Uint8List data,
|
Uint8List data,
|
||||||
String filename,
|
String filename,
|
||||||
|
@ -6,6 +6,7 @@ import 'package:flutter/foundation.dart';
|
|||||||
import 'package:flutter/gestures.dart';
|
import 'package:flutter/gestures.dart';
|
||||||
import 'package:flutter/material.dart';
|
import 'package:flutter/material.dart';
|
||||||
import 'package:flutter/services.dart';
|
import 'package:flutter/services.dart';
|
||||||
|
import 'package:flutter_context_menu/flutter_context_menu.dart';
|
||||||
import 'package:gap/gap.dart';
|
import 'package:gap/gap.dart';
|
||||||
import 'package:go_router/go_router.dart';
|
import 'package:go_router/go_router.dart';
|
||||||
import 'package:hotkey_manager/hotkey_manager.dart';
|
import 'package:hotkey_manager/hotkey_manager.dart';
|
||||||
@ -15,6 +16,7 @@ import 'package:responsive_framework/responsive_framework.dart';
|
|||||||
import 'package:styled_widget/styled_widget.dart';
|
import 'package:styled_widget/styled_widget.dart';
|
||||||
import 'package:surface/controllers/post_write_controller.dart';
|
import 'package:surface/controllers/post_write_controller.dart';
|
||||||
import 'package:surface/providers/config.dart';
|
import 'package:surface/providers/config.dart';
|
||||||
|
import 'package:surface/providers/sn_attachment.dart';
|
||||||
import 'package:surface/providers/sn_network.dart';
|
import 'package:surface/providers/sn_network.dart';
|
||||||
import 'package:surface/providers/sn_realm.dart';
|
import 'package:surface/providers/sn_realm.dart';
|
||||||
import 'package:surface/types/attachment.dart';
|
import 'package:surface/types/attachment.dart';
|
||||||
@ -23,7 +25,8 @@ import 'package:surface/types/realm.dart';
|
|||||||
import 'package:surface/widgets/account/account_image.dart';
|
import 'package:surface/widgets/account/account_image.dart';
|
||||||
import 'package:surface/widgets/attachment/attachment_input.dart';
|
import 'package:surface/widgets/attachment/attachment_input.dart';
|
||||||
import 'package:surface/widgets/attachment/attachment_item.dart';
|
import 'package:surface/widgets/attachment/attachment_item.dart';
|
||||||
import 'package:surface/widgets/attachment/pending_attachment_actions.dart';
|
import 'package:surface/widgets/attachment/pending_attachment_alt.dart';
|
||||||
|
import 'package:surface/widgets/attachment/pending_attachment_boost.dart';
|
||||||
import 'package:surface/widgets/loading_indicator.dart';
|
import 'package:surface/widgets/loading_indicator.dart';
|
||||||
import 'package:surface/widgets/markdown_content.dart';
|
import 'package:surface/widgets/markdown_content.dart';
|
||||||
import 'package:surface/widgets/navigation/app_scaffold.dart';
|
import 'package:surface/widgets/navigation/app_scaffold.dart';
|
||||||
@ -1105,7 +1108,7 @@ class _PostQuestionEditor extends StatelessWidget {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
class _PostVideoEditor extends StatefulWidget {
|
class _PostVideoEditor extends StatelessWidget {
|
||||||
final PostWriteController controller;
|
final PostWriteController controller;
|
||||||
final Function? onTapPublisher;
|
final Function? onTapPublisher;
|
||||||
final Function? onTapRealm;
|
final Function? onTapRealm;
|
||||||
@ -1113,16 +1116,7 @@ class _PostVideoEditor extends StatefulWidget {
|
|||||||
const _PostVideoEditor(
|
const _PostVideoEditor(
|
||||||
{required this.controller, this.onTapPublisher, this.onTapRealm});
|
{required this.controller, this.onTapPublisher, this.onTapRealm});
|
||||||
|
|
||||||
@override
|
void _selectVideo(BuildContext context) async {
|
||||||
State<_PostVideoEditor> createState() => _PostVideoEditorState();
|
|
||||||
}
|
|
||||||
|
|
||||||
class _PostVideoEditorState extends State<_PostVideoEditor> {
|
|
||||||
String? _renderer;
|
|
||||||
|
|
||||||
final TextEditingController _streamUrlController = TextEditingController();
|
|
||||||
|
|
||||||
void _selectVideo() async {
|
|
||||||
final video = await showDialog<SnAttachment?>(
|
final video = await showDialog<SnAttachment?>(
|
||||||
context: context,
|
context: context,
|
||||||
builder: (context) => AttachmentInputDialog(
|
builder: (context) => AttachmentInputDialog(
|
||||||
@ -1133,25 +1127,78 @@ class _PostVideoEditorState extends State<_PostVideoEditor> {
|
|||||||
);
|
);
|
||||||
if (!context.mounted) return;
|
if (!context.mounted) return;
|
||||||
if (video == null) return;
|
if (video == null) return;
|
||||||
widget.controller.setVideoAttachment(video);
|
controller.setVideoAttachment(video);
|
||||||
}
|
}
|
||||||
|
|
||||||
@override
|
void _setAlt(BuildContext context) async {
|
||||||
void initState() {
|
if (controller.videoAttachment == null) return;
|
||||||
_streamUrlController.addListener(() {
|
|
||||||
if (_streamUrlController.text.isEmpty) {
|
final result = await showDialog<SnAttachment?>(
|
||||||
widget.controller.setVideoUrl('');
|
context: context,
|
||||||
} else {
|
builder: (context) => PendingAttachmentAltDialog(
|
||||||
widget.controller.setVideoUrl(_streamUrlController.text);
|
media: PostWriteMedia(controller.videoAttachment)),
|
||||||
}
|
);
|
||||||
});
|
if (result == null) return;
|
||||||
super.initState();
|
|
||||||
|
controller.setVideoAttachment(result);
|
||||||
}
|
}
|
||||||
|
|
||||||
@override
|
Future<void> _createBoost(BuildContext context) async {
|
||||||
void dispose() {
|
if (controller.videoAttachment == null) return;
|
||||||
_streamUrlController.dispose();
|
|
||||||
super.dispose();
|
final result = await showDialog<SnAttachmentBoost?>(
|
||||||
|
context: context,
|
||||||
|
builder: (context) => PendingAttachmentBoostDialog(
|
||||||
|
media: PostWriteMedia(controller.videoAttachment)),
|
||||||
|
);
|
||||||
|
if (result == null) return;
|
||||||
|
|
||||||
|
final newAttach = controller.videoAttachment!.copyWith(
|
||||||
|
boosts: [...controller.videoAttachment!.boosts, result],
|
||||||
|
);
|
||||||
|
|
||||||
|
controller.setVideoAttachment(newAttach);
|
||||||
|
}
|
||||||
|
|
||||||
|
void _setThumbnail(BuildContext context) async {
|
||||||
|
if (controller.videoAttachment == null) return;
|
||||||
|
|
||||||
|
final thumbnail = await showDialog<SnAttachment?>(
|
||||||
|
context: context,
|
||||||
|
builder: (context) => AttachmentInputDialog(
|
||||||
|
title: 'attachmentSetThumbnail'.tr(),
|
||||||
|
pool: 'interactive',
|
||||||
|
analyzeNow: true,
|
||||||
|
),
|
||||||
|
);
|
||||||
|
if (thumbnail == null) return;
|
||||||
|
if (!context.mounted) return;
|
||||||
|
|
||||||
|
try {
|
||||||
|
final attach = context.read<SnAttachmentProvider>();
|
||||||
|
final newAttach = await attach.updateOne(
|
||||||
|
controller.videoAttachment!,
|
||||||
|
thumbnailId: thumbnail.id,
|
||||||
|
);
|
||||||
|
controller.setVideoAttachment(newAttach);
|
||||||
|
} catch (err) {
|
||||||
|
if (!context.mounted) return;
|
||||||
|
context.showErrorDialog(err);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
Future<void> _deleteAttachment(BuildContext context) async {
|
||||||
|
if (controller.videoAttachment == null) return;
|
||||||
|
|
||||||
|
try {
|
||||||
|
final sn = context.read<SnNetworkProvider>();
|
||||||
|
await sn.client
|
||||||
|
.delete('/cgi/uc/attachments/${controller.videoAttachment!.id}');
|
||||||
|
controller.setVideoAttachment(null);
|
||||||
|
} catch (err) {
|
||||||
|
if (!context.mounted) return;
|
||||||
|
context.showErrorDialog(err);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@override
|
@override
|
||||||
@ -1169,10 +1216,10 @@ class _PostVideoEditorState extends State<_PostVideoEditor> {
|
|||||||
borderRadius: const BorderRadius.all(Radius.circular(24)),
|
borderRadius: const BorderRadius.all(Radius.circular(24)),
|
||||||
child: GestureDetector(
|
child: GestureDetector(
|
||||||
onTap: () {
|
onTap: () {
|
||||||
widget.onTapPublisher?.call();
|
onTapPublisher?.call();
|
||||||
},
|
},
|
||||||
child: AccountImage(
|
child: AccountImage(
|
||||||
content: widget.controller.publisher?.avatar,
|
content: controller.publisher?.avatar,
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
@ -1182,10 +1229,10 @@ class _PostVideoEditorState extends State<_PostVideoEditor> {
|
|||||||
borderRadius: const BorderRadius.all(Radius.circular(24)),
|
borderRadius: const BorderRadius.all(Radius.circular(24)),
|
||||||
child: GestureDetector(
|
child: GestureDetector(
|
||||||
onTap: () {
|
onTap: () {
|
||||||
widget.onTapRealm?.call();
|
onTapRealm?.call();
|
||||||
},
|
},
|
||||||
child: AccountImage(
|
child: AccountImage(
|
||||||
content: widget.controller.realm?.avatar,
|
content: controller.realm?.avatar,
|
||||||
fallbackWidget: const Icon(Symbols.globe, size: 20),
|
fallbackWidget: const Icon(Symbols.globe, size: 20),
|
||||||
radius: 14,
|
radius: 14,
|
||||||
),
|
),
|
||||||
@ -1198,7 +1245,7 @@ class _PostVideoEditorState extends State<_PostVideoEditor> {
|
|||||||
children: [
|
children: [
|
||||||
const Gap(6),
|
const Gap(6),
|
||||||
TextField(
|
TextField(
|
||||||
controller: widget.controller.titleController,
|
controller: controller.titleController,
|
||||||
decoration: InputDecoration.collapsed(
|
decoration: InputDecoration.collapsed(
|
||||||
hintText: 'fieldPostTitle'.tr(),
|
hintText: 'fieldPostTitle'.tr(),
|
||||||
border: InputBorder.none,
|
border: InputBorder.none,
|
||||||
@ -1209,7 +1256,7 @@ class _PostVideoEditorState extends State<_PostVideoEditor> {
|
|||||||
).padding(horizontal: 16),
|
).padding(horizontal: 16),
|
||||||
const Gap(8),
|
const Gap(8),
|
||||||
TextField(
|
TextField(
|
||||||
controller: widget.controller.descriptionController,
|
controller: controller.descriptionController,
|
||||||
decoration: InputDecoration.collapsed(
|
decoration: InputDecoration.collapsed(
|
||||||
hintText: 'fieldPostDescription'.tr(),
|
hintText: 'fieldPostDescription'.tr(),
|
||||||
border: InputBorder.none,
|
border: InputBorder.none,
|
||||||
@ -1221,50 +1268,66 @@ class _PostVideoEditorState extends State<_PostVideoEditor> {
|
|||||||
FocusManager.instance.primaryFocus?.unfocus(),
|
FocusManager.instance.primaryFocus?.unfocus(),
|
||||||
).padding(horizontal: 16),
|
).padding(horizontal: 16),
|
||||||
const Gap(12),
|
const Gap(12),
|
||||||
if (widget.controller.videoLive)
|
|
||||||
TextField(
|
|
||||||
controller: _streamUrlController,
|
|
||||||
decoration: InputDecoration(
|
|
||||||
labelText: 'fieldPostVideoStreamUrl'.tr(),
|
|
||||||
helperText: 'fieldPostVideoStreamUrlDescription'.tr(),
|
|
||||||
border: OutlineInputBorder(),
|
|
||||||
isDense: true,
|
|
||||||
),
|
|
||||||
onTapOutside: (_) =>
|
|
||||||
FocusManager.instance.primaryFocus?.unfocus(),
|
|
||||||
).padding(horizontal: 12)
|
|
||||||
else
|
|
||||||
Container(
|
Container(
|
||||||
margin: const EdgeInsets.only(left: 16, right: 16),
|
margin: const EdgeInsets.only(left: 16, right: 16),
|
||||||
decoration: BoxDecoration(
|
decoration: BoxDecoration(
|
||||||
borderRadius: BorderRadius.circular(16),
|
borderRadius: BorderRadius.circular(16),
|
||||||
border: Border.all(color: Theme.of(context).dividerColor),
|
border: Border.all(color: Theme.of(context).dividerColor),
|
||||||
),
|
),
|
||||||
|
child: ContextMenuRegion(
|
||||||
|
contextMenu: ContextMenu(
|
||||||
|
entries: [
|
||||||
|
MenuItem(
|
||||||
|
label: 'attachmentSetAlt'.tr(),
|
||||||
|
icon: Symbols.description,
|
||||||
|
onSelected: () {
|
||||||
|
_setAlt(context);
|
||||||
|
},
|
||||||
|
),
|
||||||
|
MenuItem(
|
||||||
|
label: 'attachmentBoost'.tr(),
|
||||||
|
icon: Symbols.bolt,
|
||||||
|
onSelected: () {
|
||||||
|
_createBoost(context);
|
||||||
|
},
|
||||||
|
),
|
||||||
|
MenuItem(
|
||||||
|
label: 'attachmentSetThumbnail'.tr(),
|
||||||
|
icon: Symbols.image,
|
||||||
|
onSelected: () {
|
||||||
|
_setThumbnail(context);
|
||||||
|
},
|
||||||
|
),
|
||||||
|
MenuItem(
|
||||||
|
label: 'attachmentCopyRandomId'.tr(),
|
||||||
|
icon: Symbols.content_copy,
|
||||||
|
onSelected: () {
|
||||||
|
Clipboard.setData(ClipboardData(
|
||||||
|
text: controller.videoAttachment!.rid));
|
||||||
|
},
|
||||||
|
),
|
||||||
|
MenuItem(
|
||||||
|
label: 'delete'.tr(),
|
||||||
|
icon: Symbols.delete,
|
||||||
|
onSelected: () => _deleteAttachment(context),
|
||||||
|
),
|
||||||
|
MenuItem(
|
||||||
|
label: 'unlink'.tr(),
|
||||||
|
icon: Symbols.link_off,
|
||||||
|
onSelected: () {
|
||||||
|
controller.setVideoAttachment(null);
|
||||||
|
},
|
||||||
|
),
|
||||||
|
],
|
||||||
|
),
|
||||||
child: InkWell(
|
child: InkWell(
|
||||||
borderRadius: BorderRadius.circular(16),
|
borderRadius: BorderRadius.circular(16),
|
||||||
onTap: widget.controller.videoAttachment == null
|
onTap: controller.videoAttachment == null
|
||||||
? () => _selectVideo()
|
? () => _selectVideo(context)
|
||||||
: () {
|
: null,
|
||||||
showModalBottomSheet(
|
|
||||||
context: context,
|
|
||||||
builder: (context) =>
|
|
||||||
PendingAttachmentActionSheet(
|
|
||||||
media: PostWriteMedia(
|
|
||||||
widget.controller.videoAttachment!,
|
|
||||||
),
|
|
||||||
),
|
|
||||||
).then((value) async {
|
|
||||||
if (value is PostWriteMedia) {
|
|
||||||
widget.controller
|
|
||||||
.setVideoAttachment(value.attachment);
|
|
||||||
} else if (value == false) {
|
|
||||||
widget.controller.setVideoAttachment(null);
|
|
||||||
}
|
|
||||||
});
|
|
||||||
},
|
|
||||||
child: AspectRatio(
|
child: AspectRatio(
|
||||||
aspectRatio: 16 / 9,
|
aspectRatio: 16 / 9,
|
||||||
child: widget.controller.videoAttachment == null
|
child: controller.videoAttachment == null
|
||||||
? Center(
|
? Center(
|
||||||
child: Row(
|
child: Row(
|
||||||
mainAxisSize: MainAxisSize.min,
|
mainAxisSize: MainAxisSize.min,
|
||||||
@ -1280,30 +1343,13 @@ class _PostVideoEditorState extends State<_PostVideoEditor> {
|
|||||||
: ClipRRect(
|
: ClipRRect(
|
||||||
borderRadius: BorderRadius.circular(16),
|
borderRadius: BorderRadius.circular(16),
|
||||||
child: AttachmentItem(
|
child: AttachmentItem(
|
||||||
data: widget.controller.videoAttachment!,
|
data: controller.videoAttachment!,
|
||||||
heroTag: const Uuid().v4(),
|
heroTag: const Uuid().v4(),
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
const Gap(8),
|
|
||||||
CheckboxListTile(
|
|
||||||
secondary: const Icon(Symbols.live_tv),
|
|
||||||
title: Text('postVideoLive').tr(),
|
|
||||||
subtitle: Text('postVideoLiveDescription').tr(),
|
|
||||||
value: widget.controller.videoLive,
|
|
||||||
onChanged: (value) =>
|
|
||||||
widget.controller.setVideoLive(value ?? false),
|
|
||||||
),
|
|
||||||
CheckboxListTile(
|
|
||||||
secondary: const Icon(Symbols.web),
|
|
||||||
title: Text('postVideoRendererWeb').tr(),
|
|
||||||
subtitle: Text('postVideoRendererWebDescription').tr(),
|
|
||||||
value: _renderer == 'web',
|
|
||||||
onChanged: (value) => setState(
|
|
||||||
() => _renderer = (value ?? false) ? 'web' : null,
|
|
||||||
),
|
|
||||||
),
|
),
|
||||||
],
|
],
|
||||||
),
|
),
|
||||||
|
@ -12,9 +12,6 @@ import 'package:surface/widgets/dialog.dart';
|
|||||||
class AttachmentInputDialog extends StatefulWidget {
|
class AttachmentInputDialog extends StatefulWidget {
|
||||||
final String? title;
|
final String? title;
|
||||||
final bool? analyzeNow;
|
final bool? analyzeNow;
|
||||||
final bool canPickMedia;
|
|
||||||
final bool canReferenceLink;
|
|
||||||
final bool canRandomId;
|
|
||||||
final SnMediaType? mediaType;
|
final SnMediaType? mediaType;
|
||||||
final String pool;
|
final String pool;
|
||||||
|
|
||||||
@ -24,9 +21,6 @@ class AttachmentInputDialog extends StatefulWidget {
|
|||||||
required this.pool,
|
required this.pool,
|
||||||
this.analyzeNow = false,
|
this.analyzeNow = false,
|
||||||
this.mediaType = SnMediaType.image,
|
this.mediaType = SnMediaType.image,
|
||||||
this.canPickMedia = true,
|
|
||||||
this.canReferenceLink = true,
|
|
||||||
this.canRandomId = true,
|
|
||||||
});
|
});
|
||||||
|
|
||||||
@override
|
@override
|
||||||
@ -35,8 +29,6 @@ class AttachmentInputDialog extends StatefulWidget {
|
|||||||
|
|
||||||
class _AttachmentInputDialogState extends State<AttachmentInputDialog> {
|
class _AttachmentInputDialogState extends State<AttachmentInputDialog> {
|
||||||
final _randomIdController = TextEditingController();
|
final _randomIdController = TextEditingController();
|
||||||
final _referenceLinkController = TextEditingController();
|
|
||||||
final _referenceMimetypeController = TextEditingController();
|
|
||||||
|
|
||||||
XFile? _file;
|
XFile? _file;
|
||||||
double? _progress;
|
double? _progress;
|
||||||
@ -69,22 +61,6 @@ class _AttachmentInputDialogState extends State<AttachmentInputDialog> {
|
|||||||
if (!mounted) return;
|
if (!mounted) return;
|
||||||
context.showErrorDialog(err);
|
context.showErrorDialog(err);
|
||||||
}
|
}
|
||||||
} else if (_referenceLinkController.text.isNotEmpty) {
|
|
||||||
try {
|
|
||||||
final attachment = await attach.createWithReferenceLink(
|
|
||||||
_referenceLinkController.text,
|
|
||||||
widget.pool,
|
|
||||||
null,
|
|
||||||
mimetype: _referenceMimetypeController.text.isNotEmpty
|
|
||||||
? _referenceMimetypeController.text
|
|
||||||
: null,
|
|
||||||
);
|
|
||||||
if (!mounted) return;
|
|
||||||
Navigator.pop(context, attachment);
|
|
||||||
} catch (err) {
|
|
||||||
if (!mounted) return;
|
|
||||||
context.showErrorDialog(err);
|
|
||||||
}
|
|
||||||
} else if (_file != null) {
|
} else if (_file != null) {
|
||||||
try {
|
try {
|
||||||
final place = await attach.chunkedUploadInitialize(
|
final place = await attach.chunkedUploadInitialize(
|
||||||
@ -114,15 +90,8 @@ class _AttachmentInputDialogState extends State<AttachmentInputDialog> {
|
|||||||
return AlertDialog(
|
return AlertDialog(
|
||||||
title: Text(widget.title ?? 'attachmentInputDialog'.tr()),
|
title: Text(widget.title ?? 'attachmentInputDialog'.tr()),
|
||||||
content: Column(
|
content: Column(
|
||||||
spacing: 16,
|
|
||||||
crossAxisAlignment: CrossAxisAlignment.start,
|
crossAxisAlignment: CrossAxisAlignment.start,
|
||||||
mainAxisSize: MainAxisSize.min,
|
mainAxisSize: MainAxisSize.min,
|
||||||
children: [
|
|
||||||
if (_file == null &&
|
|
||||||
_referenceLinkController.text.isEmpty &&
|
|
||||||
widget.canRandomId)
|
|
||||||
Column(
|
|
||||||
crossAxisAlignment: CrossAxisAlignment.start,
|
|
||||||
children: [
|
children: [
|
||||||
Text('attachmentInputUseRandomId').tr().fontSize(14),
|
Text('attachmentInputUseRandomId').tr().fontSize(14),
|
||||||
const Gap(8),
|
const Gap(8),
|
||||||
@ -130,67 +99,22 @@ class _AttachmentInputDialogState extends State<AttachmentInputDialog> {
|
|||||||
controller: _randomIdController,
|
controller: _randomIdController,
|
||||||
decoration: InputDecoration(
|
decoration: InputDecoration(
|
||||||
labelText: 'fieldAttachmentRandomId'.tr(),
|
labelText: 'fieldAttachmentRandomId'.tr(),
|
||||||
border: const OutlineInputBorder(),
|
border: const UnderlineInputBorder(),
|
||||||
isDense: true,
|
isDense: true,
|
||||||
),
|
),
|
||||||
onTapOutside: (_) =>
|
onTapOutside: (_) => FocusManager.instance.primaryFocus?.unfocus(),
|
||||||
FocusManager.instance.primaryFocus?.unfocus(),
|
|
||||||
),
|
),
|
||||||
],
|
const Gap(24),
|
||||||
),
|
|
||||||
if (_file == null &&
|
|
||||||
_referenceLinkController.text.isEmpty &&
|
|
||||||
widget.canReferenceLink)
|
|
||||||
Column(
|
|
||||||
crossAxisAlignment: CrossAxisAlignment.start,
|
|
||||||
children: [
|
|
||||||
Text('attachmentReferenceLink').tr().fontSize(14),
|
|
||||||
const Gap(8),
|
|
||||||
TextField(
|
|
||||||
controller: _referenceLinkController,
|
|
||||||
decoration: InputDecoration(
|
|
||||||
labelText: 'fieldAttachmentReferenceLink'.tr(),
|
|
||||||
helperText: 'attachmentReferenceLinkDescription'.tr(),
|
|
||||||
helperMaxLines: 3,
|
|
||||||
border: const OutlineInputBorder(),
|
|
||||||
isDense: true,
|
|
||||||
),
|
|
||||||
onTapOutside: (_) =>
|
|
||||||
FocusManager.instance.primaryFocus?.unfocus(),
|
|
||||||
),
|
|
||||||
const Gap(8),
|
|
||||||
TextField(
|
|
||||||
controller: _referenceLinkController,
|
|
||||||
decoration: InputDecoration(
|
|
||||||
labelText: 'fieldAttachmentMimetype'.tr(),
|
|
||||||
helperText: 'class/type',
|
|
||||||
border: const OutlineInputBorder(),
|
|
||||||
isDense: true,
|
|
||||||
),
|
|
||||||
onTapOutside: (_) =>
|
|
||||||
FocusManager.instance.primaryFocus?.unfocus(),
|
|
||||||
),
|
|
||||||
],
|
|
||||||
),
|
|
||||||
if (_referenceLinkController.text.isEmpty &&
|
|
||||||
_randomIdController.text.isEmpty &&
|
|
||||||
widget.canPickMedia)
|
|
||||||
Column(
|
|
||||||
crossAxisAlignment: CrossAxisAlignment.start,
|
|
||||||
children: [
|
|
||||||
Text('attachmentInputNew').tr().fontSize(14),
|
Text('attachmentInputNew').tr().fontSize(14),
|
||||||
Card(
|
Card(
|
||||||
margin: EdgeInsets.only(top: 8),
|
|
||||||
child: Column(
|
child: Column(
|
||||||
children: [
|
children: [
|
||||||
ListTile(
|
ListTile(
|
||||||
shape: RoundedRectangleBorder(
|
shape: RoundedRectangleBorder(
|
||||||
borderRadius: BorderRadius.all(Radius.circular(8)),
|
borderRadius: BorderRadius.all(Radius.circular(8)),
|
||||||
),
|
),
|
||||||
contentPadding: const EdgeInsets.symmetric(
|
contentPadding:
|
||||||
horizontal: 16,
|
const EdgeInsets.symmetric(horizontal: 16, vertical: 8),
|
||||||
vertical: 8,
|
|
||||||
),
|
|
||||||
leading: const Icon(Symbols.add_photo_alternate),
|
leading: const Icon(Symbols.add_photo_alternate),
|
||||||
trailing: const Icon(Symbols.chevron_right),
|
trailing: const Icon(Symbols.chevron_right),
|
||||||
title: Text('addAttachmentFromAlbum').tr(),
|
title: Text('addAttachmentFromAlbum').tr(),
|
||||||
@ -204,8 +128,6 @@ class _AttachmentInputDialogState extends State<AttachmentInputDialog> {
|
|||||||
],
|
],
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
],
|
|
||||||
),
|
|
||||||
if (_isBusy)
|
if (_isBusy)
|
||||||
LinearProgressIndicator(
|
LinearProgressIndicator(
|
||||||
value: _progress,
|
value: _progress,
|
||||||
|
@ -27,12 +27,7 @@ import 'package:surface/widgets/loading_indicator.dart';
|
|||||||
|
|
||||||
class PendingAttachmentActionSheet extends StatefulWidget {
|
class PendingAttachmentActionSheet extends StatefulWidget {
|
||||||
final PostWriteMedia media;
|
final PostWriteMedia media;
|
||||||
final bool canInsertLink;
|
const PendingAttachmentActionSheet({super.key, required this.media});
|
||||||
const PendingAttachmentActionSheet({
|
|
||||||
super.key,
|
|
||||||
required this.media,
|
|
||||||
this.canInsertLink = true,
|
|
||||||
});
|
|
||||||
|
|
||||||
@override
|
@override
|
||||||
State<PendingAttachmentActionSheet> createState() =>
|
State<PendingAttachmentActionSheet> createState() =>
|
||||||
@ -275,7 +270,6 @@ class _PendingAttachmentActionSheetState
|
|||||||
Navigator.pop(context);
|
Navigator.pop(context);
|
||||||
},
|
},
|
||||||
),
|
),
|
||||||
if (widget.canInsertLink)
|
|
||||||
ListTile(
|
ListTile(
|
||||||
minTileHeight: 48,
|
minTileHeight: 48,
|
||||||
leading: const Icon(Symbols.add_link),
|
leading: const Icon(Symbols.add_link),
|
||||||
|
@ -8,7 +8,6 @@ import 'package:flutter/foundation.dart';
|
|||||||
import 'package:flutter/material.dart';
|
import 'package:flutter/material.dart';
|
||||||
import 'package:flutter/services.dart';
|
import 'package:flutter/services.dart';
|
||||||
import 'package:flutter_animate/flutter_animate.dart';
|
import 'package:flutter_animate/flutter_animate.dart';
|
||||||
import 'package:flutter_inappwebview/flutter_inappwebview.dart';
|
|
||||||
import 'package:go_router/go_router.dart';
|
import 'package:go_router/go_router.dart';
|
||||||
import 'package:google_fonts/google_fonts.dart';
|
import 'package:google_fonts/google_fonts.dart';
|
||||||
import 'package:material_symbols_icons/symbols.dart';
|
import 'package:material_symbols_icons/symbols.dart';
|
||||||
@ -42,7 +41,6 @@ import 'package:surface/widgets/post/post_poll.dart';
|
|||||||
import 'package:surface/widgets/post/post_reaction.dart';
|
import 'package:surface/widgets/post/post_reaction.dart';
|
||||||
import 'package:surface/widgets/post/publisher_popover.dart';
|
import 'package:surface/widgets/post/publisher_popover.dart';
|
||||||
import 'package:surface/widgets/universal_image.dart';
|
import 'package:surface/widgets/universal_image.dart';
|
||||||
import 'package:url_launcher/url_launcher_string.dart';
|
|
||||||
import 'package:xml/xml.dart';
|
import 'package:xml/xml.dart';
|
||||||
|
|
||||||
class OpenablePostItem extends StatelessWidget {
|
class OpenablePostItem extends StatelessWidget {
|
||||||
@ -2323,10 +2321,7 @@ class _PostVideoPlayer extends StatelessWidget {
|
|||||||
|
|
||||||
@override
|
@override
|
||||||
Widget build(BuildContext context) {
|
Widget build(BuildContext context) {
|
||||||
return Column(
|
return Container(
|
||||||
crossAxisAlignment: CrossAxisAlignment.start,
|
|
||||||
children: [
|
|
||||||
Container(
|
|
||||||
decoration: BoxDecoration(
|
decoration: BoxDecoration(
|
||||||
borderRadius: const BorderRadius.all(Radius.circular(8)),
|
borderRadius: const BorderRadius.all(Radius.circular(8)),
|
||||||
border: Border.all(
|
border: Border.all(
|
||||||
@ -2338,33 +2333,12 @@ class _PostVideoPlayer extends StatelessWidget {
|
|||||||
aspectRatio: 16 / 9,
|
aspectRatio: 16 / 9,
|
||||||
child: ClipRRect(
|
child: ClipRRect(
|
||||||
borderRadius: const BorderRadius.all(Radius.circular(8)),
|
borderRadius: const BorderRadius.all(Radius.circular(8)),
|
||||||
child: (data.body['video'] is String)
|
child: AttachmentItem(
|
||||||
? InAppWebView(
|
|
||||||
initialUrlRequest: URLRequest(
|
|
||||||
url: WebUri(data.body['video']),
|
|
||||||
),
|
|
||||||
)
|
|
||||||
: AttachmentItem(
|
|
||||||
data: data.body['video'],
|
data: data.body['video'],
|
||||||
heroTag: 'post-video-${data.id}',
|
heroTag: 'post-video-${data.id}',
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
),
|
|
||||||
if (data.body['video'] is String)
|
|
||||||
InkWell(
|
|
||||||
child: Row(
|
|
||||||
children: [
|
|
||||||
const Icon(Symbols.launch, size: 16),
|
|
||||||
const Gap(6),
|
|
||||||
Text('openInBrowser').tr(),
|
|
||||||
],
|
|
||||||
).opacity(0.8),
|
|
||||||
onTap: () {
|
|
||||||
launchUrlString(data.body['video']);
|
|
||||||
},
|
|
||||||
).padding(top: 4),
|
|
||||||
],
|
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
Loading…
x
Reference in New Issue
Block a user