💄 Optimize the embed view experience

This commit is contained in:
2025-09-23 21:02:14 +08:00
parent cccade763f
commit 27d478ba4f
4 changed files with 339 additions and 238 deletions

View File

@@ -30,10 +30,13 @@ class ComposeEmbedSheet extends HookConsumerWidget {
final selectedRenderer = useState<PostEmbedViewRenderer>(
PostEmbedViewRenderer.webView,
);
final tabController = useTabController(initialLength: 2);
final iframeController = useTextEditingController();
void clearForm() {
uriController.clear();
aspectRatioController.clear();
iframeController.clear();
selectedRenderer.value = PostEmbedViewRenderer.webView;
}
@@ -77,6 +80,57 @@ class ComposeEmbedSheet extends HookConsumerWidget {
}
}
void parseIframe() {
final iframe = iframeController.text.trim();
if (iframe.isEmpty) return;
final srcMatch = RegExp(r'src="([^"]*)"').firstMatch(iframe);
final widthMatch = RegExp(r'width="([^"]*)"').firstMatch(iframe);
final heightMatch = RegExp(r'height="([^"]*)"').firstMatch(iframe);
if (srcMatch != null) {
uriController.text = srcMatch.group(1)!;
}
if (widthMatch != null && heightMatch != null) {
final w = double.tryParse(widthMatch.group(1)!);
final h = double.tryParse(heightMatch.group(1)!);
if (w != null && h != null && h != 0) {
aspectRatioController.text = (w / h).toStringAsFixed(3);
}
}
tabController.animateTo(1);
}
void deleteEmbed(BuildContext context) {
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: Theme.of(context).colorScheme.error,
),
child: Text('delete').tr(),
),
],
),
);
}
return SheetScaffold(
titleText: 'embedView'.tr(),
heightFactor: 0.7,
@@ -85,7 +139,7 @@ class ComposeEmbedSheet extends HookConsumerWidget {
// Header with save button when editing
if (currentEmbedView != null)
Container(
padding: const EdgeInsets.symmetric(horizontal: 20, vertical: 8),
padding: const EdgeInsets.symmetric(horizontal: 20, vertical: 6),
color: Theme.of(context).colorScheme.surfaceContainerHigh,
child: Row(
children: [
@@ -97,187 +151,207 @@ class ComposeEmbedSheet extends HookConsumerWidget {
),
TextButton(
onPressed: saveEmbedView,
style: ButtonStyle(visualDensity: VisualDensity.compact),
child: Text('save'.tr()),
),
],
),
),
// Tab bar
TabBar(
controller: tabController,
tabs: [Tab(text: 'auto'.tr()), Tab(text: 'manual'.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),
child: TabBarView(
controller: tabController,
children: [
// Auto tab
SingleChildScrollView(
padding: const EdgeInsets.all(16),
child: Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
TextField(
controller: iframeController,
decoration: InputDecoration(
labelText: 'iframeCode'.tr(),
hintText: 'iframeCodeHint'.tr(),
border: OutlineInputBorder(
borderRadius: BorderRadius.circular(8),
),
),
maxLines: 5,
),
),
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),
const Gap(16),
SizedBox(
width: double.infinity,
child: FilledButton.icon(
onPressed: parseIframe,
icon: const Icon(Symbols.auto_fix),
label: Text('parseIframe'.tr()),
),
),
),
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,
),
// Manual tab
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),
),
),
child: Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
Row(
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),
),
),
selectedItemBuilder: (context) {
return PostEmbedViewRenderer.values.map((renderer) {
return Text(renderer.name).tr();
}).toList();
},
menuItemStyleData: MenuItemStyleData(
padding: EdgeInsets.zero,
),
items:
PostEmbedViewRenderer.values.map((renderer) {
return DropdownMenuItem(
value: renderer,
child: Text(
renderer.name,
).tr().padding(horizontal: 20),
);
}).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: [
Icon(
currentEmbedView.renderer ==
PostEmbedViewRenderer.webView
? Symbols.web
: Symbols.web,
color: colorScheme.primary,
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: () => deleteEmbed(context),
tooltip: 'delete'.tr(),
color: colorScheme.error,
),
],
),
const Gap(12),
Expanded(
child: Text(
currentEmbedView.uri,
style: theme.textTheme.bodyMedium,
maxLines: 1,
overflow: TextOverflow.ellipsis,
Text(
'aspectRatio'.tr(),
style: theme.textTheme.labelMedium?.copyWith(
color: colorScheme.onSurfaceVariant,
),
),
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(4),
Text(
currentEmbedView.aspectRatio != null
? currentEmbedView.aspectRatio!
.toStringAsFixed(2)
: 'notSet'.tr(),
style: theme.textTheme.bodyMedium,
),
],
),
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()),
),
),
],
],
),
] else ...[
const Gap(16),
SizedBox(
width: double.infinity,
child: FilledButton.icon(
onPressed: saveEmbedView,
icon: const Icon(Symbols.add),
label: Text('addEmbed'.tr()),
),
),
],
],
),
),
],
),
),
],