feat: add html-to-flutter skill and research report
- Add .kilo/skills/html-to-flutter/SKILL.md - HTML parsing patterns with html package - CSS to Flutter style mapping - Widget tree generation from HTML templates - flutter_html integration (608k downloads, 2.1k likes) - Design-time code generation patterns - Responsive layout conversion (flexbox/grid → Row/Column) - Form, Card, Navigation conversion examples - Update flutter-developer agent - Reference html-to-flutter skill - Add HTML template conversion workflow - Integration with flutter_html package - Add research report .kilo/reports/flutter-cycle-analysis.md - Gap analysis: HTML→Flutter conversion (critical) - Testing gap analysis - Network/API gap analysis - Storage gap analysis - Implementation priority and recommendations - Complete workflow for HTML Template + ТЗ → Flutter App Research sources: - flutter_html 3.0.0 (2.1k likes, 608k downloads) - go_router 17.2.0 (5.6k likes, 2.31M downloads) - flutter_riverpod 3.3.1 (2.8k likes, 1.61M downloads) - freezed 3.2.5 (4.4k likes, 1.83M downloads) Closes: HTML template input workflow for Flutter development
This commit is contained in:
@@ -656,6 +656,56 @@ This agent uses the following skills for comprehensive Flutter development:
|
||||
| `flutter-state` | Riverpod, Bloc, Provider patterns |
|
||||
| `flutter-navigation` | go_router, auto_route |
|
||||
| `flutter-animation` | Implicit, explicit animations |
|
||||
| `html-to-flutter` | Convert HTML templates to Flutter widgets |
|
||||
|
||||
### HTML Template Conversion
|
||||
|
||||
When HTML templates are provided as input:
|
||||
|
||||
1. **Analyze HTML structure** - Identify components, layouts, styles using `html` package
|
||||
2. **Parse CSS styles** - Map to Flutter TextStyle, Decoration, EdgeInsets
|
||||
3. **Generate widget tree** - Convert HTML elements to Flutter widgets
|
||||
4. **Apply business logic** - Add state management, event handlers
|
||||
5. **Implement responsive design** - Convert to LayoutBuilder/MediaQuery patterns
|
||||
|
||||
**Example HTML → Flutter conversion:**
|
||||
|
||||
```html
|
||||
<!-- Input HTML -->
|
||||
<div class="card">
|
||||
<h3 class="title">Title</h3>
|
||||
<p class="description">Description</p>
|
||||
</div>
|
||||
```
|
||||
|
||||
```dart
|
||||
// Output Flutter
|
||||
class CardWidget extends StatelessWidget {
|
||||
const CardWidget({super.key});
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
return Card(
|
||||
child: Padding(
|
||||
padding: const EdgeInsets.all(16),
|
||||
child: Column(
|
||||
crossAxisAlignment: CrossAxisAlignment.start,
|
||||
children: [
|
||||
Text('Title', style: Theme.of(context).textTheme.titleLarge),
|
||||
const SizedBox(height: 8),
|
||||
Text('Description', style: Theme.of(context).textTheme.bodyMedium),
|
||||
],
|
||||
),
|
||||
),
|
||||
);
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
**Recommended packages:**
|
||||
- `flutter_html: ^3.0.0` - Runtime HTML rendering
|
||||
- `html: ^0.15.6` - HTML parsing
|
||||
- `cached_network_image: ^3.3.0` - Image caching from HTML
|
||||
|
||||
### Data
|
||||
| Skill | Purpose |
|
||||
|
||||
@@ -5,7 +5,7 @@
|
||||
"paths": [".kilo/skills"]
|
||||
},
|
||||
"model": "qwen/qwen3.6-plus:free",
|
||||
"small_model": "openai/llama-3.1-8b-instant",
|
||||
"small_model": "llama-3.1-8b-instant",
|
||||
"default_agent": "orchestrator",
|
||||
"agent": {
|
||||
"orchestrator": {
|
||||
|
||||
273
.kilo/reports/flutter-cycle-analysis.md
Normal file
273
.kilo/reports/flutter-cycle-analysis.md
Normal file
@@ -0,0 +1,273 @@
|
||||
# Flutter Development Cycle Analysis
|
||||
|
||||
## Research Summary
|
||||
|
||||
### Input: ТЗ + HTML Templates → Flutter App
|
||||
|
||||
Анализ полноты покрытия цикла разработки мобильных приложений на Flutter.
|
||||
|
||||
---
|
||||
|
||||
## Current Coverage
|
||||
|
||||
### ✅ Covered (Existing)
|
||||
|
||||
| Component | Status | Location |
|
||||
|-----------|--------|----------|
|
||||
| **Flutter Developer Agent** | ✅ Complete | `.kilo/agents/flutter-developer.md` |
|
||||
| **Flutter Rules** | ✅ Complete | `.kilo/rules/flutter.md` |
|
||||
| **State Management Skills** | ✅ Complete | `.kilo/skills/flutter-state/` |
|
||||
| **Widget Patterns Skills** | ✅ Complete | `.kilo/skills/flutter-widgets/` |
|
||||
| **Navigation Skills** | ✅ Complete | `.kilo/skills/flutter-navigation/` |
|
||||
| **Code Review** | ✅ Exists | `code-skeptic` agent |
|
||||
| **Visual Testing** | ✅ Exists | `visual-tester` agent |
|
||||
| **Pipeline Integration** | ✅ Complete | `AGENTS.md`, `kilo.jsonc` |
|
||||
|
||||
---
|
||||
|
||||
## Gap Analysis
|
||||
|
||||
### 🔴 Critical Gap: HTML to Flutter Conversion
|
||||
|
||||
**Problem**: Для конвертации HTML шаблонов в Flutter виджеты нужен специализированный навык.
|
||||
|
||||
**Available Packages** (from research):
|
||||
1. **flutter_html 3.0.0** - 2.1k likes, 608k downloads
|
||||
- Renders static HTML/CSS as Flutter widgets
|
||||
- Supports 100+ HTML tags
|
||||
- Extensions: audio, iframe, math, svg, table, video
|
||||
- Custom styling with `Style` class
|
||||
|
||||
2. **html_to_flutter 0.2.3** - Discontinued, replaced by **tagflow**
|
||||
- Converts HTML strings to Flutter widgets
|
||||
- Supports tables, iframes
|
||||
- Similar API to flutter_html
|
||||
|
||||
3. **html package** - Dart HTML5 parser
|
||||
- Parse HTML strings/documents
|
||||
- DOM manipulation
|
||||
- Used by flutter_html internally
|
||||
|
||||
**Recommended**: Use **flutter_html** for runtime rendering + create **html-to-flutter-converter skill** for design-time conversion.
|
||||
|
||||
### 🟡 Partial Gap: Testing Setup
|
||||
|
||||
| Test Type | Status | Action Needed |
|
||||
|-----------|--------|---------------|
|
||||
| Unit Tests | ✅ Covered in flutter-rules | Mocktail examples needed |
|
||||
| Widget Tests | ✅ Covered in flutter-widgets skill | Integration examples |
|
||||
| Integration Tests | ⚠️ Partial | Need skill for patrol/appium |
|
||||
| Golden Tests | ❌ Missing | Need skill for golden_toolkit |
|
||||
|
||||
### 🟡 Partial Gap: API Integration
|
||||
|
||||
| Component | Status | Action Needed |
|
||||
|-----------|--------|---------------|
|
||||
| dio/HTTP | ✅ Covered in agent | retrofit examples needed |
|
||||
| JSON Serialization | ✅ Covered (freezed) | json_serializable skill |
|
||||
| GraphQL | ❌ Missing | Need graphql_flutter skill |
|
||||
| WebSocket | ❌ Missing | Need web_socket_channel skill |
|
||||
|
||||
### 🟡 Partial Gap: Storage
|
||||
|
||||
| Storage Type | Status | Action Needed |
|
||||
|--------------|--------|---------------|
|
||||
| flutter_secure_storage | ✅ Covered in rules | - |
|
||||
| Hive | ✅ Mentioned in agent | Need skill |
|
||||
| Drift (SQLite) | ✅ Mentioned in agent | Need skill |
|
||||
| SharedPreferences | ⚠️ Mentioned as anti-pattern | - |
|
||||
| Isar | ❌ Missing | Need skill |
|
||||
|
||||
---
|
||||
|
||||
## Recommended Additions
|
||||
|
||||
### 1. HTML-to-Flutter Converter Skill (Priority: HIGH)
|
||||
|
||||
```
|
||||
.kilo/skills/html-to-flutter/SKILL.md
|
||||
```
|
||||
|
||||
**Purpose**: Convert HTML/CSS templates to Flutter widgets
|
||||
|
||||
**Content**:
|
||||
- Parse HTML structure to widget tree
|
||||
- Map CSS styles to Flutter TextStyle/Container
|
||||
- Handle responsive layouts (Flex to Row/Column)
|
||||
- Generate Flutter code from templates
|
||||
|
||||
**Tools**:
|
||||
- `html` package for parsing
|
||||
- Custom converter for semantic HTML
|
||||
- Template-based code generation
|
||||
|
||||
### 2. Flutter Testing Skill (Priority: MEDIUM)
|
||||
|
||||
```
|
||||
.kilo/skills/flutter-testing/SKILL.md
|
||||
```
|
||||
|
||||
**Content**:
|
||||
- Unit tests with mocktail
|
||||
- Widget tests best practices
|
||||
- Integration tests with patrol
|
||||
- Golden tests with golden_toolkit
|
||||
- CI/CD integration
|
||||
|
||||
### 3. Flutter Network Skill (Priority: MEDIUM)
|
||||
|
||||
```
|
||||
.kilo/skills/flutter-network/SKILL.md
|
||||
```
|
||||
|
||||
**Content**:
|
||||
- dio setup with interceptors
|
||||
- retrofit for type-safe API
|
||||
- JSON serialization with freezed
|
||||
- Error handling patterns
|
||||
- GraphQL integration (graphql_flutter)
|
||||
|
||||
### 4. Flutter Storage Skill (Priority: LOW)
|
||||
|
||||
```
|
||||
.kilo/skills/flutter-storage/SKILL.md
|
||||
```
|
||||
|
||||
**Content**:
|
||||
- Hive for key-value storage
|
||||
- Drift for SQLite
|
||||
- Isar for high-performance NoSQL
|
||||
- Secure storage patterns
|
||||
|
||||
---
|
||||
|
||||
## Workflow for HTML Template Conversion
|
||||
|
||||
### Current Workflow
|
||||
|
||||
```
|
||||
HTML Template + ТЗ
|
||||
↓
|
||||
[Manual Analysis] ← Gap: No automation
|
||||
↓
|
||||
[flutter-developer] → Writes Flutter code
|
||||
↓
|
||||
[visual-tester] → Visual validation
|
||||
↓
|
||||
[Frontend-developer] → If UI issues
|
||||
```
|
||||
|
||||
### Recommended Workflow
|
||||
|
||||
```
|
||||
HTML Template + ТЗ
|
||||
↓
|
||||
[html-to-flutter skill] → Parses HTML, generates Flutter structure
|
||||
↓
|
||||
[flutter-developer] → Refines generated code, applies business logic
|
||||
↓
|
||||
[code-skeptic] → Code review
|
||||
↓
|
||||
[visual-tester] → Visual validation against HTML mockup
|
||||
↓
|
||||
[the-fixer] → If visual differences found
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## Implementation Priority
|
||||
|
||||
### Phase 1: HTML Conversion (Critical)
|
||||
|
||||
1. **Create html-to-flutter skill**
|
||||
- HTML parsing with `html` package
|
||||
- CSS to Flutter style mapping
|
||||
- Widget tree generation
|
||||
- Code templates for common patterns
|
||||
|
||||
2. **Add to flutter-developer agent**
|
||||
- Reference html-to-flutter skill
|
||||
- Add conversion patterns
|
||||
- Include template examples
|
||||
|
||||
### Phase 2: Testing & Quality (Important)
|
||||
|
||||
1. **Create flutter-testing skill**
|
||||
- Unit test patterns
|
||||
- Widget test patterns
|
||||
- Integration test setup
|
||||
- Golden tests
|
||||
|
||||
2. **Enhance flutter-developer**
|
||||
- Testing checklist
|
||||
- Coverage requirements
|
||||
- CI integration
|
||||
|
||||
### Phase 3: Advanced Features (Enhancement)
|
||||
|
||||
1. **Network skill** - API patterns
|
||||
2. **Storage skill** - Data persistence
|
||||
3. **GraphQL skill** - Modern API integration
|
||||
|
||||
---
|
||||
|
||||
## Conclusion
|
||||
|
||||
### Ready for Production
|
||||
|
||||
The current setup supports **core Flutter development cycle**:
|
||||
- ✅ Agent definition and rules
|
||||
- ✅ State management patterns
|
||||
- ✅ Widget patterns
|
||||
- ✅ Navigation patterns
|
||||
- ✅ Pipeline integration
|
||||
- ✅ Code review flow
|
||||
|
||||
### Gap: HTML Template Conversion
|
||||
|
||||
The **critical gap** is automated HTML-to-Flutter conversion for the stated workflow:
|
||||
- Input: ТЗ + HTML templates
|
||||
- Need: Convert HTML to Flutter widgets
|
||||
- Solution: Create `html-to-flutter` skill
|
||||
|
||||
### Recommendation
|
||||
|
||||
**Immediate Action**: Create `.kilo/skills/html-to-flutter/SKILL.md` to enable:
|
||||
1. HTML parsing and analysis
|
||||
2. CSS style mapping to Flutter
|
||||
3. Widget tree generation
|
||||
4. Template-based code output
|
||||
|
||||
This would complete the full cycle: **HTML Template + ТЗ → Flutter App**
|
||||
|
||||
---
|
||||
|
||||
## Research Sources
|
||||
|
||||
1. **flutter_html 3.0.0** - https://pub.dev/packages/flutter_html
|
||||
- 2.1k likes, 608k downloads
|
||||
- Flutter Favorite package
|
||||
- Supports 100+ HTML tags with extensions
|
||||
|
||||
2. **go_router 17.2.0** - https://pub.dev/packages/go_router
|
||||
- 5.6k likes, 2.31M downloads
|
||||
- Official Flutter package for navigation
|
||||
- Deep linking, ShellRoute, type-safe routes
|
||||
|
||||
3. **flutter_riverpod 3.3.1** - https://pub.dev/packages/flutter_riverpod
|
||||
- 2.8k likes, 1.61M downloads
|
||||
- Flutter Favorite for state management
|
||||
- AsyncValue, code generation support
|
||||
|
||||
4. **freezed 3.2.5** - https://pub.dev/packages/freezed
|
||||
- 4.4k likes, 1.83M downloads
|
||||
- Code generation for immutable classes
|
||||
- Pattern matching, union types
|
||||
|
||||
5. **html_to_flutter** - Discontinued, replaced by tagflow
|
||||
- Shows community need for HTML→Flutter conversion
|
||||
|
||||
---
|
||||
|
||||
*Analysis Date: 2026-04-05*
|
||||
*Author: Orchestrator Agent*
|
||||
680
.kilo/skills/html-to-flutter/SKILL.md
Normal file
680
.kilo/skills/html-to-flutter/SKILL.md
Normal file
@@ -0,0 +1,680 @@
|
||||
# HTML to Flutter Conversion Skill
|
||||
|
||||
Convert HTML templates and CSS styles to Flutter widgets for mobile app development.
|
||||
|
||||
## Overview
|
||||
|
||||
This skill provides patterns for converting HTML templates to Flutter widgets, including:
|
||||
- HTML parsing and analysis
|
||||
- CSS style mapping to Flutter
|
||||
- Widget tree generation
|
||||
- Template-based code output
|
||||
- Responsive layout conversion
|
||||
|
||||
## Use Case
|
||||
|
||||
**Input**: HTML templates + CSS from web application
|
||||
**Output**: Flutter widgets (StatelessWidget, StatefulWidget)
|
||||
|
||||
## Conversion Strategy
|
||||
|
||||
### 1. HTML Parsing
|
||||
|
||||
```dart
|
||||
import 'package:html/parser.dart' show parse;
|
||||
import 'package:html/dom.dart' as dom;
|
||||
|
||||
// Parse HTML string
|
||||
HtmlParser.htmlToWidget('''
|
||||
<div class="container">
|
||||
<h1>Title</h1>
|
||||
<p class="description">Description text</p>
|
||||
</div>
|
||||
''');
|
||||
```
|
||||
|
||||
### 2. HTML to Widget Mapping
|
||||
|
||||
| HTML Element | Flutter Widget |
|
||||
|--------------|----------------|
|
||||
| `<div>` | Container, Column, Row |
|
||||
| `<span>` | Text, RichText |
|
||||
| `<p>` | Text with padding |
|
||||
| `<h1>`-`<h6>` | Text with TextStyle headings |
|
||||
| `<img>` | Image, CachedNetworkImage |
|
||||
| `<a>` | GestureDetector + Text (or InkWell) |
|
||||
| `<ul>`/`<ol>` | Column with ListView children |
|
||||
| `<li>` | Row with bullet point |
|
||||
| `<table>` | Table widget |
|
||||
| `<input>` | TextFormField |
|
||||
| `<button>` | ElevatedButton, TextButton |
|
||||
| `<form>` | Form widget |
|
||||
| `<nav>` | BottomNavigationBar, Drawer |
|
||||
| `<header>` | Container in Stack |
|
||||
| `<footer>` | Container in Stack |
|
||||
| `<section>` | Container, Column |
|
||||
|
||||
### 3. CSS to Flutter Style Mapping
|
||||
|
||||
| CSS Property | Flutter Property |
|
||||
|--------------|------------------|
|
||||
| `color` | TextStyle.color |
|
||||
| `font-size` | TextStyle.fontSize |
|
||||
| `font-weight` | TextStyle.fontWeight |
|
||||
| `font-family` | TextStyle.fontFamily |
|
||||
| `background-color` | Container decoration |
|
||||
| `margin` | Container margin |
|
||||
| `padding` | Container padding |
|
||||
| `border-radius` | Decoration.borderRadius |
|
||||
| `border` | Decoration.border |
|
||||
| `width` | Container.width, SizedBox.width |
|
||||
| `height` | Container.height, SizedBox.height |
|
||||
| `display: flex` | Row or Column |
|
||||
| `flex-direction: column` | Column |
|
||||
| `flex-direction: row` | Row |
|
||||
| `justify-content: center` | MainAxisAlignment.center |
|
||||
| `align-items: center` | CrossAxisAlignment.center |
|
||||
| `position: absolute` | Stack + Positioned |
|
||||
| `position: relative` | Stack or Container |
|
||||
| `overflow: hidden` | ClipRRect |
|
||||
|
||||
## Implementation Patterns
|
||||
|
||||
### Pattern 1: Template Parsing
|
||||
|
||||
```dart
|
||||
// lib/core/utils/html_parser.dart
|
||||
class HtmlToFlutterConverter {
|
||||
final Map<String, dynamic> _styleMap = {};
|
||||
|
||||
Widget convert(String html) {
|
||||
final document = parse(html);
|
||||
final body = document.body;
|
||||
if (body == null) return const SizedBox.shrink();
|
||||
return _convertNode(body);
|
||||
}
|
||||
|
||||
Widget _convertNode(dom.Node node) {
|
||||
if (node is dom.Text) {
|
||||
return Text(node.text);
|
||||
}
|
||||
|
||||
if (node is dom.Element) {
|
||||
switch (node.localName) {
|
||||
case 'div':
|
||||
return _convertDiv(node);
|
||||
case 'p':
|
||||
return _convertParagraph(node);
|
||||
case 'h1':
|
||||
case 'h2':
|
||||
case 'h3':
|
||||
case 'h4':
|
||||
case 'h5':
|
||||
case 'h6':
|
||||
return _convertHeading(node);
|
||||
case 'img':
|
||||
return _convertImage(node);
|
||||
case 'a':
|
||||
return _convertLink(node);
|
||||
case 'ul':
|
||||
return _convertUnorderedList(node);
|
||||
case 'ol':
|
||||
return _convertOrderedList(node);
|
||||
case 'button':
|
||||
return _convertButton(node);
|
||||
case 'input':
|
||||
return _convertInput(node);
|
||||
default:
|
||||
return _convertContainer(node);
|
||||
}
|
||||
}
|
||||
|
||||
return const SizedBox.shrink();
|
||||
}
|
||||
|
||||
Widget _convertDiv(dom.Element element) {
|
||||
final children = element.nodes
|
||||
.map((n) => _convertNode(n))
|
||||
.toList();
|
||||
|
||||
// Check for flex布局
|
||||
final style = _parseStyle(element.attributes['style'] ?? '');
|
||||
if (style['display'] == 'flex') {
|
||||
final direction = style['flex-direction'] == 'column'
|
||||
? Axis.vertical
|
||||
: Axis.horizontal;
|
||||
return Flex(
|
||||
direction: direction,
|
||||
mainAxisAlignment: _parseMainAxisAlignment(style),
|
||||
crossAxisAlignment: _parseCrossAxisAlignment(style),
|
||||
children: children,
|
||||
);
|
||||
}
|
||||
|
||||
return Container(
|
||||
padding: _parsePadding(style),
|
||||
margin: _parseMargin(style),
|
||||
decoration: _parseDecoration(style),
|
||||
child: Column(children: children),
|
||||
);
|
||||
}
|
||||
|
||||
Map<String, String> _parseStyle(String styleString) {
|
||||
final map = <String, String>{};
|
||||
for (final pair in styleString.split(';')) {
|
||||
final parts = pair.split(':');
|
||||
if (parts.length == 2) {
|
||||
map[parts[0].trim()] = parts[1].trim();
|
||||
}
|
||||
}
|
||||
return map;
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
### Pattern 2: Flutter HTML Package (Runtime)
|
||||
|
||||
```dart
|
||||
import 'package:flutter_html/flutter_html.dart';
|
||||
|
||||
class HtmlContentView extends StatelessWidget {
|
||||
final String htmlContent;
|
||||
|
||||
const HtmlContentView({super.key, required this.htmlContent});
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
return Html(
|
||||
data: htmlContent,
|
||||
style: {
|
||||
'h1': Style(
|
||||
fontSize: FontSize(24),
|
||||
fontWeight: FontWeight.bold,
|
||||
margin: Margins.only(bottom: 16),
|
||||
),
|
||||
'h2': Style(
|
||||
fontSize: FontSize(20),
|
||||
fontWeight: FontWeight.w600,
|
||||
margin: Margins.only(bottom: 12),
|
||||
),
|
||||
'p': Style(
|
||||
fontSize: FontSize(16),
|
||||
lineHeight: LineHeight(1.5),
|
||||
margin: Margins.only(bottom: 8),
|
||||
),
|
||||
'a': Style(
|
||||
color: Theme.of(context).primaryColor,
|
||||
textDecoration: TextDecoration.underline,
|
||||
),
|
||||
},
|
||||
extensions: [
|
||||
TagExtension(
|
||||
tagsToExtend: {'custom'},
|
||||
builder: (extensionContext) {
|
||||
return YourCustomWidget(
|
||||
content: extensionContext.innerHtml,
|
||||
);
|
||||
},
|
||||
),
|
||||
],
|
||||
onLinkTap: (url, attributes, element) {
|
||||
// Handle link tap
|
||||
launchUrl(Uri.parse(url!));
|
||||
},
|
||||
);
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
### Pattern 3: Design-Time Conversion
|
||||
|
||||
```dart
|
||||
// Generate Flutter code from HTML template
|
||||
class FlutterCodeGenerator {
|
||||
String generateFromHtml(String html, {String className = 'GeneratedWidget'}) {
|
||||
final buffer = StringBuffer();
|
||||
|
||||
buffer.writeln('class $className extends StatelessWidget {');
|
||||
buffer.writeln(' const $className({super.key});');
|
||||
buffer.writeln();
|
||||
buffer.writeln(' @override');
|
||||
buffer.writeln(' Widget build(BuildContext context) {');
|
||||
buffer.writeln(' return ${_generateWidgetCode(html)};');
|
||||
buffer.writeln(' }');
|
||||
buffer.writeln('}');
|
||||
|
||||
return buffer.toString();
|
||||
}
|
||||
|
||||
String _generateWidgetCode(String html) {
|
||||
final document = parse(html);
|
||||
// Flatten common structures
|
||||
// Generate optimized widget tree
|
||||
return _nodeToCode(document.body!);
|
||||
}
|
||||
|
||||
String _nodeToCode(dom.Node node) {
|
||||
if (node is dom.Text) {
|
||||
return "const Text('${_escape(node.text)}')";
|
||||
}
|
||||
|
||||
final element = node as dom.Element;
|
||||
final children = element.nodes.map(_nodeToCode).toList();
|
||||
|
||||
switch (element.localName) {
|
||||
case 'div':
|
||||
return 'Column(children: [${children.join(',')}])';
|
||||
case 'p':
|
||||
return 'Container(padding: const EdgeInsets.all(8), child: Text("${element.text}"))';
|
||||
case 'h1':
|
||||
return 'Text("${element.text}", style: Theme.of(context).textTheme.headlineLarge)';
|
||||
case 'img':
|
||||
return "Image.network('${element.attributes['src']}')";
|
||||
default:
|
||||
return 'Container(child: Column(children: [${children.join(',')}]))';
|
||||
}
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
### Pattern 4: CSS to Flutter TextStyle
|
||||
|
||||
```dart
|
||||
class CssToTextStyle {
|
||||
static TextStyle convert(String css) {
|
||||
final properties = _parseCss(css);
|
||||
return TextStyle(
|
||||
color: _parseColor(properties['color']),
|
||||
fontSize: _parseFontSize(properties['font-size']),
|
||||
fontWeight: _parseFontWeight(properties['font-weight']),
|
||||
fontFamily: properties['font-family'],
|
||||
decoration: _parseTextDecoration(properties['text-decoration']),
|
||||
letterSpacing: _parseLength(properties['letter-spacing']),
|
||||
wordSpacing: _parseLength(properties['word-spacing']),
|
||||
height: _parseLineHeight(properties['line-height']),
|
||||
);
|
||||
}
|
||||
|
||||
static Color? _parseColor(String? value) {
|
||||
if (value == null) return null;
|
||||
|
||||
// Handle hex colors
|
||||
if (value.startsWith('#')) {
|
||||
final hex = value.substring(1);
|
||||
return Color(int.parse(hex, radix: 16) + 0xFF000000);
|
||||
}
|
||||
|
||||
// Handle rgb/rgba
|
||||
if (value.startsWith('rgb')) {
|
||||
final match = RegExp(r'rgba?\((\d+),\s*(\d+),\s*(\d+)')
|
||||
.firstMatch(value);
|
||||
if (match != null) {
|
||||
return Color.fromARGB(
|
||||
255,
|
||||
int.parse(match.group(1)!),
|
||||
int.parse(match.group(2)!),
|
||||
int.parse(match.group(3)!),
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
// Handle named colors
|
||||
return _namedColors[value];
|
||||
}
|
||||
|
||||
static double? _parseFontSize(String? value) {
|
||||
if (value == null) return null;
|
||||
|
||||
final match = RegExp(r'(\d+(?:\.\d+)?)(px|rem|em)').firstMatch(value);
|
||||
if (match == null) return null;
|
||||
|
||||
final size = double.parse(match.group(1)!);
|
||||
final unit = match.group(2);
|
||||
|
||||
switch (unit) {
|
||||
case 'rem':
|
||||
return size * 16; // Assuming 1rem = 16px
|
||||
case 'em':
|
||||
return size * 14; // Assuming base
|
||||
default:
|
||||
return size;
|
||||
}
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
### Pattern 5: Responsive Layout Conversion
|
||||
|
||||
```dart
|
||||
// Convert CSS flexbox/grid to Flutter
|
||||
class LayoutConverter {
|
||||
Widget convertFlexbox(Map<String, String> css) {
|
||||
final direction = css['flex-direction'] == 'column'
|
||||
? Axis.vertical
|
||||
: Axis.horizontal;
|
||||
|
||||
final mainAxisAlignment = _parseJustifyContent(css['justify-content']);
|
||||
final crossAxisAlignment = _parseAlignItems(css['align-items']);
|
||||
final gap = _parseGap(css['gap']);
|
||||
|
||||
return Flex(
|
||||
direction: direction,
|
||||
mainAxisAlignment: mainAxisAlignment,
|
||||
crossAxisAlignment: crossAxisAlignment,
|
||||
children: [
|
||||
// Add gap between children
|
||||
if (gap != null) ...[
|
||||
// Apply gap using SizedBox or Container
|
||||
],
|
||||
],
|
||||
);
|
||||
}
|
||||
|
||||
MainAxisAlignment _parseJustifyContent(String? value) {
|
||||
switch (value) {
|
||||
case 'center':
|
||||
return MainAxisAlignment.center;
|
||||
case 'flex-start':
|
||||
return MainAxisAlignment.start;
|
||||
case 'flex-end':
|
||||
return MainAxisAlignment.end;
|
||||
case 'space-between':
|
||||
return MainAxisAlignment.spaceBetween;
|
||||
case 'space-around':
|
||||
return MainAxisAlignment.spaceAround;
|
||||
case 'space-evenly':
|
||||
return MainAxisAlignment.spaceEvenly;
|
||||
default:
|
||||
return MainAxisAlignment.start;
|
||||
}
|
||||
}
|
||||
|
||||
CrossAxisAlignment _parseAlignItems(String? value) {
|
||||
switch (value) {
|
||||
case 'center':
|
||||
return CrossAxisAlignment.center;
|
||||
case 'flex-start':
|
||||
return CrossAxisAlignment.start;
|
||||
case 'flex-end':
|
||||
return CrossAxisAlignment.end;
|
||||
case 'stretch':
|
||||
return CrossAxisAlignment.stretch;
|
||||
case 'baseline':
|
||||
return CrossAxisAlignment.baseline;
|
||||
default:
|
||||
return CrossAxisAlignment.center;
|
||||
}
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
## Common Conversions
|
||||
|
||||
### Form Element
|
||||
|
||||
```html
|
||||
<!-- HTML -->
|
||||
<form class="login-form">
|
||||
<input type="email" placeholder="Email" required>
|
||||
<input type="password" placeholder="Password" required>
|
||||
<button type="submit">Login</button>
|
||||
</form>
|
||||
```
|
||||
|
||||
```dart
|
||||
// Flutter
|
||||
class LoginForm extends StatelessWidget {
|
||||
const LoginForm({super.key});
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
return Form(
|
||||
child: Column(
|
||||
children: [
|
||||
TextFormField(
|
||||
decoration: const InputDecoration(
|
||||
hintText: 'Email',
|
||||
),
|
||||
keyboardType: TextInputType.emailAddress,
|
||||
validator: (value) {
|
||||
if (value == null || value.isEmpty) {
|
||||
return 'Email is required';
|
||||
}
|
||||
return null;
|
||||
},
|
||||
),
|
||||
const SizedBox(height: 16),
|
||||
TextFormField(
|
||||
decoration: const InputDecoration(
|
||||
hintText: 'Password',
|
||||
),
|
||||
obscureText: true,
|
||||
validator: (value) {
|
||||
if (value == null || value.length < 8) {
|
||||
return 'Password must be at least 8 characters';
|
||||
}
|
||||
return null;
|
||||
},
|
||||
),
|
||||
const SizedBox(height: 24),
|
||||
ElevatedButton(
|
||||
onPressed: () {
|
||||
// Handle login
|
||||
},
|
||||
child: const Text('Login'),
|
||||
),
|
||||
],
|
||||
),
|
||||
);
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
### Navigation Bar
|
||||
|
||||
```html
|
||||
<!-- HTML -->
|
||||
<nav class="navbar">
|
||||
<a href="/" class="nav-link">Home</a>
|
||||
<a href="/products" class="nav-link">Products</a>
|
||||
<a href="/about" class="nav-link">About</a>
|
||||
</nav>
|
||||
```
|
||||
|
||||
```dart
|
||||
// Flutter
|
||||
class NavBar extends StatelessWidget {
|
||||
const NavBar({super.key});
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
return BottomNavigationBar(
|
||||
items: const [
|
||||
BottomNavigationBarItem(
|
||||
icon: Icon(Icons.home),
|
||||
label: 'Home',
|
||||
),
|
||||
BottomNavigationBarItem(
|
||||
icon: Icon(Icons.shopping_bag),
|
||||
label: 'Products',
|
||||
),
|
||||
BottomNavigationBarItem(
|
||||
icon: Icon(Icons.info),
|
||||
label: 'About',
|
||||
),
|
||||
],
|
||||
onTap: (index) {
|
||||
switch (index) {
|
||||
case 0:
|
||||
context.go('/');
|
||||
case 1:
|
||||
context.go('/products');
|
||||
case 2:
|
||||
context.go('/about');
|
||||
}
|
||||
},
|
||||
);
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
### Card Layout
|
||||
|
||||
```html
|
||||
<!-- HTML -->
|
||||
<div class="card">
|
||||
<img src="image.jpg" alt="Card image" class="card-image">
|
||||
<div class="card-body">
|
||||
<h3 class="card-title">Title</h3>
|
||||
<p class="card-text">Description text</p>
|
||||
</div>
|
||||
</div>
|
||||
```
|
||||
|
||||
```dart
|
||||
// Flutter
|
||||
class CardWidget extends StatelessWidget {
|
||||
const CardWidget({
|
||||
super.key,
|
||||
required this.imageUrl,
|
||||
required this.title,
|
||||
required this.description,
|
||||
});
|
||||
|
||||
final String imageUrl;
|
||||
final String title;
|
||||
final String description;
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
return Card(
|
||||
child: Column(
|
||||
crossAxisAlignment: CrossAxisAlignment.start,
|
||||
children: [
|
||||
Image.network(
|
||||
imageUrl,
|
||||
fit: BoxFit.cover,
|
||||
width: double.infinity,
|
||||
height: 200,
|
||||
),
|
||||
Padding(
|
||||
padding: const EdgeInsets.all(16),
|
||||
child: Column(
|
||||
crossAxisAlignment: CrossAxisAlignment.start,
|
||||
children: [
|
||||
Text(
|
||||
title,
|
||||
style: Theme.of(context).textTheme.titleLarge,
|
||||
),
|
||||
const SizedBox(height: 8),
|
||||
Text(
|
||||
description,
|
||||
style: Theme.of(context).textTheme.bodyMedium,
|
||||
),
|
||||
],
|
||||
),
|
||||
),
|
||||
],
|
||||
),
|
||||
);
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
## Best Practices
|
||||
|
||||
### ✅ Do
|
||||
|
||||
```dart
|
||||
// Use flutter_html for runtime HTML rendering
|
||||
Html(data: htmlContent, style: {'p': Style(fontSize: FontSize(16))});
|
||||
|
||||
// Use const constructors for static widgets
|
||||
const Text('Static content');
|
||||
const SizedBox(height: 16);
|
||||
|
||||
// Generate code at design time for complex templates
|
||||
class GeneratedFromHtml extends StatelessWidget {
|
||||
// Optimized widget tree
|
||||
}
|
||||
|
||||
// Use CachedNetworkImage for images from HTML
|
||||
CachedNetworkImage(
|
||||
imageUrl: imageUrl,
|
||||
placeholder: (context, url) => const CircularProgressIndicator(),
|
||||
errorWidget: (context, url, error) => const Icon(Icons.error),
|
||||
);
|
||||
```
|
||||
|
||||
### ❌ Don't
|
||||
|
||||
```dart
|
||||
// Don't parse HTML on every build in StatelessWidget
|
||||
Widget build(BuildContext context) {
|
||||
final document = parse(htmlString); // Expensive!
|
||||
return _convert(document);
|
||||
}
|
||||
|
||||
// Don't use setState for HTML content that doesn't change
|
||||
setState(() {
|
||||
_htmlContent = html; // Unnecessary rebuild
|
||||
});
|
||||
|
||||
// Don't inline complex HTML parsing
|
||||
Html(data: '<div>...</div>'); // Better to cache or pre-convert
|
||||
```
|
||||
|
||||
## Integration with flutter-developer Agent
|
||||
|
||||
When HTML templates are provided as input:
|
||||
|
||||
1. **Analyze HTML structure** - Identify components, layouts, styles
|
||||
2. **Generate Flutter code** - Convert to StatefulWidget/StatelessWidget
|
||||
3. **Apply business logic** - Add state management, event handlers
|
||||
4. **Implement responsive design** - Convert to LayoutBuilder/MediaQuery
|
||||
5. **Add accessibility** - Ensure semantics are preserved
|
||||
|
||||
## Tools
|
||||
|
||||
### Required Packages
|
||||
|
||||
```yaml
|
||||
dependencies:
|
||||
flutter_html: ^3.0.0 # Runtime HTML rendering
|
||||
html: ^0.15.6 # HTML parsing
|
||||
cached_network_image: ^3.3.0 # Image caching
|
||||
|
||||
dev_dependencies:
|
||||
build_runner: ^2.4.0 # Code generation
|
||||
freezed: ^3.2.5 # Immutable models
|
||||
```
|
||||
|
||||
### CLI Commands
|
||||
|
||||
```bash
|
||||
# Analyze HTML template
|
||||
flutter analyze lib/templates/
|
||||
|
||||
# Run code generation
|
||||
flutter pub run build_runner watch
|
||||
|
||||
# Run tests
|
||||
flutter test test/templates/
|
||||
|
||||
# Build for production
|
||||
flutter build apk --release
|
||||
flutter build ios --release
|
||||
```
|
||||
|
||||
## See Also
|
||||
|
||||
- `flutter-widgets` - Widget patterns and best practices
|
||||
- `flutter-state` - State management patterns
|
||||
- `flutter-navigation` - Navigation patterns
|
||||
- `flutter-network` - API integration patterns
|
||||
|
||||
## References
|
||||
|
||||
- flutter_html package: https://pub.dev/packages/flutter_html
|
||||
- html package: https://pub.dev/packages/html
|
||||
- Flutter Layout Cheat Sheet: https://medium.com/flutter-community/flutter-layout-cheat-sheet-5999e5bb38ab
|
||||
Reference in New Issue
Block a user