commit 4dff95225f394412a3c0b930a4daef15f4002f5f
parent 2e4848288bd1450e0ba9a470ec14210dc2d6ffc9
Author: maydayv7 <maydayv7@gmail.com>
Date: Mon, 8 Dec 2025 23:34:28 +0530
Add new widgets and refactor
Diffstat:
40 files changed, 1150 insertions(+), 1698 deletions(-)
diff --git a/lib/data/models/canvas_models.dart b/lib/data/models/canvas_models.dart
@@ -1,5 +1,7 @@
import 'dart:ui';
+enum DragHandle { none, topLeft, topRight, bottomLeft, bottomRight, center }
+
class DrawingPoint {
final Offset offset;
final Paint paint;
diff --git a/lib/ui/pages/canvas_page.dart b/lib/ui/pages/canvas_page.dart
@@ -22,10 +22,14 @@ import 'package:creekui/services/flask_service.dart';
import 'package:creekui/ui/styles/variables.dart';
import 'package:creekui/data/models/canvas_models.dart';
import 'package:creekui/ui/painters/canvas_painter.dart';
+import 'package:creekui/utils/image_utils.dart';
import 'package:creekui/ui/widgets/canvas/manipulating_box.dart';
import 'package:creekui/ui/widgets/canvas/canvas_bottom_bar.dart';
import 'package:creekui/ui/widgets/canvas/asset_picker_sheet.dart';
+import 'package:creekui/ui/widgets/app_bar.dart';
+import 'package:creekui/ui/widgets/dialog.dart';
+import 'package:creekui/ui/widgets/text_field.dart';
import './canvas_toolbar/magic_draw_overlay.dart';
import './canvas_toolbar/text_tools_overlay.dart';
import 'project_file_page.dart';
@@ -93,7 +97,6 @@ class _CanvasPageState extends State<CanvasPage> {
// Tools
bool _isMagicDrawActive = false;
bool _isTextToolsActive = false;
-
bool _isMagicPanelDisabled = false;
bool _isViewMode = false;
@@ -160,30 +163,18 @@ class _CanvasPageState extends State<CanvasPage> {
}
Future<bool> _confirmDiscardMagicDraw() async {
- if (_magicPaths.isEmpty) return true; // nothing drawn – no popup
-
- final result = await showDialog<bool>(
- context: context,
- builder:
- (context) => AlertDialog(
- title: const Text("Discard Magic Draw?"),
- content: const Text(
- "Leaving Magic Draw will remove your sketch. Continue?",
- ),
- actions: [
- TextButton(
- onPressed: () => Navigator.pop(context, false),
- child: const Text("Stay"),
- ),
- TextButton(
- onPressed: () => Navigator.pop(context, true),
- child: const Text("Discard"),
- ),
- ],
- ),
- );
-
- return result == true;
+ if (_magicPaths.isEmpty) return true;
+ return await ShowDialog.show<bool>(
+ context,
+ title: "Discard Magic Draw?",
+ description: "Leaving Magic Draw will remove your sketch. Continue?",
+ primaryButtonText: "Discard",
+ isDestructive: true,
+ onPrimaryPressed: () => Navigator.pop(context, true),
+ secondaryButtonText: "Stay",
+ onSecondaryPressed: () => Navigator.pop(context, false),
+ ) ??
+ false;
}
Future<void> _analyzeCanvas() async {
@@ -551,70 +542,49 @@ class _CanvasPageState extends State<CanvasPage> {
return;
}
- showDialog(
- context: context,
- builder:
- (context) => AlertDialog(
- title: const Text("Save Changes?"),
- content: const Text(
- "Do you want to save your canvas before leaving?",
- ),
- actions: [
- TextButton(
- onPressed: () {
- Navigator.pop(context); // Close dialog
- Navigator.pop(context); // Leave page
- },
- child: const Text(
- "Discard",
- style: TextStyle(color: Colors.red),
- ),
- ),
- FilledButton(
- onPressed: () async {
- Navigator.pop(context); // Close dialog
- await _saveCanvas(); // Save
- // Note: The redirect logic is handled in _saveCanvas for new files.
- // For existing files, we pop here.
- if (mounted && widget.existingFile != null) {
- Navigator.pop(context);
- }
- },
- child: const Text("Save"),
- ),
- ],
- ),
+ ShowDialog.show(
+ context,
+ title: "Save Changes?",
+ description: "Do you want to save your canvas before leaving?",
+ primaryButtonText: "Save",
+ onPrimaryPressed: () async {
+ Navigator.pop(context); // Close dialog
+ await _saveCanvas(); // Save
+ if (mounted && widget.existingFile != null) {
+ Navigator.pop(context);
+ }
+ },
+ secondaryButtonText: "Discard",
+ onSecondaryPressed: () {
+ Navigator.pop(context); // Close dialog
+ Navigator.pop(context); // Leave page
+ },
);
}
Future<String?> _showNameDialog() async {
- TextEditingController nameController = TextEditingController(
- text: "Untitled Canvas",
- );
- return showDialog<String>(
+ final nameController = TextEditingController(text: "Untitled Canvas");
+ return await showDialog<String>(
context: context,
builder:
- (ctx) => AlertDialog(
- title: const Text("Save Canvas"),
- content: TextField(
- controller: nameController,
- autofocus: true,
- decoration: const InputDecoration(
- labelText: "Canvas Name",
+ (ctx) => Dialog(
+ backgroundColor: Colors.white,
+ shape: RoundedRectangleBorder(
+ borderRadius: BorderRadius.circular(Variables.radiusLarge),
+ ),
+ child: ShowDialog(
+ title: "Save Canvas",
+ content: CommonTextField(
hintText: "Enter a name for your file",
- border: OutlineInputBorder(),
+ controller: nameController,
+ autoFocus: true,
),
+ primaryButtonText: "Save",
+ onPrimaryPressed:
+ () => Navigator.pop(ctx, nameController.text.trim()),
+ secondaryButtonText: "Cancel",
+ onSecondaryPressed: () => Navigator.pop(ctx),
),
- actions: [
- TextButton(
- onPressed: () => Navigator.pop(ctx),
- child: const Text("Cancel"),
- ),
- FilledButton(
- onPressed: () => Navigator.pop(ctx, nameController.text.trim()),
- child: const Text("Save"),
- ),
- ],
),
);
}
@@ -1795,9 +1765,6 @@ class _CanvasPageState extends State<CanvasPage> {
],
),
),
- backgroundColor: Variables.background,
- foregroundColor: Variables.textPrimary,
- elevation: 0,
actions: [
SafeArea(
child: Row(
diff --git a/lib/ui/pages/canvas_toolbar/magic_draw_overlay.dart b/lib/ui/pages/canvas_toolbar/magic_draw_overlay.dart
@@ -155,21 +155,6 @@ class _MagicDrawToolsState extends State<MagicDrawTools> {
});
}
- void _handleToolTap(VoidCallback toolAction) {
- setState(() {
- _showStrokeSlider = false;
- _showModelMenu = false;
- if (_isViewMode) {
- _isViewMode = false;
- widget.onViewModeToggle(false);
- }
- if (widget.isMagicPanelDisabled) {
- widget.onMagicPanelActivityToggle(false);
- }
- });
- toolAction();
- }
-
@override
Widget build(BuildContext context) {
if (!widget.isActive) return const SizedBox.shrink();
@@ -210,8 +195,8 @@ class _MagicDrawToolsState extends State<MagicDrawTools> {
}
Widget _buildToolIcon(dynamic icon, bool isActive, VoidCallback onTap) {
- final Color activeColor = Colors.black;
- final Color inactiveColor = Colors.grey;
+ final Color activeColor = Variables.textPrimary;
+ final Color inactiveColor = Variables.textSecondary;
final Color iconColor = isActive ? activeColor : inactiveColor;
final Widget iconWidget =
@@ -229,7 +214,7 @@ class _MagicDrawToolsState extends State<MagicDrawTools> {
child: Container(
padding: const EdgeInsets.all(10),
decoration: BoxDecoration(
- color: isActive ? Colors.grey.shade200 : Colors.transparent,
+ color: isActive ? Variables.surfaceSubtle : Colors.transparent,
shape: BoxShape.circle,
),
child: iconWidget,
@@ -245,7 +230,7 @@ class _MagicDrawToolsState extends State<MagicDrawTools> {
children: [
Text(
widget.hasImageLayers ? 'Inpainting Models' : 'Sketch Models',
- style: const TextStyle(color: Colors.grey, fontSize: 12),
+ style: Variables.captionStyle.copyWith(fontSize: 12),
),
const SizedBox(height: 4),
@@ -270,10 +255,9 @@ class _MagicDrawToolsState extends State<MagicDrawTools> {
const SizedBox(width: 8),
Text(
model.name,
- style: TextStyle(
+ style: Variables.bodyStyle.copyWith(
fontWeight:
isSelected ? FontWeight.bold : FontWeight.normal,
- color: Colors.black87,
),
),
const Spacer(),
@@ -289,10 +273,11 @@ class _MagicDrawToolsState extends State<MagicDrawTools> {
),
child: Text(
model.badge!,
- style: TextStyle(
- color: Colors.blue.shade900,
+ style: const TextStyle(
+ color: Color(0xFF0D47A1), // Dark blue
fontSize: 10,
fontWeight: FontWeight.bold,
+ fontFamily: 'GeneralSans',
),
),
),
@@ -301,7 +286,7 @@ class _MagicDrawToolsState extends State<MagicDrawTools> {
),
);
}).toList(),
- Divider(height: 1, color: Colors.grey.shade100),
+ const Divider(height: 1, color: Variables.borderSubtle),
],
),
);
@@ -320,8 +305,9 @@ class _MagicDrawToolsState extends State<MagicDrawTools> {
child: Container(
padding: const EdgeInsets.all(8),
decoration: BoxDecoration(
- color: _showModelMenu ? Colors.grey.shade100 : Colors.white,
- border: Border.all(color: Colors.grey.shade300),
+ color:
+ _showModelMenu ? Variables.surfaceSubtle : Colors.white,
+ border: Border.all(color: Variables.borderSubtle),
borderRadius: BorderRadius.circular(8),
),
child: const Icon(
@@ -338,9 +324,10 @@ class _MagicDrawToolsState extends State<MagicDrawTools> {
controller: _promptController,
enabled: !widget.isProcessing,
onSubmitted: (_) => _handleSubmit(),
- decoration: const InputDecoration.collapsed(
+ style: Variables.bodyStyle,
+ decoration: InputDecoration.collapsed(
hintText: "tap imagination...",
- hintStyle: TextStyle(fontSize: 14, color: Colors.black54),
+ hintStyle: Variables.captionStyle.copyWith(fontSize: 14),
),
),
),
@@ -352,7 +339,7 @@ class _MagicDrawToolsState extends State<MagicDrawTools> {
width: 44,
height: 44,
decoration: const BoxDecoration(
- color: Color(0xFF2B2B2B),
+ color: Variables.textPrimary,
shape: BoxShape.circle,
),
child:
@@ -374,7 +361,7 @@ class _MagicDrawToolsState extends State<MagicDrawTools> {
],
),
),
- Divider(height: 1, color: Colors.grey.shade200),
+ const Divider(height: 1, color: Variables.borderSubtle),
Padding(
padding: const EdgeInsets.symmetric(horizontal: 16, vertical: 12),
child: Row(
@@ -461,7 +448,7 @@ class _MagicDrawToolsState extends State<MagicDrawTools> {
decoration: BoxDecoration(
color:
_showStrokeSlider
- ? Colors.grey.shade200
+ ? Variables.surfaceSubtle
: Colors.transparent,
shape: BoxShape.circle,
),
@@ -469,7 +456,7 @@ class _MagicDrawToolsState extends State<MagicDrawTools> {
width: 10,
height: 10,
decoration: const BoxDecoration(
- color: Colors.black87,
+ color: Variables.textPrimary,
shape: BoxShape.circle,
),
),
diff --git a/lib/ui/pages/canvas_toolbar/text_tools_overlay.dart b/lib/ui/pages/canvas_toolbar/text_tools_overlay.dart
@@ -47,7 +47,7 @@ class TextToolsOverlay extends StatelessWidget {
),
],
),
- // Use SingleChildScrollView + Row to prevent overflow if screen is narrow
+ // Prevent overflow if screen is narrow
child: SingleChildScrollView(
scrollDirection: Axis.horizontal,
child: Row(
@@ -61,7 +61,7 @@ class TextToolsOverlay extends StatelessWidget {
onPressed: onClose,
tooltip: "Done",
),
- Container(width: 1, height: 20, color: Colors.grey[300]),
+ Container(width: 1, height: 20, color: Variables.borderSubtle),
const SizedBox(width: 8),
IconButton(
@@ -75,13 +75,17 @@ class TextToolsOverlay extends StatelessWidget {
if (isTextSelected) ...[
const SizedBox(width: 8),
- Container(width: 1, height: 20, color: Colors.grey[300]),
+ Container(
+ width: 1,
+ height: 20,
+ color: Variables.borderSubtle,
+ ),
const SizedBox(width: 12),
const Icon(
Icons.text_fields,
size: 18,
- color: Colors.black54,
+ color: Variables.textSecondary,
),
SizedBox(
width: 100,
@@ -92,7 +96,7 @@ class TextToolsOverlay extends StatelessWidget {
enabledThumbRadius: 6,
),
activeTrackColor: Variables.textPrimary,
- inactiveTrackColor: Colors.grey[200],
+ inactiveTrackColor: Variables.borderSubtle,
thumbColor: Colors.black,
overlayShape: SliderComponentShape.noOverlay,
),
@@ -115,13 +119,20 @@ class TextToolsOverlay extends StatelessWidget {
decoration: BoxDecoration(
color: currentColor,
shape: BoxShape.circle,
- border: Border.all(color: Colors.grey[300]!, width: 1),
+ border: Border.all(
+ color: Variables.borderSubtle,
+ width: 1,
+ ),
),
),
),
const SizedBox(width: 12),
- Container(width: 1, height: 20, color: Colors.grey[300]),
+ Container(
+ width: 1,
+ height: 20,
+ color: Variables.borderSubtle,
+ ),
const SizedBox(width: 8),
],
],
@@ -144,13 +155,12 @@ class TextToolsOverlay extends StatelessWidget {
borderRadius: BorderRadius.vertical(top: Radius.circular(24)),
),
child: SafeArea(
- // Added SafeArea here for the bottom sheet content
child: Column(
mainAxisSize: MainAxisSize.min,
children: [
- const Text(
+ Text(
"Text Color",
- style: TextStyle(fontWeight: FontWeight.bold),
+ style: Variables.headerStyle.copyWith(fontSize: 16),
),
const SizedBox(height: 20),
BlockPicker(
diff --git a/lib/ui/pages/create_file_page.dart b/lib/ui/pages/create_file_page.dart
@@ -5,6 +5,7 @@ import 'package:creekui/services/project_service.dart';
import 'package:creekui/ui/styles/variables.dart';
import 'package:creekui/ui/widgets/search_bar.dart';
import 'package:creekui/ui/widgets/project_selector.dart';
+import 'package:creekui/ui/widgets/app_bar.dart';
import 'canvas_page.dart';
import 'define_brand_page.dart';
@@ -183,22 +184,13 @@ class _CreateFilePageState extends State<CreateFilePage> {
return Scaffold(
backgroundColor: theme.scaffoldBackgroundColor,
- appBar: AppBar(
- backgroundColor: theme.scaffoldBackgroundColor,
- elevation: 0,
+ appBar: CustomAppBar(
+ title: 'Create Files',
+ showBack: true,
leading: IconButton(
icon: Icon(Icons.arrow_back, color: theme.colorScheme.onSurface),
onPressed: () => Navigator.pop(context),
),
- title: Text(
- 'Create Files',
- style: Variables.headerStyle.copyWith(
- fontSize: 16,
- color: theme.colorScheme.onSurface,
- ),
- ),
-
- // Show project chooser only when ID not passed
actions: [
if (widget.projectId == null)
Padding(
@@ -409,7 +401,7 @@ class _ProjectSelectionModalContentState
if (result != null && result is Map) {
widget.onProjectSelected(result["id"], result["title"]);
} else {
- // If project is created but not selected immediately, refreshing the list is a safe bet.
+ // If project is created but not selected immediately, refreshing the list is a safe bet
setState(() {
_selectorKey = UniqueKey();
});
diff --git a/lib/ui/pages/define_brand_page.dart b/lib/ui/pages/define_brand_page.dart
@@ -4,6 +4,9 @@ import 'package:creekui/services/project_service.dart';
import 'package:creekui/ui/styles/variables.dart';
import 'package:creekui/ui/widgets/text_field.dart';
import 'package:creekui/ui/widgets/primary_button.dart';
+import 'package:creekui/ui/widgets/app_bar.dart';
+import 'package:creekui/ui/widgets/tag_chip.dart';
+import 'package:creekui/ui/widgets/dialog.dart';
import 'project_detail_page.dart';
class DefineBrandPage extends StatefulWidget {
@@ -114,32 +117,47 @@ class _DefineBrandPageState extends State<DefineBrandPage> {
}
}
+ void _showAddKeywordDialog() {
+ final controller = TextEditingController();
+
+ ShowDialog.show(
+ context,
+ title: 'Add Keyword',
+ primaryButtonText: 'Add',
+ content: CommonTextField(
+ hintText: 'e.g., Minimal',
+ controller: controller,
+ autoFocus: true,
+ ),
+ onPrimaryPressed: () {
+ final val = controller.text.trim();
+ if (val.isNotEmpty && !_keywords.contains(val)) {
+ setState(() => _keywords.add(val));
+ }
+ Navigator.pop(context);
+ },
+ );
+ }
+
@override
Widget build(BuildContext context) {
return Scaffold(
backgroundColor: Variables.background,
+ appBar: CustomAppBar(
+ title: "",
+ showBack: true,
+ leading: IconButton(
+ icon: const Icon(
+ Icons.arrow_back_ios,
+ size: 24,
+ color: Variables.textSecondary,
+ ),
+ onPressed: () => Navigator.pop(context),
+ ),
+ ),
body: SafeArea(
child: Column(
children: [
- // Top Bar with Back Button
- Container(
- height: 48,
- padding: const EdgeInsets.symmetric(horizontal: 8, vertical: 8),
- child: Row(
- children: [
- IconButton(
- icon: Icon(
- Icons.arrow_back_ios,
- size: 24,
- color: Variables.textSecondary,
- ),
- onPressed: () => Navigator.pop(context),
- ),
- ],
- ),
- ),
-
- // Scrollable Content
Expanded(
child: SingleChildScrollView(
padding: const EdgeInsets.fromLTRB(16, 0, 16, 0),
@@ -271,36 +289,9 @@ class _DefineBrandPageState extends State<DefineBrandPage> {
runSpacing: 8,
children: [
for (final keyword in _keywords)
- Container(
- padding: const EdgeInsets.symmetric(
- horizontal: 12,
- vertical: 8,
- ),
- decoration: BoxDecoration(
- color: const Color(0xFFE0E7FF),
- borderRadius: BorderRadius.circular(48),
- ),
- child: Row(
- mainAxisSize: MainAxisSize.min,
- children: [
- Text(
- keyword,
- style: Variables.captionStyle.copyWith(
- fontSize: 12,
- color: Variables.textPrimary,
- ),
- ),
- const SizedBox(width: 8),
- GestureDetector(
- onTap: () => setState(() => _keywords.remove(keyword)),
- child: Icon(
- Icons.close,
- size: 16,
- color: Variables.textPrimary,
- ),
- ),
- ],
- ),
+ TagChip(
+ label: keyword,
+ onDelete: () => setState(() => _keywords.remove(keyword)),
),
GestureDetector(
onTap: _showAddKeywordDialog,
@@ -324,7 +315,11 @@ class _DefineBrandPageState extends State<DefineBrandPage> {
),
),
const SizedBox(width: 4),
- Icon(Icons.add, size: 14, color: Variables.textPrimary),
+ const Icon(
+ Icons.add,
+ size: 14,
+ color: Variables.textPrimary,
+ ),
],
),
),
@@ -365,58 +360,33 @@ class _DefineBrandPageState extends State<DefineBrandPage> {
const SizedBox(height: 8),
SizedBox(
height: 44,
- child: ListView.builder(
+ child: ListView.separated(
scrollDirection: Axis.horizontal,
itemCount: _competitorBrands.length,
+ separatorBuilder: (_, __) => const SizedBox(width: 8),
itemBuilder: (context, index) {
final brand = _competitorBrands[index];
- return Container(
- margin: const EdgeInsets.only(right: 8),
- padding: const EdgeInsets.symmetric(
- horizontal: 16,
- vertical: 8,
- ),
- decoration: BoxDecoration(
- color: Variables.surfaceSubtle,
- border: Border.all(color: Variables.borderSubtle),
- borderRadius: BorderRadius.circular(64),
- ),
- child: Row(
- mainAxisSize: MainAxisSize.min,
- children: [
- Container(
- width: 30,
- height: 30,
- decoration: const BoxDecoration(
- color: Colors.white,
- shape: BoxShape.circle,
- ),
- child: Center(
- child: Text(
- brand['initial'] ?? '',
- style: Variables.captionStyle.copyWith(
- fontWeight: FontWeight.bold,
- fontSize: 12,
- ),
- ),
- ),
- ),
- const SizedBox(width: 8),
- Text(brand['name'] ?? '', style: Variables.bodyStyle),
- const SizedBox(width: 8),
- GestureDetector(
- onTap:
- () => setState(
- () => _competitorBrands.removeAt(index),
- ),
- child: Icon(
- Icons.close,
- size: 16,
- color: Variables.textSecondary,
+ return TagChip(
+ label: brand['name'] ?? '',
+ icon: Container(
+ width: 30,
+ height: 30,
+ decoration: const BoxDecoration(
+ color: Colors.white,
+ shape: BoxShape.circle,
+ ),
+ child: Center(
+ child: Text(
+ brand['initial'] ?? '',
+ style: Variables.captionStyle.copyWith(
+ fontWeight: FontWeight.bold,
+ fontSize: 12,
),
),
- ],
+ ),
),
+ onDelete:
+ () => setState(() => _competitorBrands.removeAt(index)),
);
},
),
@@ -425,35 +395,4 @@ class _DefineBrandPageState extends State<DefineBrandPage> {
],
);
}
-
- void _showAddKeywordDialog() {
- final controller = TextEditingController();
- showDialog<void>(
- context: context,
- builder:
- (context) => AlertDialog(
- title: const Text('Add Keyword'),
- content: TextField(
- controller: controller,
- autofocus: true,
- decoration: const InputDecoration(hintText: 'e.g., Minimal'),
- ),
- actions: [
- TextButton(
- onPressed: () => Navigator.pop(context),
- child: const Text('Cancel'),
- ),
- ElevatedButton(
- onPressed: () {
- final val = controller.text.trim();
- if (val.isNotEmpty && !_keywords.contains(val))
- setState(() => _keywords.add(val));
- Navigator.pop(context);
- },
- child: const Text('Add'),
- ),
- ],
- ),
- );
- }
}
diff --git a/lib/ui/pages/home_page.dart b/lib/ui/pages/home_page.dart
@@ -15,6 +15,8 @@ import 'package:creekui/ui/widgets/file_card.dart';
import 'package:creekui/ui/widgets/project_card.dart';
import 'package:creekui/ui/widgets/empty_state.dart';
import 'package:creekui/ui/widgets/section_header.dart';
+import 'package:creekui/ui/widgets/dialog.dart';
+import 'package:creekui/ui/widgets/text_field.dart';
import 'package:creekui/ui/pages/settings_page.dart';
import 'project_detail_page.dart';
import 'define_brand_page.dart';
@@ -138,9 +140,7 @@ class _HomePageState extends State<HomePage> {
try {
final bytes = await File(filePath).readAsBytes();
final image = img.decodeImage(bytes);
- if (image != null) {
- return '${image.width} x ${image.height} px';
- }
+ if (image != null) return '${image.width} x ${image.height} px';
} catch (e) {
debugPrint('Error getting dimensions: $e');
}
@@ -150,16 +150,13 @@ class _HomePageState extends State<HomePage> {
String _formatTimeAgo(DateTime dateTime) {
final now = DateTime.now();
final difference = now.difference(dateTime);
-
- if (difference.inDays > 0) {
+ if (difference.inDays > 0)
return 'Edited ${difference.inDays} ${difference.inDays == 1 ? 'day' : 'days'} ago';
- } else if (difference.inHours > 0) {
+ if (difference.inHours > 0)
return 'Edited ${difference.inHours} ${difference.inHours == 1 ? 'hour' : 'hours'} ago';
- } else if (difference.inMinutes > 0) {
+ if (difference.inMinutes > 0)
return 'Edited ${difference.inMinutes} ${difference.inMinutes == 1 ? 'minute' : 'minutes'} ago';
- } else {
- return 'Edited just now';
- }
+ return 'Edited just now';
}
String _getProjectBreadcrumb(FileModel file) {
@@ -169,9 +166,8 @@ class _HomePageState extends State<HomePage> {
// If the project has a parent, it's an event. Show Parent / Event
if (project.parentId != null) {
final parentProject = _projectMap[project.parentId!];
- if (parentProject != null) {
+ if (parentProject != null)
return '${parentProject.title} / ${project.title}';
- }
}
// Otherwise just the project name
return project.title;
@@ -187,9 +183,7 @@ class _HomePageState extends State<HomePage> {
projectDescription: null,
),
),
- ).then((result) {
- _loadData();
- });
+ ).then((_) => _loadData());
}
void _openProject(ProjectModel project) {
@@ -207,152 +201,73 @@ class _HomePageState extends State<HomePage> {
// Rename Project Logic
Future<void> _renameProject(ProjectModel project) async {
final controller = TextEditingController(text: project.title);
- final theme = Theme.of(context);
- final didRename = await showDialog<bool>(
- context: context,
- builder:
- (ctx) => AlertDialog(
- backgroundColor: theme.cardColor,
- title: Text(
- 'Rename Project',
- style: Variables.headerStyle.copyWith(fontSize: 18),
- ),
- content: TextField(
- controller: controller,
- autofocus: true,
- style: Variables.bodyStyle,
- decoration: InputDecoration(
- hintText: 'Enter new name',
- hintStyle: TextStyle(
- color: theme.colorScheme.onSurface.withValues(alpha: 0.5),
- ),
- enabledBorder: UnderlineInputBorder(
- borderSide: BorderSide(
- color: theme.colorScheme.primary.withValues(alpha: 0.5),
- ),
- ),
- focusedBorder: UnderlineInputBorder(
- borderSide: BorderSide(color: theme.colorScheme.primary),
- ),
- ),
- textCapitalization: TextCapitalization.sentences,
- ),
- actions: [
- TextButton(
- child: Text(
- 'Cancel',
- style: Variables.bodyStyle.copyWith(
- color: theme.colorScheme.onSurface.withValues(alpha: 0.7),
- ),
- ),
- onPressed: () => Navigator.pop(ctx, false),
- ),
- TextButton(
- child: Text(
- 'Save',
- style: Variables.bodyStyle.copyWith(
- color: theme.colorScheme.primary,
- ),
- ),
- onPressed: () {
- if (controller.text.trim().isNotEmpty) {
- Navigator.pop(ctx, true);
- }
- },
- ),
- ],
- ),
- );
-
- if (didRename == true && project.id != null) {
- setState(() => _isLoading = true);
- try {
- await _projectService.updateProjectDetails(
- project.id!,
- title: controller.text.trim(),
- );
- if (mounted) {
- ScaffoldMessenger.of(
- context,
- ).showSnackBar(const SnackBar(content: Text('Project renamed')));
- }
- _loadData();
- } catch (e) {
- if (mounted) {
- setState(() => _isLoading = false);
- ScaffoldMessenger.of(
- context,
- ).showSnackBar(SnackBar(content: Text('Error renaming: $e')));
+ await ShowDialog.show(
+ context,
+ title: 'Rename Project',
+ primaryButtonText: 'Save',
+ content: CommonTextField(
+ hintText: 'Enter new name',
+ controller: controller,
+ autoFocus: true,
+ ),
+ onPrimaryPressed: () async {
+ if (controller.text.trim().isNotEmpty) {
+ try {
+ await _projectService.updateProjectDetails(
+ project.id!,
+ title: controller.text.trim(),
+ );
+ if (mounted) {
+ Navigator.pop(context);
+ ScaffoldMessenger.of(
+ context,
+ ).showSnackBar(const SnackBar(content: Text('Project renamed')));
+ _loadData();
+ }
+ } catch (e) {
+ if (mounted)
+ ScaffoldMessenger.of(
+ context,
+ ).showSnackBar(SnackBar(content: Text('Error renaming: $e')));
+ }
}
- }
- }
+ },
+ );
}
// Delete Project Logic
Future<void> _deleteProject(ProjectModel project) async {
- final theme = Theme.of(context);
- final confirm = await showDialog<bool>(
- context: context,
- builder:
- (ctx) => AlertDialog(
- backgroundColor: theme.cardColor,
- title: Text(
- 'Delete Project',
- style: Variables.headerStyle.copyWith(fontSize: 18),
- ),
- content: Text(
- 'Are you sure you want to delete "${project.title}"? This cannot be undone.',
- style: Variables.bodyStyle.copyWith(
- color: theme.colorScheme.onSurface.withValues(alpha: 0.8),
- ),
- ),
- actions: [
- TextButton(
- child: Text(
- 'Cancel',
- style: Variables.bodyStyle.copyWith(
- color: theme.colorScheme.onSurface.withValues(alpha: 0.7),
- ),
- ),
- onPressed: () => Navigator.pop(ctx, false),
- ),
- TextButton(
- child: Text(
- 'Delete',
- style: Variables.bodyStyle.copyWith(color: Colors.red),
- ),
- onPressed: () => Navigator.pop(ctx, true),
- ),
- ],
- ),
- );
-
- if (confirm == true && project.id != null) {
- setState(() => _isLoading = true);
- try {
- await _projectService.deleteProject(project.id!);
- if (mounted) {
- ScaffoldMessenger.of(context).showSnackBar(
- const SnackBar(content: Text('Project deleted successfully')),
- );
- }
- _loadData();
- } catch (e) {
- if (mounted) {
- setState(() => _isLoading = false);
- ScaffoldMessenger.of(
- context,
- ).showSnackBar(SnackBar(content: Text('Error deleting project: $e')));
+ await ShowDialog.show(
+ context,
+ title: 'Delete Project',
+ description:
+ 'Are you sure you want to delete "${project.title}"? This cannot be undone.',
+ primaryButtonText: 'Delete',
+ isDestructive: true,
+ onPrimaryPressed: () async {
+ try {
+ await _projectService.deleteProject(project.id!);
+ if (mounted) {
+ Navigator.pop(context);
+ ScaffoldMessenger.of(context).showSnackBar(
+ const SnackBar(content: Text('Project deleted successfully')),
+ );
+ _loadData();
+ }
+ } catch (e) {
+ if (mounted)
+ ScaffoldMessenger.of(context).showSnackBar(
+ SnackBar(content: Text('Error deleting project: $e')),
+ );
}
- }
- }
+ },
+ );
}
Future<void> _openFile(FileModel file) async {
double width = 1080;
double height = 1920;
-
try {
final f = File(file.filePath);
if (await f.exists()) {
@@ -426,8 +341,8 @@ class _HomePageState extends State<HomePage> {
Widget build(BuildContext context) {
final theme = Theme.of(context);
final isDark = theme.brightness == Brightness.dark;
-
final bool isSearching = _searchQuery.isNotEmpty;
+
final List<FileModel> filteredFiles =
isSearching
? _recentFiles
@@ -518,17 +433,13 @@ class _HomePageState extends State<HomePage> {
padding: const EdgeInsets.symmetric(horizontal: 16),
child: CommonSearchBar(
controller: _searchController,
- onChanged: (value) {
- setState(() {
- _searchQuery = value.trim();
- });
- },
+ onChanged:
+ (value) =>
+ setState(() => _searchQuery = value.trim()),
),
),
),
-
const SliverToBoxAdapter(child: SizedBox(height: 12)),
-
if (isSearching)
SliverPadding(
padding: const EdgeInsets.symmetric(horizontal: 16),
@@ -541,14 +452,13 @@ class _HomePageState extends State<HomePage> {
title: "No results found",
subtitle: "Try searching for something else",
),
-
if (filteredProjects.isNotEmpty) ...[
Text(
"Projects & Events",
style: Variables.bodyStyle.copyWith(
fontWeight: FontWeight.w600,
color: theme.colorScheme.onSurface
- .withValues(alpha: 0.7),
+ .withOpacity(0.7),
),
),
const SizedBox(height: 8),
@@ -578,7 +488,7 @@ class _HomePageState extends State<HomePage> {
style: Variables.bodyStyle.copyWith(
fontWeight: FontWeight.w600,
color: theme.colorScheme.onSurface
- .withValues(alpha: 0.7),
+ .withOpacity(0.7),
),
),
const SizedBox(height: 8),
@@ -685,10 +595,7 @@ class _HomePageState extends State<HomePage> {
),
),
const SizedBox(height: 24),
- SectionHeader(
- title: 'Explore templates',
- onTap: () {},
- ),
+ const SectionHeader(title: 'Explore templates'),
const SizedBox(height: 12),
_buildTemplatesSection(theme, isDark),
const SizedBox(height: 24),
@@ -706,7 +613,7 @@ class _HomePageState extends State<HomePage> {
? null
: FloatingActionButton(
onPressed: _createNewProject,
- backgroundColor: isDark ? Colors.grey[900] : Colors.grey[900],
+ backgroundColor: Colors.grey[900],
foregroundColor: Colors.white,
child: const Icon(Icons.add, size: 24),
),
@@ -726,7 +633,6 @@ class _HomePageState extends State<HomePage> {
border: Border.all(
color: isDark ? Variables.borderDark : Variables.borderSubtle,
width: 1,
- style: BorderStyle.solid,
),
),
child: Column(
@@ -735,7 +641,7 @@ class _HomePageState extends State<HomePage> {
Icon(
Icons.add_circle_outline,
size: 32,
- color: theme.colorScheme.primary.withValues(alpha: 0.7),
+ color: theme.colorScheme.primary.withOpacity(0.7),
),
const SizedBox(height: 8),
Text(
@@ -743,7 +649,7 @@ class _HomePageState extends State<HomePage> {
style: Variables.bodyStyle.copyWith(
fontSize: 12,
fontWeight: FontWeight.w500,
- color: theme.colorScheme.onSurface.withValues(alpha: 0.7),
+ color: theme.colorScheme.onSurface.withOpacity(0.7),
),
),
],
@@ -804,8 +710,8 @@ class _HomePageState extends State<HomePage> {
child: Icon(
Icons.image_outlined,
size: 32,
- color: theme.colorScheme.onSurface.withValues(
- alpha: 0.3,
+ color: theme.colorScheme.onSurface.withOpacity(
+ 0.3,
),
),
);
@@ -829,7 +735,7 @@ class _HomePageState extends State<HomePage> {
template['subtitle']!,
style: Variables.captionStyle.copyWith(
fontSize: 11,
- color: theme.colorScheme.onSurface.withValues(alpha: 0.6),
+ color: theme.colorScheme.onSurface.withOpacity(0.6),
height: 1.2,
),
maxLines: 1,
@@ -895,13 +801,10 @@ class _SeeAllPageState extends State<_SeeAllPage> {
if (p.id != null) {
final events = await widget.projectRepo.getEvents(p.id!);
List<String> eventImages = [];
- // Get 1 image from up to 4 distinct events
for (final event in events.take(4)) {
if (event.id != null) {
final imgs = await widget.imageRepo.getImages(event.id!);
- if (imgs.isNotEmpty) {
- eventImages.add(imgs.first.filePath);
- }
+ if (imgs.isNotEmpty) eventImages.add(imgs.first.filePath);
}
}
_projectPreviews[p.id!] = eventImages;
@@ -912,7 +815,6 @@ class _SeeAllPageState extends State<_SeeAllPage> {
final files = await widget.fileRepo.getRecentFiles(limit: 50);
final allProjects = await widget.projectRepo.getAllProjects();
_projectMap = {for (var p in allProjects) p.id!: p};
-
for (final file in files) {
try {
final f = File(file.filePath);
@@ -924,13 +826,11 @@ class _SeeAllPageState extends State<_SeeAllPage> {
String dims = 'Unknown';
String? previewPath;
if (data is Map) {
- if (data['width'] != null && data['height'] != null) {
+ if (data['width'] != null && data['height'] != null)
dims =
'${(data['width'] as num).toInt()} x ${(data['height'] as num).toInt()} px';
- }
- if (data['preview_path'] != null) {
+ if (data['preview_path'] != null)
previewPath = data['preview_path'];
- }
}
_fileMetadata[file.id] = {
'dimensions': dims,
@@ -940,12 +840,11 @@ class _SeeAllPageState extends State<_SeeAllPage> {
} else {
final bytes = await f.readAsBytes();
final image = img.decodeImage(bytes);
- if (image != null) {
+ if (image != null)
_fileMetadata[file.id] = {
'dimensions': '${image.width} x ${image.height} px',
'preview': file.filePath,
};
- }
}
}
} catch (_) {}
@@ -978,8 +877,6 @@ class _SeeAllPageState extends State<_SeeAllPage> {
@override
Widget build(BuildContext context) {
final theme = Theme.of(context);
- final isDark = theme.brightness == Brightness.dark;
-
return Scaffold(
appBar: AppBar(
title: Text(
diff --git a/lib/ui/pages/image_analysis_page.dart b/lib/ui/pages/image_analysis_page.dart
@@ -5,6 +5,8 @@ import 'package:image_picker/image_picker.dart';
import 'package:path_provider/path_provider.dart';
import 'package:creekui/services/analyze/image_analyzer.dart';
import 'package:creekui/ui/styles/variables.dart';
+import 'package:creekui/ui/widgets/app_bar.dart';
+import 'package:creekui/ui/widgets/tag_chip.dart';
class ImageAnalysisPage extends StatefulWidget {
const ImageAnalysisPage({super.key});
@@ -115,10 +117,7 @@ class _ImageAnalysisPageState extends State<ImageAnalysisPage> {
Icons.camera_alt_outlined,
color: Colors.black,
),
- title: const Text(
- "Take Photo",
- style: TextStyle(fontFamily: 'GeneralSans'),
- ),
+ title: Text("Take Photo", style: Variables.bodyStyle),
onTap: () {
Navigator.pop(ctx);
_pickImage(ImageSource.camera);
@@ -129,9 +128,9 @@ class _ImageAnalysisPageState extends State<ImageAnalysisPage> {
Icons.image_outlined,
color: Colors.black,
),
- title: const Text(
+ title: Text(
"Choose from Gallery",
- style: TextStyle(fontFamily: 'GeneralSans'),
+ style: Variables.bodyStyle,
),
onTap: () {
Navigator.pop(ctx);
@@ -149,22 +148,14 @@ class _ImageAnalysisPageState extends State<ImageAnalysisPage> {
Widget build(BuildContext context) {
final colorScheme = Theme.of(context).colorScheme;
return Scaffold(
- backgroundColor: Colors.white,
- appBar: AppBar(
- backgroundColor: Colors.white,
- elevation: 0,
+ backgroundColor: Variables.surfaceBackground,
+ appBar: CustomAppBar(
+ title: "Image Analysis",
+ showBack: true,
leading: IconButton(
icon: const Icon(Icons.arrow_back, color: Colors.black),
onPressed: () => Navigator.pop(context),
),
- title: const Text(
- "Image Analysis",
- style: TextStyle(
- color: Colors.black,
- fontFamily: 'GeneralSans',
- fontWeight: FontWeight.bold,
- ),
- ),
actions: [
if (_selectedImage != null && !_isAnalyzing)
IconButton(
@@ -185,7 +176,7 @@ class _ImageAnalysisPageState extends State<ImageAnalysisPage> {
width: double.infinity,
height: 250,
decoration: BoxDecoration(
- color: Colors.grey[100],
+ color: Variables.surfaceSubtle,
borderRadius: BorderRadius.circular(16),
boxShadow: [
BoxShadow(
@@ -214,8 +205,7 @@ class _ImageAnalysisPageState extends State<ImageAnalysisPage> {
const SizedBox(height: 12),
Text(
"Select image to analyze",
- style: TextStyle(
- fontFamily: 'GeneralSans',
+ style: Variables.bodyStyle.copyWith(
color: Colors.grey[500],
),
),
@@ -259,52 +249,17 @@ class _ImageAnalysisPageState extends State<ImageAnalysisPage> {
}
});
},
- child: Container(
- padding: const EdgeInsets.symmetric(
- horizontal: 12,
- vertical: 8,
- ),
- decoration: BoxDecoration(
- color:
- isSelected
- ? const Color(0xFFEEF0FF)
- : Colors.white,
- borderRadius: BorderRadius.circular(20),
- border: Border.all(
- color:
- isSelected
- ? const Color(0xFF7C4DFF)
- : Colors.grey[300]!,
- width: 1.0,
- ),
- ),
- child: Row(
- mainAxisSize: MainAxisSize.min,
- children: [
- if (isSelected) ...[
- const Icon(
- Icons.check,
- size: 14,
- color: Color(0xFF7C4DFF),
- ),
- const SizedBox(width: 4),
- ],
- Text(
- tag,
- style: TextStyle(
- fontSize: 13,
- fontWeight:
- isSelected
- ? FontWeight.w600
- : FontWeight.normal,
- color:
- isSelected
- ? const Color(0xFF7C4DFF)
- : Colors.black87,
- ),
- ),
- ],
- ),
+ child: TagChip(
+ label: tag,
+ icon:
+ isSelected
+ ? const Icon(
+ Icons.check,
+ size: 14,
+ color: Variables.chipText,
+ )
+ : null,
+ onDelete: () {},
),
);
}).toList(),
@@ -367,9 +322,9 @@ class _ImageAnalysisPageState extends State<ImageAnalysisPage> {
width: double.infinity,
padding: const EdgeInsets.all(16),
decoration: BoxDecoration(
- color: Variables.borderSubtle,
+ color: Variables.surfaceSubtle,
borderRadius: BorderRadius.circular(12),
- border: Border.all(color: Colors.grey[800]!),
+ border: Border.all(color: Variables.borderSubtle),
),
child: SelectableText(
prettyJson,
diff --git a/lib/ui/pages/image_details_page.dart b/lib/ui/pages/image_details_page.dart
@@ -5,12 +5,17 @@ import 'package:creekui/services/image_service.dart';
import 'package:creekui/services/note_service.dart';
import 'package:creekui/data/models/note_model.dart';
import 'package:creekui/data/models/image_model.dart';
+import 'package:creekui/data/models/canvas_models.dart';
import 'package:creekui/utils/image_actions_helper.dart';
+import 'package:creekui/utils/image_utils.dart';
import 'package:creekui/ui/styles/variables.dart';
import 'package:creekui/ui/widgets/primary_button.dart';
import 'package:creekui/ui/widgets/empty_state.dart';
import 'package:creekui/ui/widgets/selection_overlay_painter.dart';
import 'package:creekui/ui/widgets/note_input_sheet.dart';
+import 'package:creekui/ui/widgets/app_bar.dart';
+import 'package:creekui/ui/widgets/dialog.dart';
+import 'package:creekui/ui/widgets/tag_chip.dart';
class ImageDetailsPage extends StatefulWidget {
final String imagePath;
@@ -356,96 +361,60 @@ class _ImageDetailsPageState extends State<ImageDetailsPage> {
// Edit Tags Dialog
void _openEditTagsDialog() {
+ List<String> tempTags = List.from(_currentTags);
showDialog(
context: context,
- builder: (ctx) {
- List<String> tempTags = List.from(_currentTags);
- return StatefulBuilder(
- builder: (context, setState) {
- return AlertDialog(
- title: const Text(
- "Edit Tags",
- style: TextStyle(fontWeight: FontWeight.bold),
- ),
- content: SingleChildScrollView(
- child: Wrap(
- spacing: 8,
- runSpacing: 8,
- children:
- _allAvailableTags.map((tag) {
- final isSelected = tempTags.contains(tag);
- return GestureDetector(
- onTap:
- () => setState(
- () =>
- isSelected
- ? tempTags.remove(tag)
- : tempTags.add(tag),
- ),
- child: Container(
- padding: const EdgeInsets.symmetric(
- horizontal: 12,
- vertical: 8,
- ),
- decoration: BoxDecoration(
- color:
- isSelected
- ? const Color(0xFFEEF0FF)
- : Colors.white,
- borderRadius: BorderRadius.circular(20),
- border: Border.all(
- color:
- isSelected
- ? const Color(0xFF7C4DFF)
- : Colors.grey[300]!,
- ),
- ),
- child: Text(
- tag,
- style: TextStyle(
- fontSize: 13,
- fontWeight:
- isSelected
- ? FontWeight.w600
- : FontWeight.normal,
- color:
- isSelected
- ? const Color(0xFF7C4DFF)
- : Colors.black87,
- ),
- ),
- ),
- );
- }).toList(),
- ),
- ),
- actions: [
- TextButton(
- onPressed: () => Navigator.pop(context),
- child: const Text(
- "Cancel",
- style: TextStyle(color: Colors.grey),
- ),
+ builder:
+ (ctx) => StatefulBuilder(
+ builder: (context, setLocalState) {
+ return Dialog(
+ backgroundColor: Colors.white,
+ shape: RoundedRectangleBorder(
+ borderRadius: BorderRadius.circular(Variables.radiusLarge),
),
- TextButton(
- onPressed: () async {
- this.setState(() => _currentTags = tempTags);
+ child: ShowDialog(
+ title: "Edit Tags",
+ primaryButtonText: "Save",
+ onPrimaryPressed: () async {
+ setState(() => _currentTags = tempTags);
await _imageService.updateTags(widget.imageId, tempTags);
- if (context.mounted) Navigator.pop(context);
+ if (mounted) Navigator.pop(context);
},
- child: const Text(
- "Save",
- style: TextStyle(
- color: Colors.black,
- fontWeight: FontWeight.bold,
+ content: SingleChildScrollView(
+ child: Wrap(
+ spacing: 8,
+ runSpacing: 8,
+ children:
+ _allAvailableTags.map((tag) {
+ final isSelected = tempTags.contains(tag);
+ return GestureDetector(
+ onTap: () {
+ setLocalState(() {
+ isSelected
+ ? tempTags.remove(tag)
+ : tempTags.add(tag);
+ });
+ },
+ child: TagChip(
+ label: tag,
+ icon:
+ isSelected
+ ? const Icon(
+ Icons.check,
+ size: 14,
+ color: Variables.chipText,
+ )
+ : null,
+ onDelete: () {},
+ ),
+ );
+ }).toList(),
),
),
),
- ],
- );
- },
- );
- },
+ );
+ },
+ ),
);
}
@@ -457,25 +426,24 @@ class _ImageDetailsPageState extends State<ImageDetailsPage> {
return Scaffold(
// Prevents the main screen from pushing up when the keyboard opens
resizeToAvoidBottomInset: false,
- backgroundColor: Colors.white,
- appBar: AppBar(
- backgroundColor: Colors.white,
- elevation: 0,
+ backgroundColor: Variables.surfaceBackground,
+ appBar: CustomAppBar(
+ showBack: true,
leading: IconButton(
icon: const Icon(
Icons.arrow_back_ios_new,
size: 20,
- color: Colors.black,
+ color: Variables.textPrimary,
),
onPressed: () => Navigator.pop(context),
),
centerTitle: true,
- title:
+ titleWidget:
(!isSelectionModeActive && _imageModel != null)
? Container(
height: 40,
decoration: BoxDecoration(
- color: const Color(0xFFF3F4F6),
+ color: Variables.surfaceSubtle,
borderRadius: BorderRadius.circular(30),
),
child: InkWell(
@@ -495,17 +463,16 @@ class _ImageDetailsPageState extends State<ImageDetailsPage> {
width: 18,
height: 18,
colorFilter: const ColorFilter.mode(
- Colors.black,
+ Variables.textPrimary,
BlendMode.srcIn,
),
),
const SizedBox(width: 8),
- const Text(
+ Text(
"Send to File",
- style: TextStyle(
- color: Colors.black,
- fontWeight: FontWeight.w600,
+ style: Variables.headerStyle.copyWith(
fontSize: 14,
+ fontWeight: FontWeight.w600,
),
),
],
@@ -528,7 +495,7 @@ class _ImageDetailsPageState extends State<ImageDetailsPage> {
// 2. Cancel Selection (Any selection mode)
if (isSelectionModeActive)
IconButton(
- icon: const Icon(Icons.close, color: Colors.black),
+ icon: const Icon(Icons.close, color: Variables.textPrimary),
onPressed: _resetSelectionMode,
),
@@ -546,7 +513,7 @@ class _ImageDetailsPageState extends State<ImageDetailsPage> {
width: 20,
height: 20,
colorFilter: const ColorFilter.mode(
- Colors.black,
+ Variables.textPrimary,
BlendMode.srcIn,
),
),
@@ -558,11 +525,11 @@ class _ImageDetailsPageState extends State<ImageDetailsPage> {
() => ImageActionsHelper.renameImage(
context,
_imageModel!,
- () => _loadData(), // Refresh title after rename
+ () => _loadData(),
),
icon: const Icon(
Icons.drive_file_rename_outline,
- color: Colors.black,
+ color: Variables.textPrimary,
),
),
],
@@ -571,7 +538,7 @@ class _ImageDetailsPageState extends State<ImageDetailsPage> {
body:
_isLoading
? const Center(
- child: CircularProgressIndicator(color: Colors.black),
+ child: CircularProgressIndicator(color: Variables.textPrimary),
)
: Column(
children: [
@@ -822,7 +789,7 @@ class _ImageDetailsPageState extends State<ImageDetailsPage> {
color: const Color(0xFFF9F9F9),
borderRadius: BorderRadius.circular(16),
border: Border.all(
- color: Colors.grey[200]!,
+ color: Variables.borderSubtle,
),
),
child: Column(
@@ -843,15 +810,15 @@ class _ImageDetailsPageState extends State<ImageDetailsPage> {
child: const Icon(
Icons.edit_outlined,
size: 18,
- color: Colors.black54,
+ color: Variables.textSecondary,
),
),
],
),
const SizedBox(height: 12),
- Divider(
+ const Divider(
height: 1,
- color: Colors.grey[300],
+ color: Variables.borderSubtle,
),
const SizedBox(height: 16),
SizedBox(
@@ -870,40 +837,10 @@ class _ImageDetailsPageState extends State<ImageDetailsPage> {
children:
_currentTags
.map(
- (tag) => Container(
- padding:
- const EdgeInsets.symmetric(
- horizontal:
- 12,
- vertical: 6,
- ),
- decoration: BoxDecoration(
- color:
- const Color(
- 0xFFEEF0FF,
- ),
- borderRadius:
- BorderRadius.circular(
- 20,
- ),
- border: Border.all(
- color: const Color(
- 0xFF7C4DFF,
- ),
- ),
- ),
- child: Text(
- tag,
- style: const TextStyle(
- fontSize: 13,
- fontWeight:
- FontWeight
- .w500,
- color: Color(
- 0xFF7C4DFF,
- ),
- ),
- ),
+ (tag) => TagChip(
+ label: tag,
+ onDelete: () {},
+ icon: null,
),
)
.toList(),
@@ -950,12 +887,13 @@ class __NotesListSheetState extends State<_NotesListSheet> {
final index = widget.notes.indexWhere(
(n) => n.id == widget.highlightId,
);
- if (index != -1 && _scrollController.hasClients)
+ if (index != -1 && _scrollController.hasClients) {
_scrollController.animateTo(
index * 80.0,
duration: const Duration(milliseconds: 300),
curve: Curves.easeOut,
);
+ }
});
}
}
diff --git a/lib/ui/pages/image_save_page.dart b/lib/ui/pages/image_save_page.dart
@@ -1,12 +1,15 @@
import 'dart:io';
import 'package:flutter/material.dart';
import 'package:flutter/services.dart';
+import 'package:creekui/data/models/canvas_models.dart';
import 'package:creekui/services/image_service.dart';
import 'package:creekui/services/note_service.dart';
+import 'package:creekui/utils/image_utils.dart';
import 'package:creekui/ui/styles/variables.dart';
import 'package:creekui/ui/widgets/primary_button.dart';
import 'package:creekui/ui/widgets/selection_overlay_painter.dart';
import 'package:creekui/ui/widgets/note_input_sheet.dart';
+import 'package:creekui/ui/widgets/app_bar.dart';
import 'project_board_page.dart';
// Temporary note model
@@ -430,22 +433,17 @@ class _ImageSavePageState extends State<ImageSavePage> {
return Scaffold(
resizeToAvoidBottomInset: false,
- backgroundColor: Colors.white,
- appBar: AppBar(
- backgroundColor: Colors.white,
- elevation: 0,
+ backgroundColor: Variables.surfaceBackground,
+ appBar: CustomAppBar(
+ title: titleText,
leading: IconButton(
icon: const Icon(
Icons.arrow_back_ios_new,
size: 20,
- color: Colors.black,
+ color: Variables.textPrimary,
),
onPressed: () => Navigator.pop(context),
),
- title: Text(
- titleText,
- style: Variables.headerStyle.copyWith(fontSize: 20),
- ),
actions: [
// Confirm Selection Button (Visible only in resizing mode)
if (_isResizing && _finalSelectionRect != null)
diff --git a/lib/ui/pages/project_board_page.dart b/lib/ui/pages/project_board_page.dart
@@ -48,7 +48,7 @@ class _ProjectBoardPageState extends State<ProjectBoardPage> {
@override
void initState() {
super.initState();
- // Set initial view based on parameter, default to true (grid view)
+ // Set initial view, default to grid view
_showAlternateView = widget.initialShowAlternateView ?? true;
_initData();
}
@@ -158,13 +158,13 @@ class _ProjectBoardPageState extends State<ProjectBoardPage> {
Widget build(BuildContext context) {
if (_currentProject == null) {
return const Scaffold(
- backgroundColor: Variables.background,
+ backgroundColor: Variables.surfaceBackground,
body: Center(child: CircularProgressIndicator()),
);
}
return Scaffold(
- backgroundColor: Variables.background,
+ backgroundColor: Variables.surfaceBackground,
appBar: TopBar(
currentProjectId: _currentProject!.id!,
onBack: () => Navigator.pop(context),
@@ -220,7 +220,7 @@ class _ProjectBoardPageState extends State<ProjectBoardPage> {
Widget _buildCategorizedView() {
if (_isLoading) return const Center(child: CircularProgressIndicator());
if (_categorizedImages.isEmpty) {
- return EmptyState(
+ return const EmptyState(
icon: Icons.image_not_supported_outlined,
title: "No images found",
subtitle: "Try adding new images",
@@ -308,7 +308,6 @@ class _ProjectBoardPageState extends State<ProjectBoardPage> {
),
);
},
-
child: Container(
width: 120,
decoration: BoxDecoration(
diff --git a/lib/ui/pages/project_detail_page.dart b/lib/ui/pages/project_detail_page.dart
@@ -9,6 +9,9 @@ import 'package:creekui/services/project_service.dart';
import 'package:creekui/ui/styles/variables.dart';
import 'package:creekui/ui/widgets/empty_state.dart';
import 'package:creekui/ui/widgets/section_header.dart';
+import 'package:creekui/ui/widgets/app_bar.dart';
+import 'package:creekui/ui/widgets/dialog.dart';
+import 'package:creekui/ui/widgets/text_field.dart';
import 'package:creekui/ui/pages/settings_page.dart';
import 'package:creekui/ui/pages/home_page.dart';
import 'project_board_page.dart';
@@ -90,182 +93,45 @@ class _ProjectDetailPageState extends State<ProjectDetailPage> {
if (!mounted) return;
- await showModalBottomSheet(
- context: context,
- backgroundColor: Colors.transparent,
- isScrollControlled: true,
- builder:
- (context) => Container(
- decoration: const BoxDecoration(
- color: Color(0xFFFAFAFA),
- borderRadius: BorderRadius.only(
- topLeft: Radius.circular(24),
- topRight: Radius.circular(24),
- ),
- ),
- child: Padding(
- padding: EdgeInsets.only(
- bottom: MediaQuery.of(context).viewInsets.bottom,
- ),
- child: Column(
- mainAxisSize: MainAxisSize.min,
- crossAxisAlignment: CrossAxisAlignment.start,
- children: [
- Container(
- padding: const EdgeInsets.only(top: 8, bottom: 16),
- child: Center(
- child: Container(
- width: 48,
- height: 2,
- decoration: BoxDecoration(
- color: const Color(0xFF71717B),
- borderRadius: BorderRadius.circular(100),
- ),
- ),
- ),
- ),
- Container(
- padding: const EdgeInsets.symmetric(
- horizontal: 16,
- vertical: 8,
- ),
- decoration: const BoxDecoration(
- border: Border(
- bottom: BorderSide(color: Color(0xFFE4E4E7), width: 1),
- ),
- ),
- child: Text("Event Details", style: Variables.bodyStyle),
- ),
- Padding(
- padding: const EdgeInsets.all(16),
- child: Column(
- crossAxisAlignment: CrossAxisAlignment.start,
- children: [
- Container(
- height: 40,
- decoration: BoxDecoration(
- color: const Color(0xFFE4E4E7),
- borderRadius: BorderRadius.circular(8),
- ),
- child: TextField(
- controller: nameController,
- autofocus: true,
- style: Variables.bodyStyle.copyWith(fontSize: 12),
- decoration: InputDecoration(
- hintText: "Add Name*",
- hintStyle: Variables.bodyStyle.copyWith(
- fontSize: 12,
- color: const Color(0xFF71717B),
- ),
- contentPadding: const EdgeInsets.symmetric(
- horizontal: 16,
- vertical: 12,
- ),
- border: InputBorder.none,
- ),
- ),
- ),
- const SizedBox(height: 16),
- Container(
- height: 80,
- decoration: BoxDecoration(
- color: const Color(0xFFE4E4E7),
- borderRadius: BorderRadius.circular(8),
- ),
- child: TextField(
- controller: descriptionController,
- maxLines: null,
- expands: true,
- textAlignVertical: TextAlignVertical.top,
- style: Variables.bodyStyle.copyWith(fontSize: 12),
- decoration: InputDecoration(
- hintText: "Add Description",
- hintStyle: Variables.bodyStyle.copyWith(
- fontSize: 12,
- color: const Color(0xFF71717B),
- ),
- contentPadding: const EdgeInsets.symmetric(
- horizontal: 16,
- vertical: 12,
- ),
- border: InputBorder.none,
- ),
- ),
- ),
- ],
- ),
- ),
- Container(
- padding: const EdgeInsets.fromLTRB(16, 16, 16, 32),
- child: Row(
- children: [
- Expanded(
- child: GestureDetector(
- onTap: () => Navigator.pop(context),
- child: Container(
- height: 40,
- decoration: BoxDecoration(
- color: const Color(0xFFE4E4E7),
- borderRadius: BorderRadius.circular(1000),
- ),
- child: Center(
- child: Text(
- "Cancel",
- style: Variables.buttonTextStyle.copyWith(
- color: Variables.textPrimary,
- ),
- ),
- ),
- ),
- ),
- ),
- const SizedBox(width: 8),
- Expanded(
- child: GestureDetector(
- onTap: () async {
- if (nameController.text.trim().isNotEmpty) {
- try {
- await _projectService.createProject(
- nameController.text.trim(),
- description:
- descriptionController.text
- .trim()
- .isEmpty
- ? null
- : descriptionController.text.trim(),
- parentId: _project!.id,
- );
- if (context.mounted) {
- Navigator.pop(context);
- _loadData();
- }
- } catch (e) {
- debugPrint("Error creating event: $e");
- }
- }
- },
- child: Container(
- height: 40,
- decoration: BoxDecoration(
- color: const Color(0xFF27272A),
- borderRadius: BorderRadius.circular(1000),
- ),
- child: Center(
- child: Text(
- "Add Event",
- style: Variables.buttonTextStyle,
- ),
- ),
- ),
- ),
- ),
- ],
- ),
- ),
- ],
- ),
- ),
+ await ShowDialog.show(
+ context,
+ title: "Event Details",
+ primaryButtonText: "Add Event",
+ content: Column(
+ children: [
+ CommonTextField(
+ hintText: "Add Name*",
+ controller: nameController,
+ autoFocus: true,
+ ),
+ const SizedBox(height: 16),
+ CommonTextField(
+ hintText: "Add Description",
+ controller: descriptionController,
+ maxLines: 3,
),
+ ],
+ ),
+ onPrimaryPressed: () async {
+ if (nameController.text.trim().isNotEmpty) {
+ try {
+ await _projectService.createProject(
+ nameController.text.trim(),
+ description:
+ descriptionController.text.trim().isEmpty
+ ? null
+ : descriptionController.text.trim(),
+ parentId: _project!.id,
+ );
+ if (context.mounted) {
+ Navigator.pop(context);
+ _loadData();
+ }
+ } catch (e) {
+ debugPrint("Error creating event: $e");
+ }
+ }
+ },
);
}
@@ -294,7 +160,7 @@ class _ProjectDetailPageState extends State<ProjectDetailPage> {
Widget build(BuildContext context) {
if (_isLoading) {
return const Scaffold(
- backgroundColor: Color(0xFFFAFAFA),
+ backgroundColor: Variables.surfaceBackground,
body: Center(child: CircularProgressIndicator()),
);
}
@@ -302,14 +168,10 @@ class _ProjectDetailPageState extends State<ProjectDetailPage> {
if (_project == null) return const Scaffold(body: SizedBox());
return Scaffold(
- backgroundColor: const Color(0xFFFAFAFA),
- appBar: AppBar(
- title: Text(_project!.title, style: Variables.headerStyle),
- backgroundColor: const Color(0xFFFAFAFA),
- elevation: 0,
- leadingWidth: 50,
- titleSpacing: 0,
- automaticallyImplyLeading: false,
+ backgroundColor: Variables.surfaceBackground,
+ appBar: CustomAppBar(
+ title: _project!.title,
+ showBack: true,
leading: IconButton(
icon: const Icon(
Icons.arrow_back,
@@ -351,7 +213,7 @@ class _ProjectDetailPageState extends State<ProjectDetailPage> {
onRefresh: _loadData,
child: SingleChildScrollView(
physics: const AlwaysScrollableScrollPhysics(),
- padding: const EdgeInsets.only(left: 16, right: 16),
+ padding: const EdgeInsets.symmetric(horizontal: 16),
child: Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
@@ -451,12 +313,12 @@ class _ProjectDetailPageState extends State<ProjectDetailPage> {
child: Container(
decoration: BoxDecoration(
borderRadius: BorderRadius.circular(16),
- border: Border.all(color: const Color(0xFFE4E4E7), width: 1),
+ border: Border.all(color: Variables.borderSubtle, width: 1),
),
child: Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
- // Blob image container with dark background
+ // Blob image container
Container(
height: 115,
decoration: const BoxDecoration(
@@ -556,7 +418,7 @@ class _ProjectDetailPageState extends State<ProjectDetailPage> {
width: 328,
decoration: BoxDecoration(
borderRadius: BorderRadius.circular(16),
- border: Border.all(color: const Color(0xFFE4E4E7), width: 1),
+ border: Border.all(color: Variables.borderSubtle, width: 1),
),
clipBehavior: Clip.antiAlias,
child: Column(
@@ -591,7 +453,7 @@ class _ProjectDetailPageState extends State<ProjectDetailPage> {
),
// Event title
Padding(
- padding: const EdgeInsets.fromLTRB(16, 16, 16, 16),
+ padding: const EdgeInsets.all(16),
child: Text(
event.title,
style: Variables.bodyStyle.copyWith(
diff --git a/lib/ui/pages/project_file_page.dart b/lib/ui/pages/project_file_page.dart
@@ -12,6 +12,8 @@ import 'package:creekui/ui/styles/variables.dart';
import 'package:creekui/ui/widgets/search_bar.dart';
import 'package:creekui/ui/widgets/file_card.dart';
import 'package:creekui/ui/widgets/empty_state.dart';
+import 'package:creekui/ui/widgets/dialog.dart';
+import 'package:creekui/ui/widgets/text_field.dart';
import 'create_file_page.dart';
import 'canvas_page.dart';
@@ -51,9 +53,7 @@ class _ProjectFilePageState extends State<ProjectFilePage> {
super.dispose();
}
- // ---------------------------------------
- // LOAD EVERYTHING
- // ---------------------------------------
+ // Load data
Future<void> _loadEverything() async {
setState(() => _isLoading = true);
try {
@@ -77,9 +77,7 @@ class _ProjectFilePageState extends State<ProjectFilePage> {
if (mounted) setState(() => _isLoading = false);
}
- // ---------------------------------------
- // LOAD METADATA
- // ---------------------------------------
+ // File Metadata
Future<void> _loadMetadata(List<FileModel> list) async {
for (final file in list) {
try {
@@ -128,9 +126,6 @@ class _ProjectFilePageState extends State<ProjectFilePage> {
}
}
- // ---------------------------------------
- // SELECT EVENT
- // ---------------------------------------
Future<void> _onSelectEvent(ProjectModel event) async {
setState(() => _selectedEvent = event);
_eventFiles = await _fileService.getFiles(event.id!);
@@ -138,9 +133,6 @@ class _ProjectFilePageState extends State<ProjectFilePage> {
setState(() {});
}
- // ---------------------------------------
- // OPEN FILE
- // ---------------------------------------
void _openFile(FileModel file) {
Navigator.push(
context,
@@ -175,91 +167,57 @@ class _ProjectFilePageState extends State<ProjectFilePage> {
Future<void> _renameFile(FileModel file) async {
final controller = TextEditingController(text: file.name);
- final newName = await showDialog<String>(
- context: context,
- builder: (context) {
- return AlertDialog(
- title: Text(
- "Rename File",
- style: Variables.headerStyle.copyWith(fontSize: 18),
- ),
- content: TextField(
- controller: controller,
- autofocus: true,
- style: Variables.bodyStyle,
- decoration: const InputDecoration(
- labelText: "New file name",
- border: OutlineInputBorder(),
- ),
- ),
- actions: [
- TextButton(
- child: Text("Cancel", style: Variables.bodyStyle),
- onPressed: () => Navigator.pop(context),
- ),
- FilledButton(
- child: Text("Save", style: Variables.buttonTextStyle),
- onPressed: () => Navigator.pop(context, controller.text.trim()),
- ),
- ],
- );
+ await ShowDialog.show(
+ context,
+ title: "Rename File",
+ primaryButtonText: "Save",
+ content: CommonTextField(
+ hintText: "New file name",
+ controller: controller,
+ autoFocus: true,
+ ),
+ onPrimaryPressed: () async {
+ final newName = controller.text.trim();
+ if (newName.isNotEmpty) {
+ await _fileService.renameFile(file.id, newName);
+ await _loadEverything();
+ if (mounted) Navigator.pop(context);
+ }
},
);
-
- if (newName == null || newName.isEmpty) return;
- await _fileService.renameFile(file.id, newName);
- await _loadEverything();
}
Future<void> _deleteFile(FileModel file) async {
- final confirm = await showDialog<bool>(
- context: context,
- builder:
- (_) => AlertDialog(
- title: Text(
- "Delete File?",
- style: Variables.headerStyle.copyWith(fontSize: 18),
- ),
- content: Text(
- "This will permanently remove ${file.name}.",
- style: Variables.bodyStyle,
- ),
- actions: [
- TextButton(
- onPressed: () => Navigator.pop(context, false),
- child: Text("Cancel", style: Variables.bodyStyle),
- ),
- FilledButton(
- onPressed: () => Navigator.pop(context, true),
- child: Text("Delete", style: Variables.buttonTextStyle),
- ),
- ],
- ),
+ await ShowDialog.show(
+ context,
+ title: "Delete File?",
+ description: "This will permanently remove ${file.name}.",
+ primaryButtonText: "Delete",
+ isDestructive: true,
+ onPrimaryPressed: () async {
+ try {
+ await _fileService.deleteFile(file.id!);
+ final disk = File(file.filePath);
+ if (await disk.exists()) await disk.delete();
+
+ setState(() {
+ _allFiles.removeWhere((f) => f.id == file.id);
+ _eventFiles.removeWhere((f) => f.id == file.id);
+ });
+
+ if (mounted) {
+ Navigator.pop(context);
+ ScaffoldMessenger.of(
+ context,
+ ).showSnackBar(const SnackBar(content: Text("File deleted")));
+ }
+ } catch (e) {
+ debugPrint("Delete error: $e");
+ }
+ },
);
-
- if (confirm != true) return;
-
- try {
- await _fileService.deleteFile(file.id!);
- final disk = File(file.filePath);
- if (await disk.exists()) await disk.delete();
-
- setState(() {
- _allFiles.removeWhere((f) => f.id == file.id);
- _eventFiles.removeWhere((f) => f.id == file.id);
- });
-
- ScaffoldMessenger.of(
- context,
- ).showSnackBar(const SnackBar(content: Text("File deleted")));
- } catch (e) {
- debugPrint("Delete error: $e");
- }
}
- // ---------------------------------------
- // CREATE FILE
- // ---------------------------------------
void _navigateToCreateFile() {
Navigator.push(
context,
@@ -304,9 +262,7 @@ class _ProjectFilePageState extends State<ProjectFilePage> {
);
}
- // ---------------------------------------
- // BREADCRUMB
- // ---------------------------------------
+ // Breadcrumb
String _breadcrumbFor(FileModel file) {
final event = _events.firstWhere(
(e) => e.id == file.projectId,
@@ -335,13 +291,11 @@ class _ProjectFilePageState extends State<ProjectFilePage> {
return "${parent.title} / ${event.title}";
}
- // ---------------------------------------
- // UI
- // ---------------------------------------
+ // UI Builder
@override
Widget build(BuildContext context) {
return Scaffold(
- backgroundColor: const Color(0xFFF7F7F8),
+ backgroundColor: Variables.surfaceSubtle,
appBar: TopBar(
currentProjectId: widget.projectId,
onBack: () => Navigator.pop(context),
@@ -371,7 +325,7 @@ class _ProjectFilePageState extends State<ProjectFilePage> {
child: Container(
padding: const EdgeInsets.symmetric(horizontal: 20, vertical: 14),
decoration: BoxDecoration(
- color: const Color(0xFF27272A),
+ color: Variables.textPrimary,
borderRadius: BorderRadius.circular(50),
),
child: Row(
@@ -400,31 +354,22 @@ class _ProjectFilePageState extends State<ProjectFilePage> {
crossAxisAlignment: CrossAxisAlignment.start,
children: [
const SizedBox(height: 12),
-
// Search Bar
CommonSearchBar(
controller: _searchController,
hintText: "Search your files",
onChanged: (v) => setState(() => _search = v.trim()),
),
-
const SizedBox(height: 24),
-
- // -------------------------
- // ALL FILES
- // -------------------------
- const Text(
+ // All Files
+ Text(
"All Files",
- style: TextStyle(
+ style: Variables.bodyStyle.copyWith(
fontSize: 16,
fontWeight: FontWeight.w600,
- fontFamily: 'GeneralSans',
- color: Color(0xFF27272A),
),
),
-
const SizedBox(height: 12),
-
if (_filteredAllFiles().isEmpty)
EmptyState(
icon: Icons.folder_outlined,
@@ -444,24 +389,16 @@ class _ProjectFilePageState extends State<ProjectFilePage> {
.map((f) => _buildFileCard(f))
.toList(),
),
-
const SizedBox(height: 32),
-
- // -------------------------
- // FILES FOR EVENTS
- // -------------------------
- const Text(
+ // Files for Events
+ Text(
"Files for Events",
- style: TextStyle(
+ style: Variables.bodyStyle.copyWith(
fontSize: 16,
fontWeight: FontWeight.w600,
- fontFamily: 'GeneralSans',
- color: Color(0xFF27272A),
),
),
-
const SizedBox(height: 12),
-
if (_events.isEmpty)
const EmptyState(
icon: Icons.event_outlined,
@@ -472,13 +409,8 @@ class _ProjectFilePageState extends State<ProjectFilePage> {
Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
- // -----------------------
- // DROPDOWN
- // -----------------------
_buildEventDropdown(),
-
const SizedBox(height: 16),
-
_eventFiles.isEmpty
? const EmptyState(
icon: Icons.insert_drive_file_outlined,
@@ -494,7 +426,6 @@ class _ProjectFilePageState extends State<ProjectFilePage> {
),
],
),
-
const SizedBox(height: 100),
],
),
@@ -503,9 +434,7 @@ class _ProjectFilePageState extends State<ProjectFilePage> {
);
}
- // ---------------------------------------
- // DROPDOWN
- // ---------------------------------------
+ // Dropdown
Widget _buildEventDropdown() {
return Container(
height: 36,
@@ -555,9 +484,7 @@ class _ProjectFilePageState extends State<ProjectFilePage> {
);
}
- // ---------------------------------------
- // FILTER
- // ---------------------------------------
+ // Filter
List<FileModel> _filteredAllFiles() {
if (_search.isEmpty) return _allFiles;
return _allFiles
diff --git a/lib/ui/pages/project_tag_page.dart b/lib/ui/pages/project_tag_page.dart
@@ -133,7 +133,7 @@ class _ProjectTagPageState extends State<ProjectTagPage> {
}
return Scaffold(
- backgroundColor: Variables.background,
+ backgroundColor: Variables.surfaceBackground,
appBar: TopBar(
currentProjectId: widget.projectId,
titleOverride: widget.tag.toUpperCase(),
diff --git a/lib/ui/pages/settings_page.dart b/lib/ui/pages/settings_page.dart
@@ -3,6 +3,8 @@ import 'package:shared_preferences/shared_preferences.dart';
import 'package:creekui/ui/styles/variables.dart';
import 'package:creekui/ui/pages/image_analysis_page.dart';
import 'package:creekui/ui/widgets/primary_button.dart';
+import 'package:creekui/ui/widgets/app_bar.dart';
+import 'package:creekui/ui/widgets/text_field.dart';
class SettingsPage extends StatefulWidget {
const SettingsPage({super.key});
@@ -25,9 +27,7 @@ class _SettingsPageState extends State<SettingsPage> {
Future<void> _loadUserData() async {
final prefs = await SharedPreferences.getInstance();
String name = prefs.getString('user_name') ?? 'Alex';
- if (name.trim().isEmpty) {
- name = 'Alex';
- }
+ if (name.trim().isEmpty) name = 'Alex';
setState(() {
_nameController.text = name;
@@ -39,17 +39,10 @@ class _SettingsPageState extends State<SettingsPage> {
Future<void> _saveUserName() async {
final newName = _nameController.text.trim();
if (newName.isEmpty) return;
-
final prefs = await SharedPreferences.getInstance();
await prefs.setString('user_name', newName);
-
- setState(() {
- _originalName = newName;
- });
-
- if (mounted) {
- FocusScope.of(context).unfocus();
- }
+ setState(() => _originalName = newName);
+ if (mounted) FocusScope.of(context).unfocus();
}
@override
@@ -63,18 +56,7 @@ class _SettingsPageState extends State<SettingsPage> {
final bool hasChanges = _nameController.text.trim() != _originalName;
return Scaffold(
backgroundColor: Colors.white,
- appBar: AppBar(
- title: const Text(
- "Settings",
- style: TextStyle(
- fontFamily: 'GeneralSans',
- color: Variables.textPrimary,
- ),
- ),
- backgroundColor: Colors.white,
- elevation: 0,
- iconTheme: const IconThemeData(color: Variables.textPrimary),
- ),
+ appBar: const CustomAppBar(title: "Settings"),
body:
_isLoading
? const Center(child: CircularProgressIndicator())
@@ -93,58 +75,23 @@ class _SettingsPageState extends State<SettingsPage> {
),
),
const SizedBox(height: 16),
-
- // User Name Field
- const Text(
- "Your Name",
- style: TextStyle(
- fontFamily: 'GeneralSans',
- fontSize: 14,
- color: Variables.textSecondary,
- ),
- ),
- const SizedBox(height: 8),
- TextField(
+ CommonTextField(
+ label: "Your Name",
+ hintText: "Enter your name",
controller: _nameController,
- onChanged: (val) {
- setState(
- () {},
- ); // Trigger rebuild to show/hide checkmark
- },
- decoration: InputDecoration(
- hintText: "Enter your name",
- hintStyle: TextStyle(
- color: Variables.textSecondary.withValues(alpha: 0.5),
- ),
- filled: true,
- fillColor: Variables.surfaceSubtle,
- border: OutlineInputBorder(
- borderRadius: BorderRadius.circular(12),
- borderSide: BorderSide.none,
- ),
- contentPadding: const EdgeInsets.symmetric(
- horizontal: 16,
- vertical: 12,
- ),
- suffixIcon:
- hasChanges
- ? IconButton(
- icon: const Icon(
- Icons.check,
- color: Variables.textPrimary,
- ),
- onPressed: _saveUserName,
- tooltip: 'Save Name',
- )
- : null,
- ),
- style: const TextStyle(
- fontFamily: 'GeneralSans',
- fontSize: 16,
- color: Variables.textPrimary,
- ),
+ onChanged: (val) => setState(() {}),
+ suffixIcon:
+ hasChanges
+ ? IconButton(
+ icon: const Icon(
+ Icons.check,
+ color: Variables.textPrimary,
+ ),
+ onPressed: _saveUserName,
+ tooltip: 'Save Name',
+ )
+ : null,
),
-
const Spacer(),
// Test Analysis
diff --git a/lib/ui/pages/share_handler_page.dart b/lib/ui/pages/share_handler_page.dart
@@ -5,6 +5,7 @@ import 'package:creekui/services/instagram_download_service.dart';
import 'package:creekui/services/image_service.dart';
import 'package:creekui/ui/styles/variables.dart';
import 'package:creekui/ui/widgets/primary_button.dart';
+import 'package:creekui/ui/widgets/app_bar.dart';
import 'share_to_moodboard_page.dart';
import 'share_to_file_page.dart';
@@ -118,8 +119,8 @@ class _ShareHandlerPageState extends State<ShareHandlerPage> {
@override
Widget build(BuildContext context) {
return Scaffold(
- backgroundColor: Colors.white,
- appBar: AppBar(title: const Text("Processing")),
+ backgroundColor: Variables.background,
+ appBar: const CustomAppBar(title: "Processing", showBack: false),
body: Center(
child:
_hasError
@@ -158,7 +159,7 @@ class _ShareHandlerPageState extends State<ShareHandlerPage> {
: const Column(
mainAxisAlignment: MainAxisAlignment.center,
children: [
- CircularProgressIndicator(),
+ CircularProgressIndicator(color: Variables.textPrimary),
SizedBox(height: 20),
Text("Downloading media..."),
],
diff --git a/lib/ui/pages/share_to_file_page.dart b/lib/ui/pages/share_to_file_page.dart
@@ -11,6 +11,7 @@ import 'package:creekui/ui/widgets/search_bar.dart';
import 'package:creekui/ui/widgets/file_card.dart';
import 'package:creekui/ui/widgets/section_header.dart';
import 'package:creekui/ui/widgets/empty_state.dart';
+import 'package:creekui/ui/widgets/app_bar.dart';
import 'create_file_page.dart';
import 'canvas_page.dart';
@@ -279,25 +280,17 @@ class _ShareToFilePageState extends State<ShareToFilePage> {
@override
Widget build(BuildContext context) {
- final theme = Theme.of(context);
return Scaffold(
- backgroundColor: theme.scaffoldBackgroundColor,
- appBar: AppBar(
- backgroundColor: theme.scaffoldBackgroundColor,
- elevation: 0,
+ backgroundColor: Variables.surfaceBackground,
+ appBar: CustomAppBar(
+ title: 'Files',
leading: IconButton(
- icon: Icon(Icons.arrow_back, color: theme.colorScheme.onSurface),
+ icon: const Icon(Icons.arrow_back, color: Variables.textPrimary),
onPressed: () => Navigator.pop(context),
),
- title: Text(
- 'Files',
- style: Variables.headerStyle.copyWith(
- color: theme.colorScheme.onSurface,
- ),
- ),
actions: [
IconButton(
- icon: Icon(Icons.add, color: theme.colorScheme.onSurface, size: 28),
+ icon: const Icon(Icons.add, color: Variables.textPrimary, size: 28),
onPressed: _onAddPressed,
tooltip: "Create New File",
),
@@ -305,7 +298,9 @@ class _ShareToFilePageState extends State<ShareToFilePage> {
),
body:
_isLoading
- ? const Center(child: CircularProgressIndicator())
+ ? const Center(
+ child: CircularProgressIndicator(color: Variables.textPrimary),
+ )
: _allFiles.isEmpty
? _buildEmptyState()
: Column(
@@ -398,11 +393,11 @@ class _ShareToFilePageState extends State<ShareToFilePage> {
child: Column(
mainAxisAlignment: MainAxisAlignment.center,
children: [
- Icon(Icons.folder_open, size: 80, color: Colors.grey[300]),
+ const Icon(Icons.folder_open, size: 80, color: Colors.grey),
const SizedBox(height: 16),
- Text(
+ const Text(
"No files yet",
- style: TextStyle(color: Colors.grey[500], fontSize: 16),
+ style: TextStyle(color: Colors.grey, fontSize: 16),
),
const SizedBox(height: 8),
TextButton(
diff --git a/lib/ui/pages/share_to_moodboard_page.dart b/lib/ui/pages/share_to_moodboard_page.dart
@@ -4,6 +4,9 @@ import 'package:receive_sharing_intent/receive_sharing_intent.dart';
import 'package:creekui/services/project_service.dart';
import 'package:creekui/ui/styles/variables.dart';
import 'package:creekui/ui/widgets/project_selector.dart';
+import 'package:creekui/ui/widgets/app_bar.dart';
+import 'package:creekui/ui/widgets/dialog.dart';
+import 'package:creekui/ui/widgets/text_field.dart';
import 'image_save_page.dart';
class ShareToMoodboardPage extends StatefulWidget {
@@ -20,40 +23,27 @@ class _ShareToMoodboardPageState extends State<ShareToMoodboardPage> {
Future<void> _createNewProject() async {
final controller = TextEditingController();
- final String? title = await showDialog<String>(
- context: context,
- builder:
- (context) => AlertDialog(
- title: const Text(
- "New Project",
- style: TextStyle(fontFamily: 'GeneralSans'),
- ),
- content: TextField(
- controller: controller,
- decoration: const InputDecoration(hintText: "Project Title"),
- autofocus: true,
- textCapitalization: TextCapitalization.sentences,
- ),
- actions: [
- TextButton(
- onPressed: () => Navigator.pop(context),
- child: const Text("Cancel"),
- ),
- FilledButton(
- onPressed: () => Navigator.pop(context, controller.text.trim()),
- child: const Text("Create"),
- ),
- ],
- ),
+ await ShowDialog.show(
+ context,
+ title: "New Project",
+ primaryButtonText: "Create",
+ content: CommonTextField(
+ hintText: "Project Title",
+ controller: controller,
+ autoFocus: true,
+ ),
+ onPrimaryPressed: () async {
+ final title = controller.text.trim();
+ if (title.isNotEmpty) {
+ Navigator.pop(context); // Close dialog
+ final newId = await _projectService.createProject(title);
+ setState(() {
+ _selectorKey = UniqueKey();
+ });
+ _navigateToSavePage(newId, title);
+ }
+ },
);
-
- if (title != null && title.isNotEmpty) {
- final newId = await _projectService.createProject(title);
- setState(() {
- _selectorKey = UniqueKey();
- });
- _navigateToSavePage(newId, title);
- }
}
void _navigateToSavePage(
@@ -80,26 +70,17 @@ class _ShareToMoodboardPageState extends State<ShareToMoodboardPage> {
@override
Widget build(BuildContext context) {
- final theme = Theme.of(context);
-
return Scaffold(
- backgroundColor: theme.scaffoldBackgroundColor,
- appBar: AppBar(
- backgroundColor: theme.scaffoldBackgroundColor,
- elevation: 0,
+ backgroundColor: Variables.surfaceBackground,
+ appBar: CustomAppBar(
+ title: "MoodBoards",
leading: IconButton(
- icon: Icon(Icons.arrow_back, color: theme.colorScheme.onSurface),
+ icon: const Icon(Icons.arrow_back, color: Variables.textPrimary),
onPressed: () => Navigator.pop(context),
),
- title: Text(
- "MoodBoards",
- style: Variables.headerStyle.copyWith(
- color: theme.colorScheme.onSurface,
- ),
- ),
actions: [
IconButton(
- icon: Icon(Icons.add, color: theme.colorScheme.onSurface, size: 28),
+ icon: const Icon(Icons.add, color: Variables.textPrimary, size: 28),
onPressed: _createNewProject,
tooltip: "Create New Project",
),
diff --git a/lib/ui/pages/stylesheet_page.dart b/lib/ui/pages/stylesheet_page.dart
@@ -15,6 +15,7 @@ import 'package:creekui/ui/widgets/bottom_bar.dart';
import 'package:creekui/ui/widgets/top_bar.dart';
import 'package:creekui/ui/styles/variables.dart';
import 'package:creekui/ui/widgets/section_header.dart';
+import 'package:creekui/ui/widgets/primary_button.dart';
class StylesheetPage extends StatefulWidget {
final int projectId;
@@ -201,7 +202,7 @@ class _StylesheetPageState extends State<StylesheetPage> {
@override
Widget build(BuildContext context) {
return Scaffold(
- backgroundColor: Variables.background,
+ backgroundColor: Variables.surfaceBackground,
appBar: TopBar(
currentProjectId: _currentProjectId,
onBack: () => Navigator.of(context).pop(),
@@ -232,17 +233,24 @@ class _StylesheetPageState extends State<StylesheetPage> {
Widget _buildEmptyState() {
return Center(
- child: Column(
- mainAxisAlignment: MainAxisAlignment.center,
- children: [
- Text(
- "Are you ready to start building\nyour visual identity",
- style: Variables.headerStyle.copyWith(fontSize: 18),
- textAlign: TextAlign.center,
- ),
- const SizedBox(height: 24),
- _buildGenerateButton("Generate Stylesheet"),
- ],
+ child: Padding(
+ padding: const EdgeInsets.symmetric(horizontal: 40),
+ child: Column(
+ mainAxisAlignment: MainAxisAlignment.center,
+ children: [
+ Text(
+ "Are you ready to start building\nyour visual identity",
+ style: Variables.headerStyle.copyWith(fontSize: 18),
+ textAlign: TextAlign.center,
+ ),
+ const SizedBox(height: 24),
+ PrimaryButton(
+ text: "Generate Stylesheet",
+ iconPath: 'assets/icons/generate_icon.svg',
+ onPressed: _generateStylesheet,
+ ),
+ ],
+ ),
),
);
}
@@ -258,7 +266,16 @@ class _StylesheetPageState extends State<StylesheetPage> {
child: Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
- Center(child: _buildGenerateButton("Regenerate Stylesheet")),
+ Center(
+ child: SizedBox(
+ width: 200,
+ child: PrimaryButton(
+ text: "Regenerate Stylesheet",
+ iconPath: 'assets/icons/generate_icon.svg',
+ onPressed: _generateStylesheet,
+ ),
+ ),
+ ),
const SizedBox(height: 24),
_buildLogosSection(null),
@@ -283,36 +300,6 @@ class _StylesheetPageState extends State<StylesheetPage> {
);
}
- Widget _buildGenerateButton(String label) {
- return GestureDetector(
- onTap: _generateStylesheet,
- child: Container(
- padding: const EdgeInsets.symmetric(horizontal: 24, vertical: 12),
- decoration: BoxDecoration(
- color: Variables.textPrimary,
- borderRadius: BorderRadius.circular(112),
- ),
- child: Row(
- mainAxisSize: MainAxisSize.min,
- mainAxisAlignment: MainAxisAlignment.center,
- children: [
- Text(label, style: Variables.buttonTextStyle),
- const SizedBox(width: 8),
- SvgPicture.asset(
- 'assets/icons/generate_icon.svg',
- width: 18,
- height: 18,
- colorFilter: const ColorFilter.mode(
- Colors.white,
- BlendMode.srcIn,
- ),
- ),
- ],
- ),
- ),
- );
- }
-
// Logos
Widget _buildLogosSection(dynamic data) {
List<String> logoPaths = List.from(_logoPaths);
diff --git a/lib/ui/styles/variables.dart b/lib/ui/styles/variables.dart
@@ -8,9 +8,13 @@ class Variables {
static const Color textDisabled = Color(0xFFA1A1AA);
static const Color surfaceSubtle = Color(0xFFF4F4F5);
+ static const Color surfaceBackground = Color(0xFFFAFAFA);
static const Color background = Colors.white;
static const Color borderSubtle = Color(0xFFE4E4E7);
+ static const Color chipBackground = Color(0xFFE0E7FF);
+ static const Color chipText = Color(0xFF7C86FF);
+
// Dark Mode
static const Color surfaceDark = Color(0xFF27272A);
static const Color backgroundDark = Color(0xFF18181B);
@@ -36,6 +40,7 @@ class Variables {
// Radius
static const double radiusSmall = 8.0;
static const double radiusMedium = 12.0;
+ static const double radiusLarge = 16.0; // Added
// Text Styles
static TextStyle get headerStyle => const TextStyle(
diff --git a/lib/ui/widgets/app_bar.dart b/lib/ui/widgets/app_bar.dart
@@ -0,0 +1,56 @@
+import 'package:flutter/material.dart';
+import 'package:creekui/ui/styles/variables.dart';
+
+class CustomAppBar extends StatelessWidget implements PreferredSizeWidget {
+ final String? title;
+ final Widget? titleWidget;
+ final bool showBack;
+ final VoidCallback? onBack;
+ final List<Widget>? actions;
+ final Widget? leading;
+ final double? leadingWidth;
+ final bool centerTitle;
+
+ const CustomAppBar({
+ super.key,
+ this.title,
+ this.titleWidget,
+ this.showBack = true,
+ this.onBack,
+ this.actions,
+ this.leading,
+ this.leadingWidth,
+ this.centerTitle = false,
+ });
+
+ @override
+ Widget build(BuildContext context) {
+ return AppBar(
+ title:
+ titleWidget ??
+ (title != null ? Text(title!, style: Variables.headerStyle) : null),
+ backgroundColor: Variables.surfaceBackground,
+ elevation: 0,
+ centerTitle: centerTitle,
+ leadingWidth: leadingWidth ?? (showBack ? 50 : 0),
+ titleSpacing: showBack ? 0 : 16,
+ automaticallyImplyLeading: false,
+ leading:
+ leading ??
+ (showBack
+ ? IconButton(
+ icon: const Icon(
+ Icons.arrow_back,
+ size: 20,
+ color: Variables.textPrimary,
+ ),
+ onPressed: onBack ?? () => Navigator.pop(context),
+ )
+ : null),
+ actions: actions,
+ );
+ }
+
+ @override
+ Size get preferredSize => const Size.fromHeight(kToolbarHeight);
+}
diff --git a/lib/ui/widgets/bottom_bar.dart b/lib/ui/widgets/bottom_bar.dart
@@ -1,4 +1,3 @@
-import 'dart:math';
import 'package:flutter/material.dart';
import 'package:flutter_svg/flutter_svg.dart';
import 'package:creekui/ui/styles/variables.dart';
@@ -18,26 +17,26 @@ class BottomBar extends StatelessWidget {
required this.projectId,
});
- void _onTap(BuildContext context, BottomBarItem item) {
+ void _onItemTapped(BuildContext context, BottomBarItem item) {
if (item == currentTab) return;
- Widget nextPage;
+ Widget page;
switch (item) {
case BottomBarItem.moodboard:
- nextPage = ProjectBoardPage(projectId: projectId);
+ page = ProjectBoardPage(projectId: projectId);
break;
case BottomBarItem.stylesheet:
- nextPage = StylesheetPage(projectId: projectId);
+ page = StylesheetPage(projectId: projectId);
break;
case BottomBarItem.files:
- nextPage = ProjectFilePage(projectId: projectId);
+ page = ProjectFilePage(projectId: projectId);
break;
}
Navigator.pushReplacement(
context,
PageRouteBuilder(
- pageBuilder: (context, anim1, anim2) => nextPage,
+ pageBuilder: (_, __, ___) => page,
transitionDuration: Duration.zero,
reverseTransitionDuration: Duration.zero,
),
@@ -46,82 +45,78 @@ class BottomBar extends StatelessWidget {
@override
Widget build(BuildContext context) {
- // System Safe Area Padding
- final double safeBottom = MediaQuery.of(context).padding.bottom;
- final double effectiveBottomPadding = max(safeBottom, 24.0);
-
return Container(
- decoration: const BoxDecoration(
+ height: 80, // Fixed height for consistency
+ decoration: BoxDecoration(
color: Variables.background,
- border: Border(
- top: BorderSide(color: Variables.borderSubtle, width: 1),
- ),
+ border: Border(top: BorderSide(color: Variables.borderSubtle)),
),
- padding: EdgeInsets.only(bottom: effectiveBottomPadding, top: 12),
- child: SizedBox(
- height: 54,
- child: Row(
- children: [
- Expanded(
- child: _buildNavItem(
- context,
- BottomBarItem.moodboard,
- "Moodboard",
- "assets/icons/moodboard_icon.svg",
- ),
- ),
- Expanded(
- child: _buildNavItem(
- context,
- BottomBarItem.stylesheet,
- "Stylesheet",
- "assets/icons/stylesheet_icon.svg",
- ),
- ),
- Expanded(
- child: _buildNavItem(
- context,
- BottomBarItem.files,
- "Files",
- "assets/icons/files_icon.svg",
- ),
- ),
- ],
- ),
+ child: Row(
+ mainAxisAlignment: MainAxisAlignment.spaceAround,
+ children: [
+ _NavBarItem(
+ iconPath: 'assets/icons/moodboard_icon.svg',
+ label: 'Moodboard',
+ isActive: currentTab == BottomBarItem.moodboard,
+ onTap: () => _onItemTapped(context, BottomBarItem.moodboard),
+ ),
+ _NavBarItem(
+ iconPath: 'assets/icons/stylesheet.svg',
+ label: 'Stylesheet',
+ isActive: currentTab == BottomBarItem.stylesheet,
+ onTap: () => _onItemTapped(context, BottomBarItem.stylesheet),
+ ),
+ _NavBarItem(
+ iconPath: 'assets/icons/files_icon.svg',
+ label: 'Files',
+ isActive: currentTab == BottomBarItem.files,
+ onTap: () => _onItemTapped(context, BottomBarItem.files),
+ ),
+ ],
),
);
}
+}
+
+class _NavBarItem extends StatelessWidget {
+ final String iconPath;
+ final String label;
+ final bool isActive;
+ final VoidCallback onTap;
- Widget _buildNavItem(
- BuildContext context,
- BottomBarItem item,
- String label,
- String assetPath,
- ) {
- final bool isSelected = item == currentTab;
- final Color color =
- isSelected ? Variables.textPrimary : Variables.textDisabled;
+ const _NavBarItem({
+ required this.iconPath,
+ required this.label,
+ required this.isActive,
+ required this.onTap,
+ });
+
+ @override
+ Widget build(BuildContext context) {
+ final color = isActive ? Variables.iconActive : Variables.iconInactive;
- return GestureDetector(
- onTap: () => _onTap(context, item),
- behavior: HitTestBehavior.opaque,
- child: Center(
+ return InkWell(
+ onTap: onTap,
+ borderRadius: BorderRadius.circular(8),
+ child: Padding(
+ padding: const EdgeInsets.symmetric(horizontal: 16, vertical: 8),
child: Column(
mainAxisAlignment: MainAxisAlignment.center,
+ mainAxisSize: MainAxisSize.min,
children: [
SvgPicture.asset(
- assetPath,
+ iconPath,
width: 24,
height: 24,
colorFilter: ColorFilter.mode(color, BlendMode.srcIn),
),
- const SizedBox(height: 6),
+ const SizedBox(height: 4),
Text(
label,
style: Variables.captionStyle.copyWith(
color: color,
- fontSize: 11,
- fontWeight: isSelected ? FontWeight.w600 : FontWeight.normal,
+ fontWeight: isActive ? FontWeight.w600 : FontWeight.w500,
+ fontSize: 10,
),
),
],
diff --git a/lib/ui/widgets/canvas/asset_picker_sheet.dart b/lib/ui/widgets/canvas/asset_picker_sheet.dart
@@ -4,6 +4,7 @@ import 'package:path_provider/path_provider.dart';
import 'package:path/path.dart' as p;
import 'package:creekui/data/repos/project_repo.dart';
import 'package:creekui/ui/styles/variables.dart';
+import 'package:creekui/ui/widgets/primary_button.dart';
class AssetPickerSheet extends StatefulWidget {
final int projectId;
@@ -188,24 +189,9 @@ class _AssetPickerSheetState extends State<AssetPickerSheet> {
child: Container(
width: double.infinity,
margin: const EdgeInsets.only(top: 16, bottom: 16),
- child: ElevatedButton(
+ child: PrimaryButton(
+ text: "Add to File",
onPressed: () => widget.onAddAssets(_selectedPaths.toList()),
- style: ElevatedButton.styleFrom(
- backgroundColor: Variables.surfaceDark,
- foregroundColor: Variables.textWhite,
- padding: const EdgeInsets.symmetric(vertical: 16),
- shape: RoundedRectangleBorder(
- borderRadius: BorderRadius.circular(30),
- ),
- ),
- child: const Text(
- "Add to File",
- style: TextStyle(
- fontFamily: 'GeneralSans',
- fontSize: 16,
- fontWeight: FontWeight.w600,
- ),
- ),
),
),
),
diff --git a/lib/ui/widgets/canvas/canvas_bottom_bar.dart b/lib/ui/widgets/canvas/canvas_bottom_bar.dart
@@ -111,28 +111,30 @@ class _BottomBarItem extends StatelessWidget {
});
@override
Widget build(BuildContext context) {
+ final color = isActive ? Variables.iconActive : Variables.iconInactive;
+
return InkWell(
onTap: onTap,
+ borderRadius: BorderRadius.circular(8),
child: Padding(
padding: const EdgeInsets.all(8.0),
child: Column(
mainAxisAlignment: MainAxisAlignment.center,
+ mainAxisSize: MainAxisSize.min,
children: [
SvgPicture.asset(
iconPath,
width: 24,
- colorFilter: ColorFilter.mode(
- isActive ? Variables.iconActive : Variables.iconInactive,
- BlendMode.srcIn,
- ),
+ height: 24,
+ colorFilter: ColorFilter.mode(color, BlendMode.srcIn),
),
const SizedBox(height: 6),
Text(
label,
- style: TextStyle(
- fontSize: 11,
- fontWeight: FontWeight.w500,
- color: isActive ? Variables.iconActive : Variables.iconInactive,
+ style: Variables.captionStyle.copyWith(
+ color: color,
+ fontWeight: isActive ? FontWeight.w600 : FontWeight.w500,
+ fontSize: 10,
),
),
],
diff --git a/lib/ui/widgets/canvas/manipulating_box.dart b/lib/ui/widgets/canvas/manipulating_box.dart
@@ -153,8 +153,7 @@ class _ManipulatingBoxState extends State<ManipulatingBox> {
final rotated = _rotateVector(scaledDelta, -_rot);
setState(() {
_pos += scaledDelta;
- _previousFocalPoint =
- currentFocalPoint; // Update for next frame
+ _previousFocalPoint = currentFocalPoint;
});
widget.onUpdate(_pos, _size, _rot);
}
diff --git a/lib/ui/widgets/dialog.dart b/lib/ui/widgets/dialog.dart
@@ -0,0 +1,108 @@
+import 'package:flutter/material.dart';
+import 'package:creekui/ui/styles/variables.dart';
+import 'package:creekui/ui/widgets/primary_button.dart';
+import 'package:creekui/ui/widgets/secondary_button.dart';
+
+class ShowDialog extends StatelessWidget {
+ final String title;
+ final String? description;
+ final Widget? content;
+ final String primaryButtonText;
+ final VoidCallback onPrimaryPressed;
+ final String secondaryButtonText;
+ final VoidCallback? onSecondaryPressed;
+ final bool
+ isDestructive; // Makes primary button red if true (future enhancement)
+ final bool isLoading;
+
+ const ShowDialog({
+ super.key,
+ required this.title,
+ this.description,
+ this.content,
+ required this.primaryButtonText,
+ required this.onPrimaryPressed,
+ this.secondaryButtonText = "Cancel",
+ this.onSecondaryPressed,
+ this.isDestructive = false,
+ this.isLoading = false,
+ });
+
+ static Future<T?> show<T>(
+ BuildContext context, {
+ required String title,
+ String? description,
+ Widget? content,
+ required String primaryButtonText,
+ required VoidCallback onPrimaryPressed,
+ String secondaryButtonText = "Cancel",
+ VoidCallback? onSecondaryPressed,
+ bool isDestructive = false,
+ bool isLoading = false,
+ }) {
+ return showDialog<T>(
+ context: context,
+ builder:
+ (context) => Dialog(
+ backgroundColor: Colors.white,
+ shape: RoundedRectangleBorder(
+ borderRadius: BorderRadius.circular(Variables.radiusLarge),
+ ),
+ child: ShowDialog(
+ title: title,
+ description: description,
+ content: content,
+ primaryButtonText: primaryButtonText,
+ onPrimaryPressed: onPrimaryPressed,
+ secondaryButtonText: secondaryButtonText,
+ onSecondaryPressed: onSecondaryPressed,
+ isDestructive: isDestructive,
+ isLoading: isLoading,
+ ),
+ ),
+ );
+ }
+
+ @override
+ Widget build(BuildContext context) {
+ return Padding(
+ padding: const EdgeInsets.all(24.0),
+ child: Column(
+ mainAxisSize: MainAxisSize.min,
+ crossAxisAlignment: CrossAxisAlignment.start,
+ children: [
+ Text(title, style: Variables.headerStyle.copyWith(fontSize: 18)),
+ if (description != null) ...[
+ const SizedBox(height: 8),
+ Text(
+ description!,
+ style: Variables.bodyStyle.copyWith(
+ color: Variables.textSecondary,
+ ),
+ ),
+ ],
+ if (content != null) ...[const SizedBox(height: 16), content!],
+ const SizedBox(height: 24),
+ Row(
+ children: [
+ Expanded(
+ child: SecondaryButton(
+ text: secondaryButtonText,
+ onPressed: onSecondaryPressed ?? () => Navigator.pop(context),
+ ),
+ ),
+ const SizedBox(width: 12),
+ Expanded(
+ child: PrimaryButton(
+ text: primaryButtonText,
+ onPressed: onPrimaryPressed,
+ isLoading: isLoading,
+ ),
+ ),
+ ],
+ ),
+ ],
+ ),
+ );
+ }
+}
diff --git a/lib/ui/widgets/empty_state.dart b/lib/ui/widgets/empty_state.dart
@@ -1,4 +1,5 @@
import 'package:flutter/material.dart';
+import 'package:creekui/ui/styles/variables.dart';
class EmptyState extends StatelessWidget {
final IconData icon;
@@ -14,30 +15,23 @@ class EmptyState extends StatelessWidget {
@override
Widget build(BuildContext context) {
- return Container(
- width: double.infinity,
- padding: const EdgeInsets.symmetric(vertical: 48),
+ return Center(
child: Column(
+ mainAxisAlignment: MainAxisAlignment.center,
children: [
- Icon(icon, size: 64, color: Colors.grey[400]),
+ Icon(icon, size: 48, color: Variables.textDisabled),
const SizedBox(height: 16),
Text(
title,
- style: TextStyle(
- fontSize: 16,
- fontWeight: FontWeight.w500,
- fontFamily: 'GeneralSans',
- color: Colors.grey[600],
+ style: Variables.bodyStyle.copyWith(
+ fontWeight: FontWeight.w600,
+ color: Variables.textPrimary,
),
),
const SizedBox(height: 8),
Text(
subtitle,
- style: TextStyle(
- fontSize: 14,
- fontFamily: 'GeneralSans',
- color: Colors.grey[500],
- ),
+ style: Variables.captionStyle,
textAlign: TextAlign.center,
),
],
diff --git a/lib/ui/widgets/file_card.dart b/lib/ui/widgets/file_card.dart
@@ -27,129 +27,119 @@ class FileCard extends StatelessWidget {
@override
Widget build(BuildContext context) {
- // Resolve valid image path
- final bool hasPreview =
- previewPath.isNotEmpty && File(previewPath).existsSync();
- final ImageProvider? imageProvider =
- hasPreview ? FileImage(File(previewPath)) : null;
-
return GestureDetector(
onTap: onTap,
child: Container(
- margin: const EdgeInsets.only(bottom: 12),
+ height: 100,
decoration: BoxDecoration(
- color: Colors.white,
- borderRadius: BorderRadius.circular(16),
- border: Border.all(color: const Color(0xFFE4E4E7)),
+ color: Variables.background,
+ borderRadius: BorderRadius.circular(Variables.radiusMedium),
+ border: Border.all(color: Variables.borderSubtle),
),
+ padding: const EdgeInsets.all(12),
child: Row(
- crossAxisAlignment: CrossAxisAlignment.start,
children: [
- // Thumbnail
- ClipRRect(
- borderRadius: const BorderRadius.only(
- topLeft: Radius.circular(16),
- bottomLeft: Radius.circular(16),
- ),
- child: SizedBox(
- width: 120,
- height: 120,
- child:
- hasPreview
- ? Image(
- image: imageProvider!,
- fit: BoxFit.cover,
- errorBuilder: (_, __, ___) => _buildPlaceholder(),
- )
- : _buildPlaceholder(),
+ // Preview Image
+ Container(
+ width: 76,
+ height: 76,
+ decoration: BoxDecoration(
+ color: Variables.surfaceSubtle,
+ borderRadius: BorderRadius.circular(Variables.radiusSmall),
),
+ clipBehavior: Clip.antiAlias,
+ child:
+ previewPath.isNotEmpty
+ ? Image.file(
+ File(previewPath),
+ fit: BoxFit.cover,
+ errorBuilder:
+ (context, error, stackTrace) => const Center(
+ child: Icon(
+ Icons.broken_image,
+ color: Variables.textDisabled,
+ ),
+ ),
+ )
+ : const Center(
+ child: Icon(Icons.image, color: Variables.textDisabled),
+ ),
),
-
- const SizedBox(width: 12),
-
- // Info Column
+ const SizedBox(width: 16),
+ // Info
Expanded(
- child: Padding(
- padding: const EdgeInsets.symmetric(vertical: 14),
- child: Column(
- crossAxisAlignment: CrossAxisAlignment.start,
- children: [
- if (breadcrumb.isNotEmpty)
- Text(
+ child: Column(
+ crossAxisAlignment: CrossAxisAlignment.start,
+ mainAxisAlignment: MainAxisAlignment.center,
+ children: [
+ if (breadcrumb.isNotEmpty)
+ Padding(
+ padding: const EdgeInsets.only(bottom: 4),
+ child: Text(
breadcrumb,
- style: const TextStyle(
- fontSize: 11,
- color: Color(0xFF71717B),
- fontFamily: 'GeneralSans',
- ),
+ style: Variables.captionStyle,
maxLines: 1,
overflow: TextOverflow.ellipsis,
),
- const SizedBox(height: 4),
- Text(
- file.name,
- style: const TextStyle(
- fontSize: 15,
- fontWeight: FontWeight.w600,
- fontFamily: 'GeneralSans',
- color: Color(0xFF27272A),
- ),
- maxLines: 1,
- overflow: TextOverflow.ellipsis,
),
- const SizedBox(height: 6),
- Text(
- dimensions,
- style: const TextStyle(
- fontSize: 13,
- color: Color(0xFF71717B),
- fontFamily: 'GeneralSans',
- ),
+ Text(
+ file.name,
+ style: Variables.bodyStyle.copyWith(
+ fontWeight: FontWeight.w600,
),
- const SizedBox(height: 6),
- Text(
- timeAgo,
- style: TextStyle(
- fontSize: 12,
- color: const Color(0xFF71717B).withValues(alpha: 0.8),
- fontFamily: 'GeneralSans',
+ maxLines: 1,
+ overflow: TextOverflow.ellipsis,
+ ),
+ const SizedBox(height: 4),
+ Row(
+ children: [
+ Text(dimensions, style: Variables.captionStyle),
+ const SizedBox(width: 8),
+ Container(
+ width: 3,
+ height: 3,
+ decoration: const BoxDecoration(
+ color: Variables.textDisabled,
+ shape: BoxShape.circle,
+ ),
),
- ),
- ],
- ),
+ const SizedBox(width: 8),
+ Text(timeAgo, style: Variables.captionStyle),
+ ],
+ ),
+ ],
),
),
-
- // Menu
+ // Menu Action
if (onMenuAction != null)
PopupMenuButton<String>(
- onSelected: onMenuAction,
- itemBuilder:
- (_) => const [
- PopupMenuItem(value: "open", child: Text("Open")),
- PopupMenuItem(value: "rename", child: Text("Rename")),
- PopupMenuItem(value: "delete", child: Text("Delete")),
- ],
icon: const Icon(
Icons.more_vert,
- size: 20,
- color: Color(0xFF71717B),
+ color: Variables.textSecondary,
),
- )
- else
- const SizedBox(width: 40),
+ onSelected: onMenuAction,
+ itemBuilder:
+ (BuildContext context) => <PopupMenuEntry<String>>[
+ const PopupMenuItem<String>(
+ value: 'open',
+ child: Text('Open'),
+ ),
+ const PopupMenuItem<String>(
+ value: 'rename',
+ child: Text('Rename'),
+ ),
+ const PopupMenuItem<String>(
+ value: 'delete',
+ child: Text(
+ 'Delete',
+ style: TextStyle(color: Colors.red),
+ ),
+ ),
+ ],
+ ),
],
),
),
);
}
-
- Widget _buildPlaceholder() {
- return Container(
- color: Colors.grey[300],
- child: const Center(
- child: Icon(Icons.image, size: 32, color: Colors.white),
- ),
- );
- }
}
diff --git a/lib/ui/widgets/image_context_menu.dart b/lib/ui/widgets/image_context_menu.dart
@@ -312,7 +312,7 @@ class _FloatingCircleButtonState extends State<_FloatingCircleButton> {
shape: BoxShape.circle,
boxShadow: [
BoxShadow(
- color: Colors.black.withValues(alpha: 0.15),
+ color: Colors.black.withOpacity(0.15),
blurRadius: 10,
offset: const Offset(0, 4),
),
diff --git a/lib/ui/widgets/note_input_sheet.dart b/lib/ui/widgets/note_input_sheet.dart
@@ -61,7 +61,7 @@ class _NoteInputSheetState extends State<NoteInputSheet> {
decoration: BoxDecoration(
shape: BoxShape.circle,
border: Border.all(
- color: const Color(0xFFFAFAFA),
+ color: Variables.surfaceBackground,
width: 1.25,
),
),
@@ -74,11 +74,9 @@ class _NoteInputSheetState extends State<NoteInputSheet> {
const SizedBox(width: 10),
Text(
_userName,
- style: const TextStyle(
- fontFamily: 'GeneralSans',
+ style: Variables.bodyStyle.copyWith(
fontSize: 12,
fontWeight: FontWeight.w500,
- color: Colors.black,
),
),
const Spacer(),
@@ -88,7 +86,7 @@ class _NoteInputSheetState extends State<NoteInputSheet> {
vertical: 6,
),
decoration: BoxDecoration(
- color: const Color(0xFFE0E7FF),
+ color: Variables.chipBackground,
borderRadius: BorderRadius.circular(1000),
),
child: DropdownButtonHideUnderline(
@@ -97,21 +95,17 @@ class _NoteInputSheetState extends State<NoteInputSheet> {
widget.categories.contains(_selectedCategory)
? _selectedCategory
: null,
- hint: const Text(
+ hint: Text(
"Type",
- style: TextStyle(fontFamily: 'GeneralSans', fontSize: 12),
+ style: Variables.bodyStyle.copyWith(fontSize: 12),
),
isDense: true,
icon: const Icon(
Icons.arrow_drop_down,
size: 20,
- color: Color(0xFF27272A),
- ),
- style: const TextStyle(
- fontFamily: 'GeneralSans',
- fontSize: 12,
- color: Color(0xFF27272A),
+ color: Variables.textPrimary,
),
+ style: Variables.bodyStyle.copyWith(fontSize: 12),
dropdownColor: Colors.white,
items:
widget.categories
@@ -135,18 +129,15 @@ class _NoteInputSheetState extends State<NoteInputSheet> {
Expanded(
child: Container(
decoration: BoxDecoration(
- color: const Color(0xFFF4F4F5),
- border: Border.all(color: const Color(0xFFE4E4E7)),
+ color: Variables.surfaceSubtle,
+ border: Border.all(color: Variables.borderSubtle),
borderRadius: BorderRadius.circular(8),
),
child: TextField(
controller: _controller,
autofocus: true,
maxLines: null,
- style: const TextStyle(
- fontFamily: 'GeneralSans',
- fontSize: 12,
- ),
+ style: Variables.bodyStyle.copyWith(fontSize: 12),
decoration: const InputDecoration(
hintText: "Enter note details...",
border: InputBorder.none,
@@ -162,7 +153,7 @@ class _NoteInputSheetState extends State<NoteInputSheet> {
IconButton(
icon: const Icon(
Icons.send,
- color: Color(0xFF27272A),
+ color: Variables.textPrimary,
size: 24,
),
onPressed: () {
diff --git a/lib/ui/widgets/project_card.dart b/lib/ui/widgets/project_card.dart
@@ -73,10 +73,9 @@ class ProjectCard extends StatelessWidget {
Expanded(
child: Text(
project.title,
- style: TextStyle(
+ style: Variables.bodyStyle.copyWith(
fontSize: 13,
fontWeight: FontWeight.w600,
- fontFamily: 'GeneralSans',
color: theme.colorScheme.onSurface,
),
maxLines: 1,
diff --git a/lib/ui/widgets/project_selector.dart b/lib/ui/widgets/project_selector.dart
@@ -103,9 +103,7 @@ class _ProjectSelectorState extends State<ProjectSelector> {
final parent = await _projectRepo.getProjectById(item.parentId!);
parentTitle = parent?.title;
}
-
final cover = await _getProjectCover(item.id!);
-
recents.add(
ProjectItemViewModel(
item: item,
@@ -124,9 +122,7 @@ class _ProjectSelectorState extends State<ProjectSelector> {
final eCover = await _getProjectCover(e.id!);
eventVMs.add(ProjectItemViewModel(item: e, coverPath: eCover));
}
-
final pCover = await _getProjectCover(p.id!);
-
groups.add(ProjectGroup(project: p, events: eventVMs, coverPath: pCover));
}
@@ -180,9 +176,6 @@ class _ProjectSelectorState extends State<ProjectSelector> {
@override
Widget build(BuildContext context) {
- final theme = Theme.of(context);
- final isDark = theme.brightness == Brightness.dark;
-
if (_isLoading) {
return const Center(child: CircularProgressIndicator());
}
@@ -198,7 +191,6 @@ class _ProjectSelectorState extends State<ProjectSelector> {
hintText: widget.searchHint,
),
),
-
Expanded(
child: SingleChildScrollView(
controller: widget.scrollController,
@@ -228,7 +220,7 @@ class _ProjectSelectorState extends State<ProjectSelector> {
child: Column(
children:
_recentViewModels
- .map((vm) => _buildRecentItem(vm, theme, isDark))
+ .map((vm) => _buildRecentItem(vm))
.toList(),
),
),
@@ -256,8 +248,6 @@ class _ProjectSelectorState extends State<ProjectSelector> {
itemBuilder:
(context, index) => _buildProjectGroup(
_filteredGroupedProjects[index],
- theme,
- isDark,
),
),
),
@@ -271,12 +261,7 @@ class _ProjectSelectorState extends State<ProjectSelector> {
);
}
- // Widgets
- Widget _buildRecentItem(
- ProjectItemViewModel vm,
- ThemeData theme,
- bool isDark,
- ) {
+ Widget _buildRecentItem(ProjectItemViewModel vm) {
return Container(
margin: const EdgeInsets.only(bottom: 8),
child: InkWell(
@@ -285,12 +270,9 @@ class _ProjectSelectorState extends State<ProjectSelector> {
child: Container(
padding: const EdgeInsets.fromLTRB(4, 4, 0, 4),
decoration: BoxDecoration(
- color: theme.scaffoldBackgroundColor,
+ color: Variables.background,
borderRadius: BorderRadius.circular(Variables.radiusMedium),
- border: Border.all(
- color: isDark ? Variables.borderDark : Variables.borderSubtle,
- width: 1,
- ),
+ border: Border.all(color: Variables.borderSubtle, width: 1),
),
child: Row(
children: [
@@ -299,8 +281,7 @@ class _ProjectSelectorState extends State<ProjectSelector> {
width: 56,
height: 56,
decoration: BoxDecoration(
- color:
- isDark ? Variables.surfaceDark : Variables.surfaceSubtle,
+ color: Variables.surfaceSubtle,
borderRadius: BorderRadius.circular(Variables.radiusSmall),
image:
vm.coverPath != null
@@ -312,11 +293,9 @@ class _ProjectSelectorState extends State<ProjectSelector> {
),
child:
vm.coverPath == null
- ? Icon(
+ ? const Icon(
Icons.image,
- color: theme.colorScheme.onSurface.withValues(
- alpha: 0.3,
- ),
+ color: Variables.textDisabled,
size: 28,
)
: null,
@@ -333,11 +312,7 @@ class _ProjectSelectorState extends State<ProjectSelector> {
padding: const EdgeInsets.only(bottom: 2),
child: Text(
vm.parentTitle!,
- style: Variables.captionStyle.copyWith(
- color: theme.colorScheme.onSurface.withValues(
- alpha: 0.6,
- ),
- ),
+ style: Variables.captionStyle,
maxLines: 1,
overflow: TextOverflow.ellipsis,
),
@@ -346,7 +321,6 @@ class _ProjectSelectorState extends State<ProjectSelector> {
vm.title,
style: Variables.bodyStyle.copyWith(
fontWeight: FontWeight.w600,
- color: theme.colorScheme.onSurface,
),
maxLines: 1,
overflow: TextOverflow.ellipsis,
@@ -361,7 +335,7 @@ class _ProjectSelectorState extends State<ProjectSelector> {
);
}
- Widget _buildProjectGroup(ProjectGroup g, ThemeData theme, bool isDark) {
+ Widget _buildProjectGroup(ProjectGroup g) {
final project = g.project;
final hasEvents = g.events.isNotEmpty;
@@ -369,11 +343,9 @@ class _ProjectSelectorState extends State<ProjectSelector> {
margin: const EdgeInsets.only(bottom: 8),
clipBehavior: Clip.antiAlias,
decoration: BoxDecoration(
- color: theme.scaffoldBackgroundColor,
+ color: Variables.background,
borderRadius: BorderRadius.circular(Variables.radiusMedium),
- border: Border.all(
- color: isDark ? Variables.borderDark : Variables.borderSubtle,
- ),
+ border: Border.all(color: Variables.borderSubtle),
),
child: Column(
children: [
@@ -397,7 +369,7 @@ class _ProjectSelectorState extends State<ProjectSelector> {
width: 48,
height: 48,
decoration: BoxDecoration(
- color: isDark ? Variables.surfaceDark : Variables.surfaceSubtle,
+ color: Variables.surfaceSubtle,
borderRadius: BorderRadius.circular(Variables.radiusMedium),
image:
g.coverPath != null
@@ -409,12 +381,7 @@ class _ProjectSelectorState extends State<ProjectSelector> {
),
child:
g.coverPath == null
- ? Icon(
- Icons.folder,
- color: theme.colorScheme.onSurface.withValues(
- alpha: 0.4,
- ),
- )
+ ? const Icon(Icons.folder, color: Variables.textDisabled)
: null,
),
title: Text(
@@ -422,7 +389,6 @@ class _ProjectSelectorState extends State<ProjectSelector> {
style: Variables.bodyStyle.copyWith(
fontWeight: FontWeight.w600,
fontSize: 16,
- color: theme.colorScheme.onSurface,
),
),
trailing:
@@ -432,9 +398,7 @@ class _ProjectSelectorState extends State<ProjectSelector> {
g.isExpanded
? Icons.keyboard_arrow_up
: Icons.keyboard_arrow_down,
- color: theme.colorScheme.onSurface.withValues(
- alpha: 0.6,
- ),
+ color: Variables.textSecondary,
),
onPressed: () {
setState(() => g.isExpanded = !g.isExpanded);
@@ -449,10 +413,7 @@ class _ProjectSelectorState extends State<ProjectSelector> {
firstChild: const SizedBox.shrink(),
secondChild: Container(
width: double.infinity,
- color:
- isDark
- ? Colors.black26
- : Variables.surfaceSubtle.withValues(alpha: 0.5),
+ color: Variables.surfaceSubtle.withOpacity(0.5),
child: Column(
children:
g.events.map((e) {
@@ -472,16 +433,11 @@ class _ProjectSelectorState extends State<ProjectSelector> {
width: 40,
height: 40,
decoration: BoxDecoration(
- color: theme.cardColor,
+ color: Variables.background,
borderRadius: BorderRadius.circular(
Variables.radiusSmall,
),
- border: Border.all(
- color:
- isDark
- ? Variables.borderDark
- : Variables.borderSubtle,
- ),
+ border: Border.all(color: Variables.borderSubtle),
image:
e.coverPath != null
? DecorationImage(
@@ -492,11 +448,10 @@ class _ProjectSelectorState extends State<ProjectSelector> {
),
child:
e.coverPath == null
- ? Icon(
+ ? const Icon(
Icons.event,
size: 20,
- color: theme.colorScheme.onSurface
- .withValues(alpha: 0.4),
+ color: Variables.textDisabled,
)
: null,
),
@@ -505,7 +460,6 @@ class _ProjectSelectorState extends State<ProjectSelector> {
style: Variables.bodyStyle.copyWith(
fontSize: 15,
fontWeight: FontWeight.w500,
- color: theme.colorScheme.onSurface,
),
),
);
diff --git a/lib/ui/widgets/search_bar.dart b/lib/ui/widgets/search_bar.dart
@@ -15,30 +15,26 @@ class CommonSearchBar extends StatelessWidget {
@override
Widget build(BuildContext context) {
- final theme = Theme.of(context);
- final isDark = theme.brightness == Brightness.dark;
-
return TextField(
controller: controller,
onChanged: onChanged,
+ style: Variables.bodyStyle,
decoration: InputDecoration(
hintText: hintText,
- hintStyle: TextStyle(
- fontSize: 12,
- color: theme.colorScheme.onSurface.withValues(alpha: 0.5),
- fontFamily: 'GeneralSans',
+ hintStyle: Variables.bodyStyle.copyWith(
+ color: Variables.textSecondary.withOpacity(0.5),
),
prefixIcon: Icon(
Icons.search,
size: 18,
- color: theme.colorScheme.onSurface.withValues(alpha: 0.5),
+ color: Variables.textSecondary.withOpacity(0.5),
),
prefixIconConstraints: const BoxConstraints(
minWidth: 50,
minHeight: 18,
),
filled: true,
- fillColor: isDark ? Variables.surfaceDark : Variables.surfaceSubtle,
+ fillColor: Variables.surfaceSubtle,
border: OutlineInputBorder(
borderRadius: BorderRadius.circular(Variables.radiusSmall),
borderSide: BorderSide.none,
@@ -56,11 +52,6 @@ class CommonSearchBar extends StatelessWidget {
vertical: 12,
),
),
- style: TextStyle(
- fontSize: 12,
- fontFamily: 'GeneralSans',
- color: theme.colorScheme.onSurface,
- ),
);
}
}
diff --git a/lib/ui/widgets/secondary_button.dart b/lib/ui/widgets/secondary_button.dart
@@ -0,0 +1,50 @@
+import 'package:flutter/material.dart';
+import 'package:creekui/ui/styles/variables.dart';
+
+class SecondaryButton extends StatelessWidget {
+ final String text;
+ final VoidCallback? onPressed;
+ final bool isLoading;
+
+ const SecondaryButton({
+ super.key,
+ required this.text,
+ this.onPressed,
+ this.isLoading = false,
+ });
+
+ @override
+ Widget build(BuildContext context) {
+ return SizedBox(
+ width: double.infinity,
+ child: ElevatedButton(
+ onPressed: isLoading ? null : onPressed,
+ style: ElevatedButton.styleFrom(
+ backgroundColor: Variables.borderSubtle,
+ foregroundColor: Variables.textPrimary,
+ padding: const EdgeInsets.symmetric(vertical: 12),
+ shape: RoundedRectangleBorder(
+ borderRadius: BorderRadius.circular(100),
+ ),
+ elevation: 0,
+ ),
+ child:
+ isLoading
+ ? const SizedBox(
+ height: 20,
+ width: 20,
+ child: CircularProgressIndicator(
+ color: Variables.textPrimary,
+ strokeWidth: 2,
+ ),
+ )
+ : Text(
+ text,
+ style: Variables.buttonTextStyle.copyWith(
+ color: Variables.textPrimary,
+ ),
+ ),
+ ),
+ );
+ }
+}
diff --git a/lib/ui/widgets/selection_overlay_painter.dart b/lib/ui/widgets/selection_overlay_painter.dart
@@ -1,18 +1,6 @@
-import 'dart:ui' as ui;
import 'package:flutter/material.dart';
-
-enum DragHandle { none, topLeft, topRight, bottomLeft, bottomRight, center }
-
-extension RectUtils on Rect {
- Rect normalize() {
- return Rect.fromLTRB(
- left < right ? left : right,
- top < bottom ? top : bottom,
- left > right ? left : right,
- top > bottom ? top : bottom,
- );
- }
-}
+import 'package:creekui/data/models/canvas_models.dart';
+import 'package:creekui/ui/styles/variables.dart';
class SelectionOverlayPainter extends CustomPainter {
final Rect rect;
@@ -21,95 +9,68 @@ class SelectionOverlayPainter extends CustomPainter {
SelectionOverlayPainter({
required this.rect,
- required this.isResizing,
+ this.isResizing = false,
this.activeHandle = DragHandle.none,
});
@override
void paint(Canvas canvas, Size size) {
- // 1. Dim Background
- if (isResizing) {
- final Path backgroundPath =
- Path()..addRect(Rect.fromLTWH(0, 0, size.width, size.height));
- final Path holePath = Path()..addRect(rect);
- final Path overlayPath = Path.combine(
- ui.PathOperation.difference,
- backgroundPath,
- holePath,
- );
- canvas.drawPath(overlayPath, Paint()..color = Colors.black54);
- }
-
- // 2. Draw Dashed Border
- final Paint borderPaint =
+ // 1. Draw the Selection Border
+ final paint =
Paint()
- ..color = const Color(0xFF448AFF)
- ..strokeWidth = 2.0
- ..style = PaintingStyle.stroke;
+ ..color = Variables.selectionBorder
+ ..style = PaintingStyle.stroke
+ ..strokeWidth = 2.0;
- double dashWidth = 6;
- double dashSpace = 4;
- Path borderPath = Path()..addRect(rect);
+ // Use a dash effect for the selection box
+ _drawDashedRect(canvas, rect, paint);
- for (ui.PathMetric pathMetric in borderPath.computeMetrics()) {
- double distance = 0.0;
- while (distance < pathMetric.length) {
- canvas.drawPath(
- pathMetric.extractPath(distance, distance + dashWidth),
- borderPaint,
- );
- distance += (dashWidth + dashSpace);
- }
- }
+ // 2. If Resizing, Draw Handles
+ if (isResizing) {
+ final handlePaint =
+ Paint()
+ ..color = Colors.white
+ ..style = PaintingStyle.fill;
- // 3. Draw Center Dot (Initial) or Resize Handles (Resizing)
- if (!isResizing) {
- canvas.drawCircle(
- rect.center,
- 8,
- Paint()
- ..color = Colors.black26
- ..maskFilter = const MaskFilter.blur(BlurStyle.normal, 3),
- );
- canvas.drawCircle(
- rect.center,
- 6,
- Paint()
- ..color = Colors.white
- ..style = PaintingStyle.fill,
- );
- } else {
- final List<Offset> corners = [
+ final handleBorderPaint =
+ Paint()
+ ..color = Variables.selectionBorder
+ ..style = PaintingStyle.stroke
+ ..strokeWidth = 2.0;
+
+ final double handleRadius = 6.0;
+
+ final handles = [
rect.topLeft,
rect.topRight,
rect.bottomLeft,
rect.bottomRight,
];
- final Paint handleShadow =
- Paint()
- ..color = Colors.black.withOpacity(0.3)
- ..maskFilter = const MaskFilter.blur(BlurStyle.normal, 2);
- final Paint handleFill = Paint()..color = Colors.white;
- final Paint handleBorder =
- Paint()
- ..color = const Color(0xFF448AFF)
- ..strokeWidth = 2
- ..style = PaintingStyle.stroke;
+ for (final handle in handles) {
+ canvas.drawCircle(handle, handleRadius, handlePaint);
+ canvas.drawCircle(handle, handleRadius, handleBorderPaint);
+ }
- const double handleRadius = 8;
+ // 3. Draw Center Handle (Move)
+ final centerPaint =
+ Paint()
+ ..color = Variables.accentMagic.withOpacity(0.5)
+ ..style = PaintingStyle.fill;
- for (final corner in corners) {
- canvas.drawCircle(corner, handleRadius, handleShadow);
- canvas.drawCircle(corner, handleRadius, handleFill);
- canvas.drawCircle(corner, handleRadius, handleBorder);
- }
+ canvas.drawCircle(rect.center, 8.0, centerPaint);
}
}
+ void _drawDashedRect(Canvas canvas, Rect rect, Paint paint) {
+ final path = Path()..addRect(rect);
+ canvas.drawPath(path, paint);
+ }
+
@override
- bool shouldRepaint(covariant SelectionOverlayPainter oldDelegate) =>
- rect != oldDelegate.rect ||
- isResizing != oldDelegate.isResizing ||
- activeHandle != oldDelegate.activeHandle;
+ bool shouldRepaint(covariant SelectionOverlayPainter oldDelegate) {
+ return oldDelegate.rect != rect ||
+ oldDelegate.isResizing != isResizing ||
+ oldDelegate.activeHandle != activeHandle;
+ }
}
diff --git a/lib/ui/widgets/tag_chip.dart b/lib/ui/widgets/tag_chip.dart
@@ -0,0 +1,48 @@
+import 'package:flutter/material.dart';
+import 'package:creekui/ui/styles/variables.dart';
+
+class TagChip extends StatelessWidget {
+ final String label;
+ final VoidCallback onDelete;
+ final Widget? icon;
+
+ const TagChip({
+ super.key,
+ required this.label,
+ required this.onDelete,
+ this.icon,
+ });
+
+ @override
+ Widget build(BuildContext context) {
+ return Container(
+ padding: const EdgeInsets.symmetric(horizontal: 12, vertical: 8),
+ decoration: BoxDecoration(
+ color: Variables.chipBackground,
+ borderRadius: BorderRadius.circular(48),
+ ),
+ child: Row(
+ mainAxisSize: MainAxisSize.min,
+ children: [
+ if (icon != null) ...[icon!, const SizedBox(width: 8)],
+ Text(
+ label,
+ style: Variables.captionStyle.copyWith(
+ fontSize: 12,
+ color: Variables.textPrimary,
+ ),
+ ),
+ const SizedBox(width: 8),
+ GestureDetector(
+ onTap: onDelete,
+ child: const Icon(
+ Icons.close,
+ size: 16,
+ color: Variables.textPrimary,
+ ),
+ ),
+ ],
+ ),
+ );
+ }
+}
diff --git a/lib/ui/widgets/text_field.dart b/lib/ui/widgets/text_field.dart
@@ -2,21 +2,27 @@ import 'package:flutter/material.dart';
import 'package:creekui/ui/styles/variables.dart';
class CommonTextField extends StatelessWidget {
- final String label;
+ final String? label;
final String hintText;
final TextEditingController controller;
final int maxLines;
final bool isRequired;
final ValueChanged<String>? onSubmitted;
+ final ValueChanged<String>? onChanged;
+ final Widget? suffixIcon;
+ final bool autoFocus;
const CommonTextField({
super.key,
- required this.label,
+ this.label,
required this.hintText,
required this.controller,
this.maxLines = 1,
this.isRequired = false,
this.onSubmitted,
+ this.onChanged,
+ this.suffixIcon,
+ this.autoFocus = false,
});
@override
@@ -24,23 +30,27 @@ class CommonTextField extends StatelessWidget {
return Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
- Row(
- children: [
- Text(
- label,
- style: Variables.bodyStyle.copyWith(fontWeight: FontWeight.w500),
- ),
- if (isRequired)
+ if (label != null) ...[
+ Row(
+ children: [
Text(
- '*',
+ label!,
style: Variables.bodyStyle.copyWith(
- color: const Color(0xFF4F39F6),
- fontSize: 12,
+ fontWeight: FontWeight.w500,
),
),
- ],
- ),
- const SizedBox(height: 6),
+ if (isRequired)
+ Text(
+ '*',
+ style: Variables.bodyStyle.copyWith(
+ color: const Color(0xFF4F39F6),
+ fontSize: 12,
+ ),
+ ),
+ ],
+ ),
+ const SizedBox(height: 6),
+ ],
Container(
decoration: BoxDecoration(
color: Variables.borderSubtle,
@@ -49,7 +59,9 @@ class CommonTextField extends StatelessWidget {
child: TextField(
controller: controller,
maxLines: maxLines,
+ autofocus: autoFocus,
onSubmitted: onSubmitted,
+ onChanged: onChanged,
style: Variables.bodyStyle,
decoration: InputDecoration(
hintText: hintText,
@@ -61,6 +73,7 @@ class CommonTextField extends StatelessWidget {
horizontal: 16,
vertical: 12,
),
+ suffixIcon: suffixIcon,
),
),
),
diff --git a/lib/ui/widgets/top_bar.dart b/lib/ui/widgets/top_bar.dart
@@ -170,12 +170,9 @@ class _TopBarState extends State<TopBar> {
widget.titleOverride != null
? Text(
widget.titleOverride!,
- style: const TextStyle(
- fontFamily: 'GeneralSans',
+ style: Variables.headerStyle.copyWith(
fontSize: 20,
- fontWeight: FontWeight.w500,
height: 24 / 20,
- color: Variables.textPrimary,
),
overflow: TextOverflow.ellipsis,
)
@@ -184,12 +181,9 @@ class _TopBarState extends State<TopBar> {
_rootProject != null)
? Text(
_rootProject!.title,
- style: const TextStyle(
- fontFamily: 'GeneralSans',
+ style: Variables.headerStyle.copyWith(
fontSize: 20,
- fontWeight: FontWeight.w500,
height: 24 / 20,
- color: Variables.textPrimary,
),
overflow: TextOverflow.ellipsis,
)
@@ -283,7 +277,6 @@ class _TopBarState extends State<TopBar> {
child: Text(
isRoot ? "Global" : project.title,
style: Variables.bodyStyle.copyWith(
- fontFamily: 'GeneralSans',
fontWeight:
isSelected
? FontWeight.bold
diff --git a/lib/utils/image_actions_helper.dart b/lib/utils/image_actions_helper.dart
@@ -4,7 +4,8 @@ import 'package:share_plus/share_plus.dart';
import 'package:creekui/data/models/image_model.dart';
import 'package:creekui/services/image_service.dart';
import 'package:creekui/ui/pages/share_to_file_page.dart';
-import 'package:creekui/ui/styles/variables.dart';
+import 'package:creekui/ui/widgets/dialog.dart';
+import 'package:creekui/ui/widgets/text_field.dart';
class ImageActionsHelper {
static Future<void> shareImage(BuildContext context, String filePath) async {
@@ -31,67 +32,26 @@ class ImageActionsHelper {
final TextEditingController nameController = TextEditingController(
text: image.name,
);
- await showDialog(
- context: context,
- builder:
- (ctx) => AlertDialog(
- backgroundColor: Colors.white,
- shape: RoundedRectangleBorder(
- borderRadius: BorderRadius.circular(20),
- ),
- title: const Text(
- "Rename Image",
- style: TextStyle(
- fontFamily: 'GeneralSans',
- fontWeight: FontWeight.w600,
- ),
- ),
- content: TextField(
- controller: nameController,
- autofocus: true,
- decoration: InputDecoration(
- hintText: "Enter new name",
- filled: true,
- fillColor: Variables.surfaceSubtle,
- border: OutlineInputBorder(
- borderRadius: BorderRadius.circular(12),
- borderSide: BorderSide.none,
- ),
- ),
- ),
- actions: [
- TextButton(
- onPressed: () => Navigator.pop(ctx),
- child: const Text(
- "Cancel",
- style: TextStyle(
- color: Variables.textSecondary,
- fontFamily: 'GeneralSans',
- ),
- ),
- ),
- TextButton(
- onPressed: () async {
- if (nameController.text.isNotEmpty) {
- await ImageService().renameImage(
- image.id,
- nameController.text.trim(),
- );
- onSuccess();
- if (ctx.mounted) Navigator.pop(ctx);
- }
- },
- child: const Text(
- "Save",
- style: TextStyle(
- color: Variables.textPrimary,
- fontFamily: 'GeneralSans',
- fontWeight: FontWeight.w600,
- ),
- ),
- ),
- ],
- ),
+
+ await ShowDialog.show(
+ context,
+ title: "Rename Image",
+ primaryButtonText: "Save",
+ content: CommonTextField(
+ hintText: "Enter new name",
+ controller: nameController,
+ autoFocus: true,
+ ),
+ onPrimaryPressed: () async {
+ if (nameController.text.isNotEmpty) {
+ await ImageService().renameImage(
+ image.id,
+ nameController.text.trim(),
+ );
+ onSuccess();
+ if (context.mounted) Navigator.pop(context);
+ }
+ },
);
}
@@ -100,57 +60,17 @@ class ImageActionsHelper {
ImageModel image,
VoidCallback onSuccess,
) async {
- final confirm = await showDialog<bool>(
- context: context,
- builder:
- (ctx) => AlertDialog(
- backgroundColor: Colors.white,
- shape: RoundedRectangleBorder(
- borderRadius: BorderRadius.circular(20),
- ),
- title: const Text(
- "Delete Image?",
- style: TextStyle(
- fontFamily: 'GeneralSans',
- fontWeight: FontWeight.w600,
- ),
- ),
- content: const Text(
- "This action cannot be undone.",
- style: TextStyle(
- fontFamily: 'GeneralSans',
- color: Variables.textSecondary,
- ),
- ),
- actions: [
- TextButton(
- onPressed: () => Navigator.pop(ctx, false),
- child: const Text(
- "Cancel",
- style: TextStyle(
- color: Variables.textSecondary,
- fontFamily: 'GeneralSans',
- ),
- ),
- ),
- TextButton(
- onPressed: () => Navigator.pop(ctx, true),
- child: const Text(
- "Delete",
- style: TextStyle(
- color: Colors.red,
- fontFamily: 'GeneralSans',
- fontWeight: FontWeight.w600,
- ),
- ),
- ),
- ],
- ),
+ await ShowDialog.show(
+ context,
+ title: "Delete Image?",
+ description: "This action cannot be undone.",
+ primaryButtonText: "Delete",
+ isDestructive: true,
+ onPrimaryPressed: () async {
+ await ImageService().deleteImage(image.id);
+ onSuccess();
+ if (context.mounted) Navigator.pop(context);
+ },
);
-
- if (confirm == true) {
- await ImageService().deleteImage(image.id);
- onSuccess();
- }
}
}
diff --git a/lib/utils/image_utils.dart b/lib/utils/image_utils.dart
@@ -1,4 +1,17 @@
import 'dart:math';
+import 'dart:ui';
+
+// Extension to normalize Rect
+extension RectNormalize on Rect {
+ Rect normalize() {
+ return Rect.fromLTRB(
+ left < right ? left : right,
+ top < bottom ? top : bottom,
+ left < right ? right : left,
+ top < bottom ? bottom : top,
+ );
+ }
+}
List<double> l2Normalize(List<double> vec) {
double sum = vec.fold(0, (p, c) => p + c * c);