Create boost

This commit is contained in:
2024-12-29 02:13:31 +08:00
parent 03943a7138
commit d4fbdd397e
14 changed files with 956 additions and 41 deletions

View File

@ -10,8 +10,8 @@ import 'package:surface/widgets/dialog.dart';
class AttachmentInputDialog extends StatefulWidget {
final String? title;
const AttachmentInputDialog({super.key, required this.title});
final bool? analyzeNow;
const AttachmentInputDialog({super.key, required this.title, this.analyzeNow = false});
@override
State<AttachmentInputDialog> createState() => _AttachmentInputDialogState();
@ -53,6 +53,7 @@ class _AttachmentInputDialogState extends State<AttachmentInputDialog> {
_thumbnailFile!.path,
'interactive',
null,
analyzeNow: widget.analyzeNow ?? false,
);
if (!mounted) return;
Navigator.pop(context, attachment);
@ -77,7 +78,8 @@ class _AttachmentInputDialogState extends State<AttachmentInputDialog> {
controller: _randomIdController,
decoration: InputDecoration(
labelText: 'fieldAttachmentRandomId'.tr(),
border: const OutlineInputBorder(),
border: const UnderlineInputBorder(),
isDense: true,
),
onTapOutside: (_) => FocusManager.instance.primaryFocus?.unfocus(),
),

View File

@ -0,0 +1,120 @@
import 'package:easy_localization/easy_localization.dart';
import 'package:flutter/material.dart';
import 'package:gap/gap.dart';
import 'package:provider/provider.dart';
import 'package:styled_widget/styled_widget.dart';
import 'package:surface/controllers/post_write_controller.dart';
import 'package:surface/providers/sn_network.dart';
import 'package:surface/types/attachment.dart';
import 'package:surface/widgets/dialog.dart';
class PendingAttachmentBoostDialog extends StatefulWidget {
final PostWriteMedia media;
const PendingAttachmentBoostDialog({super.key, required this.media});
@override
State<PendingAttachmentBoostDialog> createState() => _PendingAttachmentBoostDialogState();
}
class _PendingAttachmentBoostDialogState extends State<PendingAttachmentBoostDialog> {
List<SnAttachmentDestination>? _regions;
SnAttachmentDestination? _selectedRegion;
Future<void> _fetchRegions() async {
try {
final sn = context.read<SnNetworkProvider>();
final resp = await sn.client.get('/cgi/uc/destinations');
setState(() {
_regions = List<SnAttachmentDestination>.from(
resp.data?.map((e) => SnAttachmentDestination.fromJson(e)) ?? [],
).cast<SnAttachmentDestination>().where((ele) => ele.isBoost).toList();
});
} catch (err) {
if (!mounted) return;
context.showErrorDialog(err);
}
}
bool _isBusy = false;
Future<void> _performAction() async {
if (_isBusy) return;
if (_selectedRegion == null) return;
setState(() => _isBusy = true);
try {
final sn = context.read<SnNetworkProvider>();
final resp = await sn.client.post('/cgi/uc/boosts', data: {
'attachment': widget.media.attachment!.id,
'destination': _selectedRegion!.id,
});
if (!mounted) return;
Navigator.pop(context, SnAttachmentBoost.fromJson(resp.data));
} catch (err) {
if (!mounted) return;
context.showErrorDialog(err);
setState(() => _isBusy = false);
}
}
@override
void initState() {
super.initState();
_fetchRegions();
}
@override
Widget build(BuildContext context) {
return AlertDialog(
title: Text('attachmentBoost').tr(),
content: Column(
mainAxisSize: MainAxisSize.min,
crossAxisAlignment: CrossAxisAlignment.start,
children: [
Text('attachmentBoostHint').tr(),
const Gap(16),
Text('attachmentDestinationRegion').tr().fontSize(18),
const Gap(8),
Card(
child: _regions == null
? const CircularProgressIndicator().center().padding(all: 16)
: Column(
children: _regions!.map(
(ele) {
return RadioListTile(
title: Text(ele.label).tr(),
subtitle: Text(
'attachmentDestinationRegion${ele.region}'.trExists()
? 'attachmentDestinationRegion${ele.region}'.tr()
: ele.region,
),
selected: _selectedRegion == ele,
value: ele,
groupValue: _selectedRegion,
onChanged: (value) {
if (value != null) setState(() => _selectedRegion = value);
},
);
},
).toList(),
),
),
],
),
actions: [
TextButton(
onPressed: _isBusy ? null : () {
Navigator.pop(context);
},
child: Text('dialogDismiss'.tr()),
),
TextButton(
onPressed: _isBusy ? null : () => _performAction(),
child: Text('dialogConfirm'.tr()),
),
],
);
}
}

View File

@ -18,7 +18,6 @@ import 'package:screenshot/screenshot.dart';
import 'package:share_plus/share_plus.dart';
import 'package:styled_widget/styled_widget.dart';
import 'package:surface/providers/config.dart';
import 'package:surface/providers/link_preview.dart';
import 'package:surface/providers/sn_network.dart';
import 'package:surface/providers/userinfo.dart';
import 'package:surface/types/post.dart';
@ -255,6 +254,10 @@ class PostItem extends StatelessWidget {
maxHeight: 560,
listPadding: const EdgeInsets.symmetric(horizontal: 12),
),
if (data.body['content'] != null)
LinkPreviewWidget(
text: data.body['content'],
).padding(horizontal: 4),
Container(
constraints: BoxConstraints(maxWidth: maxWidth ?? double.infinity),
child: Column(
@ -336,10 +339,6 @@ class PostShareImageWidget extends StatelessWidget {
data: data.preload!.attachments!,
isFlatted: true,
).padding(horizontal: 16, bottom: 8),
if (data.body['content'] != null)
LinkPreviewWidget(
text: data.body['content'],
).padding(horizontal: 4),
Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [

View File

@ -21,6 +21,7 @@ import 'package:surface/providers/sn_network.dart';
import 'package:surface/types/attachment.dart';
import 'package:surface/widgets/attachment/attachment_input.dart';
import 'package:surface/widgets/attachment/attachment_zoom.dart';
import 'package:surface/widgets/attachment/pending_attachment_boost.dart';
import 'package:surface/widgets/context_menu.dart';
import 'package:surface/widgets/dialog.dart';
import 'package:surface/widgets/universal_image.dart';
@ -93,18 +94,23 @@ class PostMediaPendingList extends StatelessWidget {
context: context,
builder: (context) => AttachmentInputDialog(
title: 'attachmentSetThumbnail'.tr(),
analyzeNow: true,
),
);
if (thumbnail == null) return;
if (!context.mounted) return;
final attach = context.read<SnAttachmentProvider>();
final newAttach = await attach.updateOne(
attachments[idx].attachment!,
thumbnailId: thumbnail.id,
);
onUpdate!(idx, PostWriteMedia(newAttach));
try {
final attach = context.read<SnAttachmentProvider>();
final newAttach = await attach.updateOne(
attachments[idx].attachment!,
thumbnailId: thumbnail.id,
);
onUpdate!(idx, PostWriteMedia(newAttach));
} catch (err) {
if (!context.mounted) return;
context.showErrorDialog(err);
}
}
Future<void> _deleteAttachment(BuildContext context, int idx) async {
@ -124,6 +130,23 @@ class PostMediaPendingList extends StatelessWidget {
}
}
Future<void> _createBoost(BuildContext context, int idx) async {
if (attachments[idx].attachment == null) return;
final result = await showDialog<SnAttachmentBoost?>(
context: context,
builder: (context) => PendingAttachmentBoostDialog(media: attachments[idx]),
);
if (result == null) return;
final newAttach = attachments[idx].attachment!.copyWith(
boosts: [...attachments[idx].attachment!.boosts, result],
);
final newMedia = PostWriteMedia(newAttach);
onUpdate!(idx, newMedia);
}
Future<void> _compressVideo(BuildContext context, int idx) async {
final result = await showDialog<PostWriteMedia?>(
context: context,
@ -146,6 +169,14 @@ class PostMediaPendingList extends StatelessWidget {
_compressVideo(context, idx);
},
),
if (media.attachment != null)
MenuItem(
label: 'attachmentBoost'.tr(),
icon: Symbols.bolt,
onSelected: () {
_createBoost(context, idx);
},
),
if (media.attachment != null && media.type == SnMediaType.video)
MenuItem(
label: 'attachmentSetThumbnail'.tr(),
@ -389,11 +420,19 @@ class _PostMediaPendingItem extends StatelessWidget {
],
),
),
if (media.attachment != null && media.attachment!.compressedId != null)
if (media.attachment != null && media.attachment!.boosts.isNotEmpty)
Row(
children: [
Icon(Symbols.bolt, size: 16),
const Gap(4),
Text('attachmentGotBoosted').tr().fontSize(13),
],
),
if (media.attachment != null && media.attachment!.compressedId != null)
Row(
children: [
Icon(Symbols.compress, size: 16),
const Gap(4),
Text('attachmentCopyCompressed').tr().fontSize(13),
],
),