288 lines
11 KiB
Dart
288 lines
11 KiB
Dart
import 'package:dropdown_button2/dropdown_button2.dart';
|
|
import 'package:easy_localization/easy_localization.dart';
|
|
import 'package:flutter/material.dart';
|
|
import 'package:flutter/services.dart';
|
|
import 'package:flutter_hooks/flutter_hooks.dart';
|
|
import 'package:gap/gap.dart';
|
|
import 'package:hooks_riverpod/hooks_riverpod.dart';
|
|
import 'package:island/models/post.dart';
|
|
import 'package:island/widgets/content/sheet.dart';
|
|
import 'package:island/widgets/post/compose_shared.dart';
|
|
import 'package:material_symbols_icons/symbols.dart';
|
|
import 'package:styled_widget/styled_widget.dart';
|
|
|
|
class ComposeEmbedSheet extends HookConsumerWidget {
|
|
final ComposeState state;
|
|
|
|
const ComposeEmbedSheet({super.key, required this.state});
|
|
|
|
@override
|
|
Widget build(BuildContext context, WidgetRef ref) {
|
|
final theme = Theme.of(context);
|
|
final colorScheme = theme.colorScheme;
|
|
|
|
// Listen to embed view changes
|
|
final currentEmbedView = useValueListenable(state.embedView);
|
|
|
|
// Form state
|
|
final uriController = useTextEditingController();
|
|
final aspectRatioController = useTextEditingController();
|
|
final selectedRenderer = useState<PostEmbedViewRenderer>(
|
|
PostEmbedViewRenderer.webView,
|
|
);
|
|
|
|
void clearForm() {
|
|
uriController.clear();
|
|
aspectRatioController.clear();
|
|
selectedRenderer.value = PostEmbedViewRenderer.webView;
|
|
}
|
|
|
|
// Populate form when embed view changes
|
|
useEffect(() {
|
|
if (currentEmbedView != null) {
|
|
uriController.text = currentEmbedView.uri;
|
|
aspectRatioController.text =
|
|
currentEmbedView.aspectRatio?.toString() ?? '';
|
|
selectedRenderer.value = currentEmbedView.renderer;
|
|
} else {
|
|
clearForm();
|
|
}
|
|
return null;
|
|
}, [currentEmbedView]);
|
|
|
|
void saveEmbedView() {
|
|
final uri = uriController.text.trim();
|
|
if (uri.isEmpty) {
|
|
ScaffoldMessenger.of(
|
|
context,
|
|
).showSnackBar(SnackBar(content: Text('embedUriRequired'.tr())));
|
|
return;
|
|
}
|
|
|
|
final aspectRatio =
|
|
aspectRatioController.text.trim().isNotEmpty
|
|
? double.tryParse(aspectRatioController.text.trim())
|
|
: null;
|
|
|
|
final embedView = SnPostEmbedView(
|
|
uri: uri,
|
|
aspectRatio: aspectRatio,
|
|
renderer: selectedRenderer.value,
|
|
);
|
|
|
|
if (currentEmbedView != null) {
|
|
ComposeLogic.updateEmbedView(state, embedView);
|
|
} else {
|
|
ComposeLogic.setEmbedView(state, embedView);
|
|
}
|
|
}
|
|
|
|
return SheetScaffold(
|
|
titleText: 'embedView'.tr(),
|
|
heightFactor: 0.7,
|
|
child: Column(
|
|
children: [
|
|
// Header with save button when editing
|
|
if (currentEmbedView != null)
|
|
Container(
|
|
padding: const EdgeInsets.symmetric(horizontal: 20, vertical: 8),
|
|
color: Theme.of(context).colorScheme.surfaceContainerHigh,
|
|
child: Row(
|
|
children: [
|
|
Expanded(
|
|
child: Text(
|
|
'editEmbed'.tr(),
|
|
style: theme.textTheme.titleMedium,
|
|
),
|
|
),
|
|
TextButton(
|
|
onPressed: saveEmbedView,
|
|
child: Text('save'.tr()),
|
|
),
|
|
],
|
|
),
|
|
),
|
|
|
|
// Content area
|
|
Expanded(
|
|
child: SingleChildScrollView(
|
|
padding: const EdgeInsets.all(16),
|
|
child: Column(
|
|
crossAxisAlignment: CrossAxisAlignment.start,
|
|
children: [
|
|
// Form fields
|
|
TextField(
|
|
controller: uriController,
|
|
decoration: InputDecoration(
|
|
labelText: 'embedUri'.tr(),
|
|
hintText: 'https://example.com',
|
|
border: OutlineInputBorder(
|
|
borderRadius: BorderRadius.circular(8),
|
|
),
|
|
),
|
|
keyboardType: TextInputType.url,
|
|
),
|
|
const Gap(16),
|
|
TextField(
|
|
controller: aspectRatioController,
|
|
decoration: InputDecoration(
|
|
labelText: 'aspectRatio'.tr(),
|
|
hintText: '16/9 = 1.777',
|
|
border: OutlineInputBorder(
|
|
borderRadius: BorderRadius.circular(8),
|
|
),
|
|
),
|
|
keyboardType: TextInputType.numberWithOptions(
|
|
decimal: true,
|
|
),
|
|
inputFormatters: [
|
|
FilteringTextInputFormatter.allow(RegExp(r'^\d*\.?\d*$')),
|
|
],
|
|
),
|
|
const Gap(16),
|
|
DropdownButtonFormField2<PostEmbedViewRenderer>(
|
|
value: selectedRenderer.value,
|
|
decoration: InputDecoration(
|
|
labelText: 'renderer'.tr(),
|
|
border: OutlineInputBorder(
|
|
borderRadius: BorderRadius.circular(8),
|
|
),
|
|
),
|
|
items:
|
|
PostEmbedViewRenderer.values.map((renderer) {
|
|
return DropdownMenuItem(
|
|
value: renderer,
|
|
child: Text(renderer.name).tr(),
|
|
);
|
|
}).toList(),
|
|
onChanged: (value) {
|
|
if (value != null) {
|
|
selectedRenderer.value = value;
|
|
}
|
|
},
|
|
),
|
|
|
|
// Current embed view display (when exists)
|
|
if (currentEmbedView != null) ...[
|
|
const Gap(32),
|
|
Text(
|
|
'currentEmbed'.tr(),
|
|
style: theme.textTheme.titleMedium,
|
|
).padding(horizontal: 4),
|
|
const Gap(8),
|
|
Card(
|
|
margin: EdgeInsets.zero,
|
|
color: Theme.of(context).colorScheme.surfaceContainerHigh,
|
|
child: Padding(
|
|
padding: const EdgeInsets.only(
|
|
left: 16,
|
|
right: 16,
|
|
bottom: 12,
|
|
top: 4,
|
|
),
|
|
child: Column(
|
|
crossAxisAlignment: CrossAxisAlignment.start,
|
|
children: [
|
|
Row(
|
|
children: [
|
|
Icon(
|
|
currentEmbedView.renderer ==
|
|
PostEmbedViewRenderer.webView
|
|
? Symbols.web
|
|
: Symbols.web,
|
|
color: colorScheme.primary,
|
|
),
|
|
const Gap(12),
|
|
Expanded(
|
|
child: Text(
|
|
currentEmbedView.uri,
|
|
style: theme.textTheme.bodyMedium,
|
|
maxLines: 1,
|
|
overflow: TextOverflow.ellipsis,
|
|
),
|
|
),
|
|
IconButton(
|
|
icon: const Icon(Symbols.delete),
|
|
onPressed: () {
|
|
showDialog(
|
|
context: context,
|
|
builder:
|
|
(dialogContext) => AlertDialog(
|
|
title: Text('deleteEmbed').tr(),
|
|
content:
|
|
Text('deleteEmbedConfirm').tr(),
|
|
actions: [
|
|
TextButton(
|
|
onPressed:
|
|
() =>
|
|
Navigator.of(
|
|
dialogContext,
|
|
).pop(),
|
|
child: Text('cancel'.tr()),
|
|
),
|
|
TextButton(
|
|
onPressed: () {
|
|
ComposeLogic.deleteEmbedView(
|
|
state,
|
|
);
|
|
clearForm();
|
|
Navigator.of(
|
|
dialogContext,
|
|
).pop();
|
|
},
|
|
style: TextButton.styleFrom(
|
|
foregroundColor:
|
|
colorScheme.error,
|
|
),
|
|
child: Text('delete').tr(),
|
|
),
|
|
],
|
|
),
|
|
);
|
|
},
|
|
tooltip: 'delete'.tr(),
|
|
color: colorScheme.error,
|
|
),
|
|
],
|
|
),
|
|
const Gap(12),
|
|
Text(
|
|
'aspectRatio'.tr(),
|
|
style: theme.textTheme.labelMedium?.copyWith(
|
|
color: colorScheme.onSurfaceVariant,
|
|
),
|
|
),
|
|
const Gap(4),
|
|
Text(
|
|
currentEmbedView.aspectRatio != null
|
|
? currentEmbedView.aspectRatio!
|
|
.toStringAsFixed(2)
|
|
: 'notSet'.tr(),
|
|
style: theme.textTheme.bodyMedium,
|
|
),
|
|
],
|
|
),
|
|
),
|
|
),
|
|
] else ...[
|
|
// Save button for new embed
|
|
const Gap(16),
|
|
SizedBox(
|
|
width: double.infinity,
|
|
child: FilledButton.icon(
|
|
onPressed: saveEmbedView,
|
|
icon: const Icon(Symbols.add),
|
|
label: Text('addEmbed'.tr()),
|
|
),
|
|
),
|
|
],
|
|
],
|
|
),
|
|
),
|
|
),
|
|
],
|
|
),
|
|
);
|
|
}
|
|
}
|