✨ Able to edit publication site config
This commit is contained in:
@@ -2,9 +2,11 @@ import 'package:easy_localization/easy_localization.dart';
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:flutter_hooks/flutter_hooks.dart';
|
||||
import 'package:hooks_riverpod/hooks_riverpod.dart';
|
||||
import 'package:island/models/publication_site.dart';
|
||||
import 'package:island/pods/network.dart';
|
||||
import 'package:island/screens/creators/sites/site_detail.dart';
|
||||
import 'package:island/screens/creators/sites/site_list.dart';
|
||||
import 'package:island/screens/creators/sites/widgets/site_config_form.dart';
|
||||
import 'package:island/widgets/alert.dart';
|
||||
import 'package:island/widgets/content/sheet.dart';
|
||||
import 'package:island/widgets/response.dart';
|
||||
@@ -23,6 +25,7 @@ class SiteForm extends HookConsumerWidget {
|
||||
TextEditingController nameController,
|
||||
TextEditingController descriptionController,
|
||||
ValueNotifier<int> modeController,
|
||||
ValueNotifier<SnPublicationSiteConfig> configController,
|
||||
Function() saveSite,
|
||||
Function() deleteSite,
|
||||
String siteSlug,
|
||||
@@ -103,38 +106,40 @@ class SiteForm extends HookConsumerWidget {
|
||||
}
|
||||
},
|
||||
),
|
||||
const SizedBox(height: 16),
|
||||
SiteConfigForm(
|
||||
initialConfig: configController.value,
|
||||
onChanged: (value) => configController.value = value,
|
||||
),
|
||||
],
|
||||
).padding(all: 20);
|
||||
|
||||
return SheetScaffold(
|
||||
titleText: 'editPublicationSite'.tr(),
|
||||
child: Builder(
|
||||
builder:
|
||||
(context) => SingleChildScrollView(
|
||||
child: Column(
|
||||
builder: (context) => SingleChildScrollView(
|
||||
child: Column(
|
||||
children: [
|
||||
Form(key: formKey, child: formFields),
|
||||
Row(
|
||||
children: [
|
||||
Form(key: formKey, child: formFields),
|
||||
Row(
|
||||
children: [
|
||||
TextButton.icon(
|
||||
onPressed: deleteSite,
|
||||
icon: const Icon(Symbols.delete_forever),
|
||||
label: Text('deletePublicationSite'.tr()),
|
||||
style: TextButton.styleFrom(
|
||||
foregroundColor: Colors.red,
|
||||
),
|
||||
).alignment(Alignment.centerRight),
|
||||
const Spacer(),
|
||||
TextButton.icon(
|
||||
onPressed: saveSite,
|
||||
icon: const Icon(Symbols.save),
|
||||
label: Text('saveChanges').tr(),
|
||||
),
|
||||
],
|
||||
).padding(horizontal: 20, vertical: 12),
|
||||
TextButton.icon(
|
||||
onPressed: deleteSite,
|
||||
icon: const Icon(Symbols.delete_forever),
|
||||
label: Text('deletePublicationSite'.tr()),
|
||||
style: TextButton.styleFrom(foregroundColor: Colors.red),
|
||||
).alignment(Alignment.centerRight),
|
||||
const Spacer(),
|
||||
TextButton.icon(
|
||||
onPressed: saveSite,
|
||||
icon: const Icon(Symbols.save),
|
||||
label: Text('saveChanges').tr(),
|
||||
),
|
||||
],
|
||||
),
|
||||
),
|
||||
).padding(horizontal: 20, vertical: 12),
|
||||
],
|
||||
),
|
||||
),
|
||||
),
|
||||
);
|
||||
}
|
||||
@@ -146,6 +151,9 @@ class SiteForm extends HookConsumerWidget {
|
||||
final nameController = useTextEditingController();
|
||||
final descriptionController = useTextEditingController();
|
||||
final modeController = useState<int>(0); // Default to fully managed (0)
|
||||
final configController = useState<SnPublicationSiteConfig>(
|
||||
const SnPublicationSiteConfig(),
|
||||
);
|
||||
final isLoading = useState(false);
|
||||
|
||||
final saveSite = useCallback(() async {
|
||||
@@ -162,6 +170,7 @@ class SiteForm extends HookConsumerWidget {
|
||||
'mode': modeController.value,
|
||||
if (descriptionController.text.isNotEmpty)
|
||||
'description': descriptionController.text,
|
||||
'config': configController.value.toJson(),
|
||||
};
|
||||
|
||||
if (siteSlug != null) {
|
||||
@@ -229,40 +238,39 @@ class SiteForm extends HookConsumerWidget {
|
||||
nameController.text = site.name;
|
||||
descriptionController.text = site.description ?? '';
|
||||
modeController.value = site.mode ?? 0;
|
||||
configController.value = site.config;
|
||||
}
|
||||
return null;
|
||||
}, [siteAsync]);
|
||||
|
||||
// Handle loading and error states for editing using AsyncValue
|
||||
return siteAsync.when(
|
||||
data:
|
||||
(_) => _buildForm(
|
||||
formKey,
|
||||
slugController,
|
||||
nameController,
|
||||
descriptionController,
|
||||
modeController,
|
||||
saveSite,
|
||||
deleteSite,
|
||||
editingSiteSlug,
|
||||
),
|
||||
loading:
|
||||
() => SheetScaffold(
|
||||
titleText: 'editPublicationSite'.tr(),
|
||||
child: Center(child: CircularProgressIndicator()),
|
||||
),
|
||||
error:
|
||||
(error, _) => SheetScaffold(
|
||||
titleText: 'editPublicationSite'.tr(),
|
||||
child: ResponseErrorWidget(
|
||||
error: error.toString(),
|
||||
onRetry: () {
|
||||
ref.invalidate(
|
||||
publicationSiteDetailProvider(pubName, editingSiteSlug),
|
||||
);
|
||||
},
|
||||
),
|
||||
),
|
||||
data: (_) => _buildForm(
|
||||
formKey,
|
||||
slugController,
|
||||
nameController,
|
||||
descriptionController,
|
||||
modeController,
|
||||
configController,
|
||||
saveSite,
|
||||
deleteSite,
|
||||
editingSiteSlug,
|
||||
),
|
||||
loading: () => SheetScaffold(
|
||||
titleText: 'editPublicationSite'.tr(),
|
||||
child: Center(child: CircularProgressIndicator()),
|
||||
),
|
||||
error: (error, _) => SheetScaffold(
|
||||
titleText: 'editPublicationSite'.tr(),
|
||||
child: ResponseErrorWidget(
|
||||
error: error.toString(),
|
||||
onRetry: () {
|
||||
ref.invalidate(
|
||||
publicationSiteDetailProvider(pubName, editingSiteSlug),
|
||||
);
|
||||
},
|
||||
),
|
||||
),
|
||||
);
|
||||
}
|
||||
|
||||
@@ -344,6 +352,11 @@ class SiteForm extends HookConsumerWidget {
|
||||
}
|
||||
},
|
||||
),
|
||||
const SizedBox(height: 16),
|
||||
SiteConfigForm(
|
||||
initialConfig: configController.value,
|
||||
onChanged: (value) => configController.value = value,
|
||||
),
|
||||
],
|
||||
).padding(all: 20);
|
||||
|
||||
@@ -354,10 +367,9 @@ class SiteForm extends HookConsumerWidget {
|
||||
).padding(horizontal: 20, vertical: 12);
|
||||
|
||||
return SheetScaffold(
|
||||
titleText:
|
||||
siteSlug == null
|
||||
? 'newPublicationSite'.tr()
|
||||
: 'editPublicationSite'.tr(),
|
||||
titleText: siteSlug == null
|
||||
? 'newPublicationSite'.tr()
|
||||
: 'editPublicationSite'.tr(),
|
||||
child: SingleChildScrollView(
|
||||
child: Column(
|
||||
children: [
|
||||
|
||||
172
lib/screens/creators/sites/widgets/site_config_form.dart
Normal file
172
lib/screens/creators/sites/widgets/site_config_form.dart
Normal file
@@ -0,0 +1,172 @@
|
||||
import 'package:easy_localization/easy_localization.dart';
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:flutter_hooks/flutter_hooks.dart';
|
||||
import 'package:island/models/publication_site.dart';
|
||||
import 'package:material_symbols_icons/symbols.dart';
|
||||
import 'package:styled_widget/styled_widget.dart';
|
||||
|
||||
class SiteConfigForm extends HookWidget {
|
||||
final SnPublicationSiteConfig? initialConfig;
|
||||
final ValueChanged<SnPublicationSiteConfig> onChanged;
|
||||
|
||||
const SiteConfigForm({
|
||||
super.key,
|
||||
this.initialConfig,
|
||||
required this.onChanged,
|
||||
});
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
final styleOverrideController = useTextEditingController(
|
||||
text: initialConfig?.styleOverride,
|
||||
);
|
||||
final navItems = useState<List<SnPublicationSiteNavItems>>(
|
||||
initialConfig?.navItems ?? [],
|
||||
);
|
||||
|
||||
useEffect(() {
|
||||
void listener() {
|
||||
onChanged(
|
||||
SnPublicationSiteConfig(
|
||||
styleOverride: styleOverrideController.text,
|
||||
navItems: navItems.value,
|
||||
),
|
||||
);
|
||||
}
|
||||
|
||||
styleOverrideController.addListener(listener);
|
||||
return () => styleOverrideController.removeListener(listener);
|
||||
}, [styleOverrideController, navItems.value]);
|
||||
|
||||
return Card(
|
||||
margin: EdgeInsets.zero,
|
||||
child: ExpansionTile(
|
||||
shape: RoundedRectangleBorder(
|
||||
borderRadius: const BorderRadius.all(Radius.circular(8)),
|
||||
),
|
||||
title: Text('siteConfig'.tr()),
|
||||
children: [
|
||||
Column(
|
||||
spacing: 8,
|
||||
children: [
|
||||
TextFormField(
|
||||
controller: styleOverrideController,
|
||||
decoration: InputDecoration(
|
||||
labelText: 'siteConfigStyleOverride'.tr(),
|
||||
hintText: "You can paste your CSS here...",
|
||||
alignLabelWithHint: true,
|
||||
border: const OutlineInputBorder(
|
||||
borderRadius: BorderRadius.all(Radius.circular(12)),
|
||||
),
|
||||
),
|
||||
minLines: 3,
|
||||
maxLines: null,
|
||||
).padding(bottom: 8),
|
||||
Row(
|
||||
children: [
|
||||
Text('siteConfigNavItems'.tr()).bold(),
|
||||
const Spacer(),
|
||||
TextButton.icon(
|
||||
onPressed: () {
|
||||
navItems.value = [
|
||||
...navItems.value,
|
||||
const SnPublicationSiteNavItems(label: '', href: ''),
|
||||
];
|
||||
// Trigger update manually as list mutation doesn't trigger controller listener
|
||||
onChanged(
|
||||
SnPublicationSiteConfig(
|
||||
styleOverride: styleOverrideController.text,
|
||||
navItems: navItems.value,
|
||||
),
|
||||
);
|
||||
},
|
||||
icon: const Icon(Symbols.add),
|
||||
label: Text('siteConfigAddNavItem'.tr()),
|
||||
),
|
||||
],
|
||||
).padding(bottom: 4),
|
||||
if (navItems.value.isEmpty)
|
||||
Text('dataEmpty'.tr()).center().padding(vertical: 20),
|
||||
...navItems.value.asMap().entries.map((entry) {
|
||||
final index = entry.key;
|
||||
final item = entry.value;
|
||||
return Container(
|
||||
decoration: BoxDecoration(
|
||||
border: Border.all(
|
||||
color: Theme.of(context).colorScheme.outline,
|
||||
width: 1,
|
||||
),
|
||||
borderRadius: BorderRadius.circular(10),
|
||||
),
|
||||
child: Column(
|
||||
spacing: 12,
|
||||
children: [
|
||||
TextFormField(
|
||||
initialValue: item.label,
|
||||
decoration: InputDecoration(
|
||||
labelText: 'siteConfigNavItemLabel'.tr(),
|
||||
border: const OutlineInputBorder(
|
||||
borderRadius: BorderRadius.all(Radius.circular(12)),
|
||||
),
|
||||
),
|
||||
onChanged: (value) {
|
||||
final newItems = [...navItems.value];
|
||||
newItems[index] = item.copyWith(label: value);
|
||||
navItems.value = newItems;
|
||||
onChanged(
|
||||
SnPublicationSiteConfig(
|
||||
styleOverride: styleOverrideController.text,
|
||||
navItems: newItems,
|
||||
),
|
||||
);
|
||||
},
|
||||
),
|
||||
TextFormField(
|
||||
initialValue: item.href,
|
||||
decoration: InputDecoration(
|
||||
labelText: 'siteConfigNavItemHref'.tr(),
|
||||
border: const OutlineInputBorder(
|
||||
borderRadius: BorderRadius.all(Radius.circular(12)),
|
||||
),
|
||||
),
|
||||
onChanged: (value) {
|
||||
final newItems = [...navItems.value];
|
||||
newItems[index] = item.copyWith(href: value);
|
||||
navItems.value = newItems;
|
||||
onChanged(
|
||||
SnPublicationSiteConfig(
|
||||
styleOverride: styleOverrideController.text,
|
||||
navItems: newItems,
|
||||
),
|
||||
);
|
||||
},
|
||||
),
|
||||
TextButton.icon(
|
||||
onPressed: () {
|
||||
final newItems = [...navItems.value];
|
||||
newItems.removeAt(index);
|
||||
navItems.value = newItems;
|
||||
onChanged(
|
||||
SnPublicationSiteConfig(
|
||||
styleOverride: styleOverrideController.text,
|
||||
navItems: newItems,
|
||||
),
|
||||
);
|
||||
},
|
||||
icon: const Icon(Symbols.delete),
|
||||
label: Text('delete'.tr()),
|
||||
style: TextButton.styleFrom(
|
||||
foregroundColor: Colors.red,
|
||||
),
|
||||
).alignment(Alignment.centerRight),
|
||||
],
|
||||
).padding(horizontal: 16, vertical: 20),
|
||||
);
|
||||
}),
|
||||
],
|
||||
).padding(all: 16),
|
||||
],
|
||||
),
|
||||
);
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user