creek

The AI Image Editor of 2030
commit 9be41dbed1afb9fa5a35a90c27d85e2f4763080f
parent 960b59c9bf8a2dd1c1b77e004492e7cbf0e295d7
Author: aditya-samal <samaladitya2004@gmail.com>
Date:   Sun, 30 Nov 2025 17:50:22 +0530

Screens Fixed

Diffstat:
Mlib/ui/pages/define_brand_page.dart | 676+++++++++++++++++++++++++++++++++++++++++++++++++++++++------------------------
Mlib/ui/pages/home_page.dart | 63+++++++++++++++++++++++++++++++++++++--------------------------
2 files changed, 506 insertions(+), 233 deletions(-)

diff --git a/lib/ui/pages/define_brand_page.dart b/lib/ui/pages/define_brand_page.dart @@ -1,7 +1,6 @@ -import 'package:flutter/material.dart'; +git import 'package:flutter/material.dart'; import 'package:adobe/ui/styles/variables.dart'; import 'package:flutter_svg/flutter_svg.dart'; -// Import your service here import '../../services/project_service.dart'; class DefineBrandPage extends StatefulWidget { @@ -26,8 +25,8 @@ class _DefineBrandPageState extends State<DefineBrandPage> { final _descriptionController = TextEditingController(); final _problemController = TextEditingController(); final _goalController = TextEditingController(); - // controllers for quick add dialogs final _whereWillAppearController = TextEditingController(); + final _competitorInputController = TextEditingController(); // State bool _isLoading = false; @@ -54,18 +53,17 @@ class _DefineBrandPageState extends State<DefineBrandPage> { _descriptionController.dispose(); _problemController.dispose(); _goalController.dispose(); - // audience controller removed; not used in this form - // no controller for keywords or competitors (we use chip lists) _whereWillAppearController.dispose(); + _competitorInputController.dispose(); super.dispose(); } Future<void> _handleFinish() async { - // 1. Basic Validation + // 1. Basic Validation - only Project Name is required if (_projectNameController.text.trim().isEmpty) { ScaffoldMessenger.of( context, - ).showSnackBar(const SnackBar(content: Text('Project name is missing.'))); + ).showSnackBar(const SnackBar(content: Text('Project name is required.'))); return; } @@ -75,8 +73,6 @@ class _DefineBrandPageState extends State<DefineBrandPage> { try { // 2. Prepare the data - // Note: Your createProject method needs to be updated to accept - // 'brandData' or these specific fields if you want to save them. final brandData = { 'description': _descriptionController.text.trim(), 'problem': _problemController.text.trim(), @@ -87,21 +83,19 @@ class _DefineBrandPageState extends State<DefineBrandPage> { }; // 3. Call the Service - // Debug: log brandData while backend integration is pending debugPrint('BrandData: $brandData'); await _projectService.createProject( - _projectNameController.text.trim(), + _projectNameController.text.trim().isEmpty + ? 'Untitled Project' + : _projectNameController.text.trim(), description: _descriptionController.text.trim().isEmpty ? null : _descriptionController.text.trim(), - // TODO: Pass brandData here if your service supports it - // brandData: brandData, ); // 4. Success Handling if (mounted) { - // Pop with 'true' to indicate success so previous page can reload if needed Navigator.pop(context, true); } } catch (e) { @@ -123,56 +117,86 @@ class _DefineBrandPageState extends State<DefineBrandPage> { } } + void _addCompetitorBrand() { + final brandName = _competitorInputController.text.trim(); + if (brandName.isNotEmpty) { + final initial = brandName[0].toUpperCase(); + setState(() { + _competitorBrands.add({ + 'name': brandName, + 'initial': initial, + }); + _competitorInputController.clear(); + }); + } + } + @override Widget build(BuildContext context) { + final theme = Theme.of(context); + final isDark = theme.brightness == Brightness.dark; + return Scaffold( - backgroundColor: Variables.background, - appBar: AppBar( - backgroundColor: Variables.background, - elevation: 0, - leading: IconButton( - icon: const Icon(Icons.arrow_back, color: Variables.textPrimary), - onPressed: () => Navigator.pop(context), - ), - title: const SizedBox.shrink(), - centerTitle: false, - // no actions (Skip removed) - ), + backgroundColor: const Color(0xFFFAFAFA), 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), + padding: EdgeInsets.zero, + constraints: const BoxConstraints(), + ), + ], + ), + ), + + // Scrollable Content Expanded( child: SingleChildScrollView( - padding: const EdgeInsets.all(24.0), + padding: const EdgeInsets.fromLTRB(16, 0, 16, 0), child: Column( crossAxisAlignment: CrossAxisAlignment.start, children: [ - const SizedBox(height: 8), - // Header area with icon, title and subtitle - Row( - crossAxisAlignment: CrossAxisAlignment.center, - children: [ + const SizedBox(height: 16), + + // Icon Container( - width: 44, - height: 44, + width: 52, + height: 52, decoration: BoxDecoration( - color: const Color(0xFFEFF6FF), - borderRadius: BorderRadius.circular(44), + color: const Color(0xFFE0E7FF), + borderRadius: BorderRadius.circular(1000), ), child: Center( child: SvgPicture.asset( 'assets/icons/painting-ai-line.svg', - width: 22, - height: 22, - color: Variables.textPrimary, - ), + width: 24, + height: 24, + colorFilter: const ColorFilter.mode( + Color(0xFF7C86FF), + BlendMode.srcIn, ), ), - const SizedBox(width: 12), - Expanded( - child: Column( + ), + ), + + const SizedBox(height: 16), + + // Title and Subtitle + Column( crossAxisAlignment: CrossAxisAlignment.start, - children: const [ + children: [ Text( 'Define Your Brand', style: TextStyle( @@ -183,7 +207,7 @@ class _DefineBrandPageState extends State<DefineBrandPage> { height: 24 / 20, ), ), - SizedBox(height: 6), + const SizedBox(height: 4), Text( 'Answer a few quick questions to help us craft your unique style guide.', style: TextStyle( @@ -191,128 +215,113 @@ class _DefineBrandPageState extends State<DefineBrandPage> { fontSize: 14, fontWeight: FontWeight.w400, color: Variables.textSecondary, - height: 18 / 14, - ), - ), - ], + height: 20 / 14, ), ), ], ), - const SizedBox(height: 20), - _buildSectionHeader("Project Name*"), - const SizedBox(height: 8), - _buildTextField( + + const SizedBox(height: 32), + + // Form Fields + Column( + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + // Project Name (required, first field) + _buildFormField( + label: 'Project Name', + hintText: 'Enter project name', controller: _projectNameController, - hintText: 'Project Name', - ), - const SizedBox(height: 18), - _buildSectionHeader("What do you want & who is it for"), - const SizedBox(height: 8), - _buildTextField( + required: true, + ), + const SizedBox(height: 16), + + // What do you want & who is it for + _buildFormField( + label: 'What do you want & who is it for.', + hintText: 'Describe your work and your audience.', controller: _descriptionController, - hintText: "Describe your work and your audience.", - maxLines: 4, - ), - const SizedBox(height: 18), + maxLines: 3, + required: false, + ), + const SizedBox(height: 16), - _buildSectionHeader("What problem you solve"), - const SizedBox(height: 8), - _buildTextField( + // What problem you solve + _buildFormField( + label: 'What problem you solve.', + hintText: 'Explain the main issue your brand addresses.', controller: _problemController, - hintText: "Explain the main issue your brand addresses.", maxLines: 3, - ), - const SizedBox(height: 18), + required: false, + ), + const SizedBox(height: 16), - _buildSectionHeader("Long-term goal for the brand"), - const SizedBox(height: 8), - _buildTextField( + // Long-term goal + _buildFormField( + label: 'Long-term goal for the brand.', + hintText: 'E.g. - Improving food availability...', controller: _goalController, - hintText: "E.g - Improving food availability...", - ), - const SizedBox(height: 18), + required: false, + ), + const SizedBox(height: 32), - _buildSectionHeader("2-3 vibe keywords*"), - const SizedBox(height: 8), - Wrap( - spacing: 8, - runSpacing: 8, - children: [ - for (final k in _keywords) - InputChip( - label: Text(k), - onDeleted: - () => setState(() => _keywords.remove(k)), - ), - ActionChip( - label: const Text('Add More +'), - onPressed: _showAddKeywordDialog, + // Keywords Section + _buildKeywordsSection(), + + const SizedBox(height: 32), + + // Competitor Brands Section + _buildCompetitorBrandsSection(), + + const SizedBox(height: 32), + + // Where will the brand appear + _buildFormField( + label: 'Where will the brand appear', + hintText: 'Banners, Posters, Instagram..', + controller: _whereWillAppearController, + required: false, ), ], ), - const SizedBox(height: 18), - - _buildSectionHeader("2-3 reference/competitor brands*"), - const SizedBox(height: 8), - SizedBox( - height: 56, - child: ListView.separated( - scrollDirection: Axis.horizontal, - padding: EdgeInsets.zero, - itemBuilder: (context, index) { - final b = _competitorBrands[index]; - return Chip( - label: Text(b['name'] ?? ''), - avatar: CircleAvatar( - child: Text(b['initial'] ?? ''), - ), - onDeleted: - () => setState( - () => _competitorBrands.removeAt(index), - ), - ); - }, - separatorBuilder: (_, __) => const SizedBox(width: 8), - itemCount: _competitorBrands.length, - ), - ), - const SizedBox(height: 10), - TextButton( - onPressed: _showAddCompetitorDialog, - child: const Text('Add more +'), - ), - const SizedBox(height: 18), - _buildSectionHeader("Where will the brand appear"), - const SizedBox(height: 8), - _buildTextField( - controller: _whereWillAppearController, - hintText: "Banners, Posters, Instagram...", - ), + const SizedBox(height: 100), // Space for bottom button ], ), ), ), + ], + ), + ), // Bottom Button - Padding( - padding: const EdgeInsets.all(24.0), + bottomNavigationBar: Container( + padding: const EdgeInsets.fromLTRB(16, 0, 16, 24), + decoration: BoxDecoration( + color: const Color(0xFFFAFAFA), + boxShadow: [ + BoxShadow( + color: Colors.black.withOpacity(0.05), + blurRadius: 10, + offset: const Offset(0, -2), + ), + ], + ), + child: SafeArea( child: SizedBox( width: double.infinity, child: ElevatedButton( onPressed: _isLoading ? null : _handleFinish, style: ElevatedButton.styleFrom( - backgroundColor: Variables.textPrimary, // Black - foregroundColor: Variables.textWhite, // White text - padding: const EdgeInsets.symmetric(vertical: 16), + backgroundColor: Variables.textPrimary, + foregroundColor: Colors.white, + padding: const EdgeInsets.symmetric(vertical: 12), shape: RoundedRectangleBorder( - borderRadius: BorderRadius.circular(12), + borderRadius: BorderRadius.circular(112), ), elevation: 0, ), - child: - _isLoading + child: _isLoading ? const SizedBox( height: 20, width: 20, @@ -325,110 +334,366 @@ class _DefineBrandPageState extends State<DefineBrandPage> { mainAxisAlignment: MainAxisAlignment.center, children: [ Text( - "Create Project", - style: Variables.buttonTextStyle.copyWith( - fontSize: 16, - ), - ), - const SizedBox(width: 8), + 'Create Project', + style: TextStyle( + fontFamily: 'GeneralSans', + fontSize: 14, + fontWeight: FontWeight.w500, + color: Colors.white, + ), + ), + const SizedBox(width: 12), SvgPicture.asset( 'assets/icons/generate_icon.svg', width: 18, height: 18, - color: Variables.textWhite, + colorFilter: const ColorFilter.mode( + Colors.white, + BlendMode.srcIn, + ), ), ], ), ), ), - ), - ], ), ), ); } - Widget _buildSectionHeader(String title) { - return Text( - title, - style: Variables.bodyStyle.copyWith( - fontWeight: FontWeight.w600, - fontSize: 14, - color: Variables.textPrimary, - ), - ); - } - - Widget _buildTextField({ - required TextEditingController controller, + Widget _buildFormField({ + required String label, required String hintText, + required TextEditingController controller, int maxLines = 1, + bool required = false, }) { - return Container( + return Column( + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + Row( + children: [ + Text( + label, + style: TextStyle( + fontFamily: 'GeneralSans', + fontSize: 14, + fontWeight: FontWeight.w500, + color: Variables.textPrimary, + height: 20 / 14, + ), + ), + if (required) + Text( + '*', + style: TextStyle( + fontFamily: 'GeneralSans', + fontSize: 12, + fontWeight: FontWeight.w500, + color: const Color(0xFF4F39F6), + height: 16 / 12, + ), + ), + ], + ), + const SizedBox(height: 6), + Container( decoration: BoxDecoration( - color: const Color(0xFFFAFAFA), // Light grey background - borderRadius: BorderRadius.circular(12), - border: Border.all(color: Variables.borderSubtle), + color: const Color(0xFFE4E4E7), + borderRadius: BorderRadius.circular(8), ), child: TextField( controller: controller, maxLines: maxLines, - style: Variables.bodyStyle, + style: TextStyle( + fontFamily: 'GeneralSans', + fontSize: 14, + fontWeight: FontWeight.w400, + color: Variables.textPrimary, + height: 20 / 14, + ), decoration: InputDecoration( hintText: hintText, - hintStyle: Variables.bodyStyle.copyWith( - color: Variables.textDisabled, + hintStyle: TextStyle( + fontFamily: 'GeneralSans', + fontSize: 14, + fontWeight: FontWeight.w400, + color: Variables.textSecondary, + height: 20 / 14, + ), + border: InputBorder.none, + enabledBorder: InputBorder.none, + focusedBorder: InputBorder.none, + contentPadding: const EdgeInsets.symmetric( + horizontal: 16, + vertical: 12, + ), + ), ), - border: InputBorder.none, - contentPadding: const EdgeInsets.all(16), ), - ), + ], ); } - 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'), + Widget _buildKeywordsSection() { + return Column( + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + Row( + children: [ + Text( + '2-3 vibe keywords', + style: TextStyle( + fontFamily: 'GeneralSans', + fontSize: 14, + fontWeight: FontWeight.w500, + color: Variables.textPrimary, + height: 20 / 14, + ), ), - actions: [ - TextButton( - onPressed: () => Navigator.pop(context), - child: const Text('Cancel'), + Text( + '*', + style: TextStyle( + fontFamily: 'GeneralSans', + fontSize: 12, + fontWeight: FontWeight.w500, + color: const Color(0xFF4F39F6), + height: 16 / 12, ), - ElevatedButton( - onPressed: () { - final val = controller.text.trim(); - if (val.isNotEmpty && !_keywords.contains(val)) { - setState(() => _keywords.add(val)); - } - Navigator.pop(context); - }, - child: const Text('Add'), + ), + ], + ), + const SizedBox(height: 8), + Wrap( + spacing: 8, + 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: TextStyle( + fontFamily: 'GeneralSans', + fontSize: 12, + fontWeight: FontWeight.w400, + color: Variables.textPrimary, + height: 16 / 12, + ), + ), + const SizedBox(width: 8), + GestureDetector( + onTap: () { + setState(() { + _keywords.remove(keyword); + }); + }, + child: Icon( + Icons.close, + size: 16, + color: Variables.textPrimary, + ), + ), + ], + ), ), - ], + GestureDetector( + onTap: _showAddKeywordDialog, + child: Container( + padding: const EdgeInsets.symmetric(horizontal: 12, vertical: 6), + decoration: BoxDecoration( + border: Border.all( + color: const Color(0xFFE4E4E7), + width: 1, + ), + borderRadius: BorderRadius.circular(48), + ), + child: Row( + mainAxisSize: MainAxisSize.min, + children: [ + Text( + 'Add More', + style: TextStyle( + fontFamily: 'GeneralSans', + fontSize: 12, + fontWeight: FontWeight.w400, + color: Variables.textPrimary, + height: 16 / 12, + ), + ), + const SizedBox(width: 4), + Icon( + Icons.add, + size: 14, + color: Variables.textPrimary, + ), + ], + ), + ), + ), + ], + ), + ], + ); + } + + Widget _buildCompetitorBrandsSection() { + return Column( + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + Row( + children: [ + Text( + '2-3 reference/competitor brands.', + style: TextStyle( + fontFamily: 'GeneralSans', + fontSize: 14, + fontWeight: FontWeight.w500, + color: Variables.textPrimary, + height: 20 / 14, + ), + ), + Text( + '*', + style: TextStyle( + fontFamily: 'GeneralSans', + fontSize: 12, + fontWeight: FontWeight.w500, + color: const Color(0xFF4F39F6), + height: 16 / 12, + ), + ), + ], + ), + const SizedBox(height: 6), + Container( + decoration: BoxDecoration( + color: const Color(0xFFE4E4E7), + borderRadius: BorderRadius.circular(8), + ), + child: TextField( + controller: _competitorInputController, + onSubmitted: (_) => _addCompetitorBrand(), + style: TextStyle( + fontFamily: 'GeneralSans', + fontSize: 14, + fontWeight: FontWeight.w400, + color: Variables.textPrimary, + height: 20 / 14, + ), + decoration: InputDecoration( + hintText: 'Type the name of brands here...', + hintStyle: TextStyle( + fontFamily: 'GeneralSans', + fontSize: 14, + fontWeight: FontWeight.w400, + color: Variables.textSecondary, + height: 20 / 14, + ), + border: InputBorder.none, + enabledBorder: InputBorder.none, + focusedBorder: InputBorder.none, + contentPadding: const EdgeInsets.symmetric( + horizontal: 16, + vertical: 12, + ), + ), + ), + ), + if (_competitorBrands.isNotEmpty) ...[ + const SizedBox(height: 8), + SizedBox( + height: 44, + child: ListView.builder( + scrollDirection: Axis.horizontal, + itemCount: _competitorBrands.length, + 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: const Color(0xFFFAFAFA), + border: Border.all( + color: const Color(0xFFE4E4E7), + width: 1, + ), + borderRadius: BorderRadius.circular(64), + ), + child: Row( + mainAxisSize: MainAxisSize.min, + children: [ + Container( + width: 30, + height: 30, + decoration: BoxDecoration( + color: Colors.white, + shape: BoxShape.circle, + ), + child: Center( + child: Text( + brand['initial'] ?? '', + style: TextStyle( + fontFamily: 'GeneralSans', + fontSize: 12, + fontWeight: FontWeight.w500, + color: Variables.textPrimary, + ), + ), + ), + ), + const SizedBox(width: 8), + Text( + brand['name'] ?? '', + style: TextStyle( + fontFamily: 'GeneralSans', + fontSize: 14, + fontWeight: FontWeight.w400, + color: Variables.textSecondary, + height: 20 / 14, + ), + ), + const SizedBox(width: 8), + GestureDetector( + onTap: () { + setState(() { + _competitorBrands.removeAt(index); + }); + }, + child: Icon( + Icons.close, + size: 16, + color: Variables.textSecondary, + ), + ), + ], + ), + ); + }, + ), ), + ], + ], ); } - void _showAddCompetitorDialog() { + void _showAddKeywordDialog() { final controller = TextEditingController(); showDialog<void>( context: context, - builder: - (context) => AlertDialog( - title: const Text('Add Competitor'), + builder: (context) => AlertDialog( + title: const Text('Add Keyword'), content: TextField( controller: controller, autofocus: true, - decoration: const InputDecoration(hintText: 'Brand name'), + decoration: const InputDecoration(hintText: 'e.g., Minimal'), ), actions: [ TextButton( @@ -438,14 +703,10 @@ class _DefineBrandPageState extends State<DefineBrandPage> { ElevatedButton( onPressed: () { final val = controller.text.trim(); - if (val.isNotEmpty) { - final initial = val.isNotEmpty ? val[0].toUpperCase() : ''; - setState( - () => _competitorBrands.add({ - 'name': val, - 'initial': initial, - }), - ); + if (val.isNotEmpty && !_keywords.contains(val)) { + setState(() { + _keywords.add(val); + }); } Navigator.pop(context); }, @@ -455,4 +716,4 @@ class _DefineBrandPageState extends State<DefineBrandPage> { ), ); } -} +} +\ No newline at end of file diff --git a/lib/ui/pages/home_page.dart b/lib/ui/pages/home_page.dart @@ -222,35 +222,46 @@ class _HomePageState extends State<HomePage> { SliverToBoxAdapter( child: Padding( padding: const EdgeInsets.symmetric(horizontal: 16), - child: Container( - decoration: BoxDecoration( - color: isDark ? Colors.grey[800] : Colors.grey[200], - borderRadius: BorderRadius.circular(8), - ), - child: TextField( - decoration: InputDecoration( - hintText: 'Search', - hintStyle: TextStyle( - fontSize: 12, - color: theme.colorScheme.onSurface.withOpacity(0.5), - fontFamily: 'GeneralSans', - ), - prefixIcon: Icon( - Icons.search, - size: 18, - color: theme.colorScheme.onSurface.withOpacity(0.5), - ), - border: InputBorder.none, - contentPadding: const EdgeInsets.symmetric( - horizontal: 16, - vertical: 12, - ), - ), - style: TextStyle( + child: TextField( + decoration: InputDecoration( + hintText: 'Search', + hintStyle: TextStyle( fontSize: 12, + color: theme.colorScheme.onSurface.withOpacity(0.5), fontFamily: 'GeneralSans', - color: theme.colorScheme.onSurface, ), + prefixIcon: Icon( + Icons.search, + size: 18, + color: theme.colorScheme.onSurface.withOpacity(0.5), + ), + prefixIconConstraints: const BoxConstraints( + minWidth: 50, + minHeight: 18, + ), + filled: true, + fillColor: isDark ? Colors.grey[800] : Colors.grey[200], + border: OutlineInputBorder( + borderRadius: BorderRadius.circular(8), + borderSide: BorderSide.none, + ), + enabledBorder: OutlineInputBorder( + borderRadius: BorderRadius.circular(8), + borderSide: BorderSide.none, + ), + focusedBorder: OutlineInputBorder( + borderRadius: BorderRadius.circular(8), + borderSide: BorderSide.none, + ), + contentPadding: const EdgeInsets.symmetric( + horizontal: 16, + vertical: 12, + ), + ), + style: TextStyle( + fontSize: 12, + fontFamily: 'GeneralSans', + color: theme.colorScheme.onSurface, ), ), ),