💄 Optimize publication site screen
This commit is contained in:
@@ -203,237 +203,207 @@ class PageForm extends HookConsumerWidget {
|
||||
|
||||
return SheetScaffold(
|
||||
titleText: page == null ? 'Create Page' : 'Edit Page',
|
||||
child: Builder(
|
||||
builder:
|
||||
(context) => SingleChildScrollView(
|
||||
child: SingleChildScrollView(
|
||||
child: Column(
|
||||
children: [
|
||||
Form(
|
||||
key: formKey,
|
||||
child: Column(
|
||||
children: [
|
||||
Form(
|
||||
key: formKey,
|
||||
child: Column(
|
||||
children: [
|
||||
// Page type selector
|
||||
DropdownButtonFormField<int>(
|
||||
value: pageType.value,
|
||||
decoration: const InputDecoration(
|
||||
labelText: 'Page Type',
|
||||
border: OutlineInputBorder(
|
||||
borderRadius: BorderRadius.all(
|
||||
Radius.circular(12),
|
||||
),
|
||||
),
|
||||
),
|
||||
items: const [
|
||||
DropdownMenuItem(
|
||||
value: 0,
|
||||
child: Row(
|
||||
children: [
|
||||
Icon(Symbols.code, size: 20),
|
||||
Gap(8),
|
||||
Text('HTML Page'),
|
||||
],
|
||||
),
|
||||
),
|
||||
DropdownMenuItem(
|
||||
value: 1,
|
||||
child: Row(
|
||||
children: [
|
||||
Icon(Symbols.link, size: 20),
|
||||
Gap(8),
|
||||
Text('Redirect Page'),
|
||||
],
|
||||
),
|
||||
),
|
||||
// Page type selector
|
||||
DropdownButtonFormField<int>(
|
||||
value: pageType.value,
|
||||
decoration: const InputDecoration(
|
||||
labelText: 'Page Type',
|
||||
border: OutlineInputBorder(
|
||||
borderRadius: BorderRadius.all(Radius.circular(12)),
|
||||
),
|
||||
),
|
||||
items: const [
|
||||
DropdownMenuItem(
|
||||
value: 0,
|
||||
child: Row(
|
||||
children: [
|
||||
Icon(Symbols.code, size: 20),
|
||||
Gap(8),
|
||||
Text('HTML Page'),
|
||||
],
|
||||
onChanged: (value) {
|
||||
if (value != null) {
|
||||
pageType.value = value;
|
||||
}
|
||||
},
|
||||
validator: (value) {
|
||||
if (value == null) {
|
||||
return 'Please select a page type';
|
||||
}
|
||||
return null;
|
||||
},
|
||||
).padding(all: 20),
|
||||
// Conditional form fields based on page type
|
||||
if (pageType.value == 0) ...[
|
||||
// HTML Page fields
|
||||
TextFormField(
|
||||
controller: pathController,
|
||||
decoration: const InputDecoration(
|
||||
labelText: 'Page Path',
|
||||
hintText: '/about, /contact, etc.',
|
||||
border: OutlineInputBorder(
|
||||
borderRadius: BorderRadius.all(
|
||||
Radius.circular(12),
|
||||
),
|
||||
),
|
||||
),
|
||||
validator: (value) {
|
||||
if (value == null || value.isEmpty) {
|
||||
return 'Please enter a page path';
|
||||
}
|
||||
if (!RegExp(
|
||||
r'^[a-zA-Z0-9\-/_]+$',
|
||||
).hasMatch(value)) {
|
||||
return 'Page path can only contain letters, numbers, hyphens, underscores, and slashes';
|
||||
}
|
||||
if (!value.startsWith('/')) {
|
||||
return 'Page path must start with /';
|
||||
}
|
||||
if (value.contains('//')) {
|
||||
return 'Page path cannot have consecutive slashes';
|
||||
}
|
||||
return null;
|
||||
},
|
||||
onTapOutside:
|
||||
(_) =>
|
||||
FocusManager.instance.primaryFocus
|
||||
?.unfocus(),
|
||||
).padding(horizontal: 20),
|
||||
const SizedBox(height: 16),
|
||||
TextFormField(
|
||||
controller: titleController,
|
||||
decoration: const InputDecoration(
|
||||
labelText: 'Page Title',
|
||||
hintText: 'About Us, Contact, etc.',
|
||||
border: OutlineInputBorder(
|
||||
borderRadius: BorderRadius.all(
|
||||
Radius.circular(12),
|
||||
),
|
||||
),
|
||||
),
|
||||
validator: (value) {
|
||||
if (value == null || value.isEmpty) {
|
||||
return 'Please enter a page title';
|
||||
}
|
||||
return null;
|
||||
},
|
||||
onTapOutside:
|
||||
(_) =>
|
||||
FocusManager.instance.primaryFocus
|
||||
?.unfocus(),
|
||||
).padding(horizontal: 20),
|
||||
const SizedBox(height: 16),
|
||||
TextFormField(
|
||||
controller: htmlController,
|
||||
decoration: const InputDecoration(
|
||||
labelText: 'Page Content (HTML)',
|
||||
hintText:
|
||||
'<h1>Hello World</h1><p>This is my page content...</p>',
|
||||
border: OutlineInputBorder(
|
||||
borderRadius: BorderRadius.all(
|
||||
Radius.circular(12),
|
||||
),
|
||||
),
|
||||
alignLabelWithHint: true,
|
||||
),
|
||||
maxLines: 10,
|
||||
onTapOutside:
|
||||
(_) =>
|
||||
FocusManager.instance.primaryFocus
|
||||
?.unfocus(),
|
||||
validator: (value) {
|
||||
if (value == null || value.isEmpty) {
|
||||
return 'Please enter HTML content for the page';
|
||||
}
|
||||
return null;
|
||||
},
|
||||
).padding(horizontal: 20),
|
||||
] else ...[
|
||||
// Redirect Page fields
|
||||
TextFormField(
|
||||
controller: pathController,
|
||||
decoration: const InputDecoration(
|
||||
labelText: 'Page Path',
|
||||
hintText: '/old-page, /redirect, etc.',
|
||||
border: OutlineInputBorder(
|
||||
borderRadius: BorderRadius.all(
|
||||
Radius.circular(12),
|
||||
),
|
||||
),
|
||||
prefixText: '/',
|
||||
),
|
||||
validator: (value) {
|
||||
if (value == null || value.isEmpty) {
|
||||
return 'Please enter a page path';
|
||||
}
|
||||
if (!RegExp(
|
||||
r'^[a-zA-Z0-9\-/_]+$',
|
||||
).hasMatch(value)) {
|
||||
return 'Page path can only contain letters, numbers, hyphens, underscores, and slashes';
|
||||
}
|
||||
if (!value.startsWith('/')) {
|
||||
return 'Page path must start with /';
|
||||
}
|
||||
if (value.contains('//')) {
|
||||
return 'Page path cannot have consecutive slashes';
|
||||
}
|
||||
return null;
|
||||
},
|
||||
onTapOutside:
|
||||
(_) =>
|
||||
FocusManager.instance.primaryFocus
|
||||
?.unfocus(),
|
||||
).padding(horizontal: 20),
|
||||
const SizedBox(height: 16),
|
||||
TextFormField(
|
||||
controller: targetController,
|
||||
decoration: const InputDecoration(
|
||||
labelText: 'Redirect Target',
|
||||
hintText: '/new-page, https://example.com, etc.',
|
||||
border: OutlineInputBorder(
|
||||
borderRadius: BorderRadius.all(
|
||||
Radius.circular(12),
|
||||
),
|
||||
),
|
||||
),
|
||||
validator: (value) {
|
||||
if (value == null || value.isEmpty) {
|
||||
return 'Please enter a redirect target';
|
||||
}
|
||||
if (!value.startsWith('/') &&
|
||||
!value.startsWith('http://') &&
|
||||
!value.startsWith('https://')) {
|
||||
return 'Target must be a relative path (/) or absolute URL (http/https)';
|
||||
}
|
||||
return null;
|
||||
},
|
||||
onTapOutside:
|
||||
(_) =>
|
||||
FocusManager.instance.primaryFocus
|
||||
?.unfocus(),
|
||||
).padding(horizontal: 20),
|
||||
],
|
||||
],
|
||||
).padding(vertical: 20),
|
||||
),
|
||||
Row(
|
||||
children: [
|
||||
if (page != null) ...[
|
||||
TextButton.icon(
|
||||
onPressed: deletePage,
|
||||
icon: const Icon(Symbols.delete_forever),
|
||||
label: const Text('Delete Page'),
|
||||
style: TextButton.styleFrom(
|
||||
foregroundColor: Colors.red,
|
||||
),
|
||||
).alignment(Alignment.centerRight),
|
||||
const Spacer(),
|
||||
] else
|
||||
const Spacer(),
|
||||
TextButton.icon(
|
||||
onPressed: savePage,
|
||||
icon: const Icon(Symbols.save),
|
||||
label: const Text('Save Page'),
|
||||
),
|
||||
),
|
||||
DropdownMenuItem(
|
||||
value: 1,
|
||||
child: Row(
|
||||
children: [
|
||||
Icon(Symbols.link, size: 20),
|
||||
Gap(8),
|
||||
Text('Redirect Page'),
|
||||
],
|
||||
),
|
||||
),
|
||||
],
|
||||
).padding(horizontal: 20, vertical: 12),
|
||||
onChanged: (value) {
|
||||
if (value != null) {
|
||||
pageType.value = value;
|
||||
}
|
||||
},
|
||||
validator: (value) {
|
||||
if (value == null) {
|
||||
return 'Please select a page type';
|
||||
}
|
||||
return null;
|
||||
},
|
||||
).padding(all: 20),
|
||||
// Conditional form fields based on page type
|
||||
if (pageType.value == 0) ...[
|
||||
// HTML Page fields
|
||||
TextFormField(
|
||||
controller: pathController,
|
||||
decoration: const InputDecoration(
|
||||
labelText: 'Page Path',
|
||||
hintText: '/about, /contact, etc.',
|
||||
border: OutlineInputBorder(
|
||||
borderRadius: BorderRadius.all(Radius.circular(12)),
|
||||
),
|
||||
),
|
||||
validator: (value) {
|
||||
if (value == null || value.isEmpty) {
|
||||
return 'Please enter a page path';
|
||||
}
|
||||
if (!RegExp(r'^[a-zA-Z0-9\-/_]+$').hasMatch(value)) {
|
||||
return 'Page path can only contain letters, numbers, hyphens, underscores, and slashes';
|
||||
}
|
||||
if (!value.startsWith('/')) {
|
||||
return 'Page path must start with /';
|
||||
}
|
||||
if (value.contains('//')) {
|
||||
return 'Page path cannot have consecutive slashes';
|
||||
}
|
||||
return null;
|
||||
},
|
||||
onTapOutside:
|
||||
(_) => FocusManager.instance.primaryFocus?.unfocus(),
|
||||
).padding(horizontal: 20),
|
||||
const SizedBox(height: 16),
|
||||
TextFormField(
|
||||
controller: titleController,
|
||||
decoration: const InputDecoration(
|
||||
labelText: 'Page Title',
|
||||
hintText: 'About Us, Contact, etc.',
|
||||
border: OutlineInputBorder(
|
||||
borderRadius: BorderRadius.all(Radius.circular(12)),
|
||||
),
|
||||
),
|
||||
validator: (value) {
|
||||
if (value == null || value.isEmpty) {
|
||||
return 'Please enter a page title';
|
||||
}
|
||||
return null;
|
||||
},
|
||||
onTapOutside:
|
||||
(_) => FocusManager.instance.primaryFocus?.unfocus(),
|
||||
).padding(horizontal: 20),
|
||||
const SizedBox(height: 16),
|
||||
TextFormField(
|
||||
controller: htmlController,
|
||||
decoration: const InputDecoration(
|
||||
labelText: 'Page Content (HTML)',
|
||||
hintText:
|
||||
'<h1>Hello World</h1><p>This is my page content...</p>',
|
||||
border: OutlineInputBorder(
|
||||
borderRadius: BorderRadius.all(Radius.circular(12)),
|
||||
),
|
||||
alignLabelWithHint: true,
|
||||
),
|
||||
maxLines: 10,
|
||||
onTapOutside:
|
||||
(_) => FocusManager.instance.primaryFocus?.unfocus(),
|
||||
validator: (value) {
|
||||
if (value == null || value.isEmpty) {
|
||||
return 'Please enter HTML content for the page';
|
||||
}
|
||||
return null;
|
||||
},
|
||||
).padding(horizontal: 20),
|
||||
] else ...[
|
||||
// Redirect Page fields
|
||||
TextFormField(
|
||||
controller: pathController,
|
||||
decoration: const InputDecoration(
|
||||
labelText: 'Page Path',
|
||||
hintText: '/old-page, /redirect, etc.',
|
||||
border: OutlineInputBorder(
|
||||
borderRadius: BorderRadius.all(Radius.circular(12)),
|
||||
),
|
||||
),
|
||||
validator: (value) {
|
||||
if (value == null || value.isEmpty) {
|
||||
return 'Please enter a page path';
|
||||
}
|
||||
if (!RegExp(r'^[a-zA-Z0-9\-/_]+$').hasMatch(value)) {
|
||||
return 'Page path can only contain letters, numbers, hyphens, underscores, and slashes';
|
||||
}
|
||||
if (!value.startsWith('/')) {
|
||||
return 'Page path must start with /';
|
||||
}
|
||||
if (value.contains('//')) {
|
||||
return 'Page path cannot have consecutive slashes';
|
||||
}
|
||||
return null;
|
||||
},
|
||||
onTapOutside:
|
||||
(_) => FocusManager.instance.primaryFocus?.unfocus(),
|
||||
).padding(horizontal: 20),
|
||||
const SizedBox(height: 16),
|
||||
TextFormField(
|
||||
controller: targetController,
|
||||
decoration: const InputDecoration(
|
||||
labelText: 'Redirect Target',
|
||||
hintText: '/new-page, https://example.com, etc.',
|
||||
border: OutlineInputBorder(
|
||||
borderRadius: BorderRadius.all(Radius.circular(12)),
|
||||
),
|
||||
),
|
||||
validator: (value) {
|
||||
if (value == null || value.isEmpty) {
|
||||
return 'Please enter a redirect target';
|
||||
}
|
||||
if (!value.startsWith('/') &&
|
||||
!value.startsWith('http://') &&
|
||||
!value.startsWith('https://')) {
|
||||
return 'Target must be a relative path (/) or absolute URL (http/https)';
|
||||
}
|
||||
return null;
|
||||
},
|
||||
onTapOutside:
|
||||
(_) => FocusManager.instance.primaryFocus?.unfocus(),
|
||||
).padding(horizontal: 20),
|
||||
Row(
|
||||
children: [
|
||||
if (page != null) ...[
|
||||
TextButton.icon(
|
||||
onPressed: deletePage,
|
||||
icon: const Icon(Symbols.delete_forever),
|
||||
label: const Text('Delete Page'),
|
||||
style: TextButton.styleFrom(
|
||||
foregroundColor: Colors.red,
|
||||
),
|
||||
).alignment(Alignment.centerRight),
|
||||
const Spacer(),
|
||||
] else
|
||||
const Spacer(),
|
||||
TextButton.icon(
|
||||
onPressed: savePage,
|
||||
icon: const Icon(Symbols.save),
|
||||
label: const Text('Save Page'),
|
||||
),
|
||||
],
|
||||
).padding(horizontal: 20, vertical: 16),
|
||||
],
|
||||
],
|
||||
),
|
||||
),
|
||||
],
|
||||
),
|
||||
),
|
||||
);
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user