appwrite_generator 0.1.0
appwrite_generator: ^0.1.0 copied to clipboard
A powerful Dart CLI tool that generates type-safe, production-ready Dart models from Appwrite configuration JSON files with automatic serialization and null-safety support.
import 'package:flutter/material.dart';
import 'package:intl/intl.dart';
import 'models/models.dart';
import 'models/projects/project_enums.dart' as project_enums;
import 'models/todos/todo_enums.dart' as todo_enums;
void main() {
runApp(const TodoApp());
}
class TodoApp extends StatelessWidget {
const TodoApp({super.key});
@override
Widget build(BuildContext context) {
return MaterialApp(
title: 'Appwrite Generator Example',
theme: ThemeData(
colorScheme: ColorScheme.fromSeed(seedColor: Colors.blue),
useMaterial3: true,
),
home: const TodoHomePage(),
);
}
}
class TodoHomePage extends StatefulWidget {
const TodoHomePage({super.key});
@override
State<TodoHomePage> createState() => _TodoHomePageState();
}
class _TodoHomePageState extends State<TodoHomePage> {
// Sample data to demonstrate the generated models
late List<Project> projects;
late List<Todo> todos;
late List<Tag> tags;
late List<Comment> comments;
int selectedProjectIndex = 0;
@override
void initState() {
super.initState();
_initializeSampleData();
}
void _initializeSampleData() {
// Create sample projects
projects = [
Project(
title: 'Personal',
description: 'Personal tasks and errands',
color: '#2196F3',
icon: 'person',
status: project_enums.Status.active,
createdAt: DateTime.now().subtract(const Duration(days: 30)),
updatedAt: DateTime.now(),
),
Project(
title: 'Work',
description: 'Work-related projects',
color: '#4CAF50',
icon: 'work',
status: project_enums.Status.active,
createdAt: DateTime.now().subtract(const Duration(days: 20)),
updatedAt: DateTime.now(),
),
Project(
title: 'Learning',
description: 'Educational content and courses',
color: '#FF9800',
status: project_enums.Status.active,
createdAt: DateTime.now().subtract(const Duration(days: 10)),
updatedAt: DateTime.now(),
),
];
// Create sample tags
tags = [
Tag(
name: 'urgent',
color: '#F44336',
createdAt: DateTime.now().subtract(const Duration(days: 15)),
),
Tag(
name: 'bug',
color: '#E91E63',
createdAt: DateTime.now().subtract(const Duration(days: 10)),
),
Tag(
name: 'feature',
color: '#9C27B0',
createdAt: DateTime.now().subtract(const Duration(days: 8)),
),
];
// Create sample todos
todos = [
Todo(
title: 'Review Appwrite Generator Documentation',
description: 'Go through the README and understand all features',
projectId: 'project_1',
priority: todo_enums.Priority.high,
status: todo_enums.Status.inProgress,
dueDate: DateTime.now().add(const Duration(days: 2)),
estimatedHours: 3,
createdAt: DateTime.now().subtract(const Duration(days: 2)),
updatedAt: DateTime.now(),
),
Todo(
title: 'Set up local Appwrite instance',
description: 'Install and configure Appwrite for testing',
projectId: 'project_2',
priority: todo_enums.Priority.urgent,
status: todo_enums.Status.todo,
dueDate: DateTime.now().add(const Duration(days: 1)),
estimatedHours: 2,
createdAt: DateTime.now().subtract(const Duration(days: 1)),
updatedAt: DateTime.now(),
),
Todo(
title: 'Test generated models',
description: 'Verify all CRUD operations work with generated code',
projectId: 'project_2',
priority: todo_enums.Priority.medium,
status: todo_enums.Status.todo,
estimatedHours: 5,
createdAt: DateTime.now().subtract(const Duration(hours: 12)),
updatedAt: DateTime.now(),
),
Todo(
title: 'Learn Flutter state management',
description: 'Study Provider, Riverpod, and Bloc patterns',
projectId: 'project_3',
priority: todo_enums.Priority.low,
status: todo_enums.Status.todo,
dueDate: DateTime.now().add(const Duration(days: 7)),
estimatedHours: 10,
createdAt: DateTime.now().subtract(const Duration(hours: 6)),
updatedAt: DateTime.now(),
),
Todo(
title: 'Buy groceries',
description: 'Milk, eggs, bread, vegetables',
projectId: 'project_1',
priority: todo_enums.Priority.medium,
status: todo_enums.Status.done,
completedAt: DateTime.now().subtract(const Duration(hours: 2)),
estimatedHours: 1,
createdAt: DateTime.now().subtract(const Duration(days: 1)),
updatedAt: DateTime.now(),
),
];
// Create sample comments
comments = [
Comment(
todoId: 'todo_1',
content: 'The generator documentation is very comprehensive!',
authorName: 'John Doe',
createdAt: DateTime.now().subtract(const Duration(hours: 5)),
updatedAt: DateTime.now().subtract(const Duration(hours: 5)),
),
Comment(
todoId: 'todo_1',
content: 'Make sure to check out the enum generation feature',
authorName: 'Jane Smith',
createdAt: DateTime.now().subtract(const Duration(hours: 3)),
updatedAt: DateTime.now().subtract(const Duration(hours: 3)),
),
];
}
@override
Widget build(BuildContext context) {
final currentProject = projects[selectedProjectIndex];
final projectTodos = todos.where((t) =>
t.projectId == 'project_${selectedProjectIndex + 1}'
).toList();
return Scaffold(
appBar: AppBar(
backgroundColor: Theme.of(context).colorScheme.inversePrimary,
title: const Text('Appwrite Generator - To-Do Example'),
),
drawer: _buildProjectDrawer(),
body: Column(
children: [
_buildProjectHeader(currentProject),
_buildStatsCard(projectTodos),
const Padding(
padding: EdgeInsets.all(16.0),
child: Text(
'Tasks',
style: TextStyle(fontSize: 20, fontWeight: FontWeight.bold),
),
),
Expanded(
child: projectTodos.isEmpty
? const Center(child: Text('No tasks in this project'))
: ListView.builder(
itemCount: projectTodos.length,
itemBuilder: (context, index) {
return _buildTodoCard(projectTodos[index]);
},
),
),
],
),
floatingActionButton: FloatingActionButton.extended(
onPressed: () {
_showAddTodoDialog(context);
},
icon: const Icon(Icons.add),
label: const Text('Add Task'),
),
);
}
Widget _buildProjectDrawer() {
return Drawer(
child: ListView(
padding: EdgeInsets.zero,
children: [
const DrawerHeader(
decoration: BoxDecoration(
gradient: LinearGradient(
colors: [Colors.blue, Colors.blueAccent],
),
),
child: Column(
crossAxisAlignment: CrossAxisAlignment.start,
mainAxisAlignment: MainAxisAlignment.end,
children: [
Icon(Icons.dashboard, size: 48, color: Colors.white),
SizedBox(height: 8),
Text(
'Projects',
style: TextStyle(
color: Colors.white,
fontSize: 24,
fontWeight: FontWeight.bold,
),
),
],
),
),
...projects.asMap().entries.map((entry) {
final index = entry.key;
final project = entry.value;
return ListTile(
leading: Icon(
_getIconData(project.icon),
color: _parseColor(project.color),
),
title: Text(project.title),
subtitle: Text(project.description ?? ''),
selected: selectedProjectIndex == index,
onTap: () {
setState(() {
selectedProjectIndex = index;
});
Navigator.pop(context);
},
);
}),
const Divider(),
ListTile(
leading: const Icon(Icons.label),
title: const Text('All Tags'),
subtitle: Text('${tags.length} tags'),
onTap: () {
Navigator.pop(context);
_showTagsDialog(context);
},
),
],
),
);
}
Widget _buildProjectHeader(Project project) {
return Container(
padding: const EdgeInsets.all(16),
decoration: BoxDecoration(
color: _parseColor(project.color).withOpacity(0.1),
border: Border(
bottom: BorderSide(
color: _parseColor(project.color),
width: 2,
),
),
),
child: Row(
children: [
Icon(
_getIconData(project.icon),
size: 32,
color: _parseColor(project.color),
),
const SizedBox(width: 12),
Expanded(
child: Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
Text(
project.title,
style: const TextStyle(
fontSize: 24,
fontWeight: FontWeight.bold,
),
),
if (project.description != null)
Text(
project.description!,
style: TextStyle(
fontSize: 14,
color: Colors.grey[600],
),
),
],
),
),
_buildStatusChip(project.status),
],
),
);
}
Widget _buildStatsCard(List<Todo> projectTodos) {
final total = projectTodos.length;
final completed = projectTodos.where((t) => t.status == todo_enums.Status.done).length;
final inProgress = projectTodos.where((t) => t.status == todo_enums.Status.inProgress).length;
final blocked = projectTodos.where((t) => t.status == todo_enums.Status.blocked).length;
return Card(
margin: const EdgeInsets.all(16),
child: Padding(
padding: const EdgeInsets.all(16),
child: Row(
mainAxisAlignment: MainAxisAlignment.spaceAround,
children: [
_buildStatItem('Total', total, Colors.blue),
_buildStatItem('Done', completed, Colors.green),
_buildStatItem('In Progress', inProgress, Colors.orange),
if (blocked > 0) _buildStatItem('Blocked', blocked, Colors.red),
],
),
),
);
}
Widget _buildStatItem(String label, int count, Color color) {
return Column(
children: [
Text(
count.toString(),
style: TextStyle(
fontSize: 24,
fontWeight: FontWeight.bold,
color: color,
),
),
Text(
label,
style: TextStyle(
fontSize: 12,
color: Colors.grey[600],
),
),
],
);
}
Widget _buildTodoCard(Todo todo) {
return Card(
margin: const EdgeInsets.symmetric(horizontal: 16, vertical: 8),
child: InkWell(
onTap: () => _showTodoDetails(context, todo),
child: Padding(
padding: const EdgeInsets.all(16),
child: Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
Row(
children: [
Expanded(
child: Text(
todo.title,
style: TextStyle(
fontSize: 16,
fontWeight: FontWeight.bold,
decoration: todo.status == todo_enums.Status.done
? TextDecoration.lineThrough
: null,
),
),
),
_buildPriorityBadge(todo.priority),
],
),
if (todo.description != null) ...[
const SizedBox(height: 8),
Text(
todo.description!,
style: TextStyle(
color: Colors.grey[600],
fontSize: 14,
),
maxLines: 2,
overflow: TextOverflow.ellipsis,
),
],
const SizedBox(height: 12),
Row(
children: [
_buildStatusChip(todo.status),
const SizedBox(width: 8),
if (todo.dueDate != null) ...[
Icon(Icons.calendar_today, size: 16, color: Colors.grey[600]),
const SizedBox(width: 4),
Text(
DateFormat('MMM dd').format(todo.dueDate!),
style: TextStyle(
fontSize: 12,
color: _isOverdue(todo) ? Colors.red : Colors.grey[600],
fontWeight: _isOverdue(todo) ? FontWeight.bold : FontWeight.normal,
),
),
const SizedBox(width: 8),
],
if (todo.estimatedHours != null) ...[
Icon(Icons.access_time, size: 16, color: Colors.grey[600]),
const SizedBox(width: 4),
Text(
'${todo.estimatedHours}h',
style: TextStyle(fontSize: 12, color: Colors.grey[600]),
),
],
],
),
],
),
),
),
);
}
Widget _buildPriorityBadge(todo_enums.Priority priority) {
Color color;
IconData icon;
switch (priority) {
case todo_enums.Priority.urgent:
color = Colors.red;
icon = Icons.priority_high;
case todo_enums.Priority.high:
color = Colors.orange;
icon = Icons.arrow_upward;
case todo_enums.Priority.medium:
color = Colors.blue;
icon = Icons.remove;
case todo_enums.Priority.low:
color = Colors.grey;
icon = Icons.arrow_downward;
}
return Container(
padding: const EdgeInsets.symmetric(horizontal: 8, vertical: 4),
decoration: BoxDecoration(
color: color.withOpacity(0.1),
borderRadius: BorderRadius.circular(12),
border: Border.all(color: color),
),
child: Row(
mainAxisSize: MainAxisSize.min,
children: [
Icon(icon, size: 14, color: color),
const SizedBox(width: 4),
Text(
priority.value.toUpperCase(),
style: TextStyle(
fontSize: 10,
fontWeight: FontWeight.bold,
color: color,
),
),
],
),
);
}
Widget _buildStatusChip(dynamic status) {
Color color;
String label;
if (status is project_enums.Status) {
switch (status) {
case project_enums.Status.active:
color = Colors.green;
label = 'Active';
case project_enums.Status.archived:
color = Colors.grey;
label = 'Archived';
case project_enums.Status.completed:
color = Colors.blue;
label = 'Completed';
}
} else {
// Status enum
switch (status as todo_enums.Status) {
case todo_enums.Status.todo:
color = Colors.grey;
label = 'To Do';
case todo_enums.Status.inProgress:
color = Colors.orange;
label = 'In Progress';
case todo_enums.Status.blocked:
color = Colors.red;
label = 'Blocked';
case todo_enums.Status.done:
color = Colors.green;
label = 'Done';
}
}
return Container(
padding: const EdgeInsets.symmetric(horizontal: 8, vertical: 4),
decoration: BoxDecoration(
color: color.withOpacity(0.1),
borderRadius: BorderRadius.circular(12),
),
child: Text(
label,
style: TextStyle(
fontSize: 12,
fontWeight: FontWeight.bold,
color: color,
),
),
);
}
bool _isOverdue(Todo todo) {
if (todo.dueDate == null || todo.status == todo_enums.Status.done) return false;
return todo.dueDate!.isBefore(DateTime.now());
}
Color _parseColor(String? hexColor) {
if (hexColor == null) return Colors.blue;
final hex = hexColor.replaceAll('#', '');
return Color(int.parse('FF$hex', radix: 16));
}
IconData _getIconData(String? iconName) {
switch (iconName) {
case 'person':
return Icons.person;
case 'work':
return Icons.work;
default:
return Icons.folder;
}
}
void _showTodoDetails(BuildContext context, Todo todo) {
final relatedComments = comments.where((c) => c.todoId == 'todo_1').toList();
showDialog(
context: context,
builder: (context) => AlertDialog(
title: Text(todo.title),
content: SingleChildScrollView(
child: Column(
crossAxisAlignment: CrossAxisAlignment.start,
mainAxisSize: MainAxisSize.min,
children: [
if (todo.description != null) ...[
const Text(
'Description',
style: TextStyle(fontWeight: FontWeight.bold),
),
const SizedBox(height: 4),
Text(todo.description!),
const SizedBox(height: 16),
],
_buildDetailRow('Priority', todo.priority.value),
_buildDetailRow('Status', todo.status.value),
if (todo.dueDate != null)
_buildDetailRow(
'Due Date',
DateFormat('MMM dd, yyyy').format(todo.dueDate!),
),
if (todo.estimatedHours != null)
_buildDetailRow('Estimated', '${todo.estimatedHours} hours'),
if (todo.completedAt != null)
_buildDetailRow(
'Completed',
DateFormat('MMM dd, yyyy HH:mm').format(todo.completedAt!),
),
const SizedBox(height: 16),
const Text(
'Comments',
style: TextStyle(fontWeight: FontWeight.bold, fontSize: 16),
),
const SizedBox(height: 8),
if (relatedComments.isEmpty)
const Text('No comments yet')
else
...relatedComments.map((comment) => Card(
margin: const EdgeInsets.only(bottom: 8),
child: Padding(
padding: const EdgeInsets.all(12),
child: Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
Text(
comment.authorName ?? 'Anonymous',
style: const TextStyle(fontWeight: FontWeight.bold),
),
const SizedBox(height: 4),
Text(comment.content),
const SizedBox(height: 4),
Text(
DateFormat('MMM dd, HH:mm').format(comment.createdAt),
style: TextStyle(
fontSize: 12,
color: Colors.grey[600],
),
),
],
),
),
)),
],
),
),
actions: [
TextButton(
onPressed: () => Navigator.pop(context),
child: const Text('Close'),
),
],
),
);
}
Widget _buildDetailRow(String label, String value) {
return Padding(
padding: const EdgeInsets.only(bottom: 8),
child: Row(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
SizedBox(
width: 100,
child: Text(
'$label:',
style: const TextStyle(fontWeight: FontWeight.bold),
),
),
Expanded(child: Text(value)),
],
),
);
}
void _showAddTodoDialog(BuildContext context) {
showDialog(
context: context,
builder: (context) => AlertDialog(
title: const Text('Add New Task'),
content: const Text(
'This is a demo app showcasing the Appwrite Generator.\n\n'
'In a real app, this would show a form to create a new task '
'using the generated Todo model with proper validation and '
'Appwrite SDK integration.',
),
actions: [
TextButton(
onPressed: () => Navigator.pop(context),
child: const Text('Got it'),
),
],
),
);
}
void _showTagsDialog(BuildContext context) {
showDialog(
context: context,
builder: (context) => AlertDialog(
title: const Text('All Tags'),
content: SizedBox(
width: double.maxFinite,
child: ListView.builder(
shrinkWrap: true,
itemCount: tags.length,
itemBuilder: (context, index) {
final tag = tags[index];
return ListTile(
leading: Container(
width: 24,
height: 24,
decoration: BoxDecoration(
color: _parseColor(tag.color),
shape: BoxShape.circle,
),
),
title: Text(tag.name),
subtitle: Text(
'Created ${DateFormat('MMM dd, yyyy').format(tag.createdAt)}',
),
);
},
),
),
actions: [
TextButton(
onPressed: () => Navigator.pop(context),
child: const Text('Close'),
),
],
),
);
}
}