227 lines
6.6 KiB
Dart
227 lines
6.6 KiB
Dart
import 'package:flutter/material.dart';
|
|
import 'package:flutter_riverpod/flutter_riverpod.dart';
|
|
import 'package:go_router/go_router.dart';
|
|
import '../../../shared/services/api_client.dart';
|
|
import '../../clients/presentation/clients_screen.dart';
|
|
|
|
class EmailComposeScreen extends ConsumerStatefulWidget {
|
|
final String? clientId;
|
|
|
|
const EmailComposeScreen({super.key, this.clientId});
|
|
|
|
@override
|
|
ConsumerState<EmailComposeScreen> createState() => _EmailComposeScreenState();
|
|
}
|
|
|
|
class _EmailComposeScreenState extends ConsumerState<EmailComposeScreen> {
|
|
final _purposeController = TextEditingController();
|
|
final _subjectController = TextEditingController();
|
|
final _contentController = TextEditingController();
|
|
|
|
String? _selectedClientId;
|
|
bool _isGenerating = false;
|
|
bool _hasGenerated = false;
|
|
|
|
@override
|
|
void initState() {
|
|
super.initState();
|
|
_selectedClientId = widget.clientId;
|
|
}
|
|
|
|
@override
|
|
void dispose() {
|
|
_purposeController.dispose();
|
|
_subjectController.dispose();
|
|
_contentController.dispose();
|
|
super.dispose();
|
|
}
|
|
|
|
Future<void> _generateEmail() async {
|
|
if (_selectedClientId == null) {
|
|
ScaffoldMessenger.of(context).showSnackBar(
|
|
const SnackBar(content: Text('Please select a client')),
|
|
);
|
|
return;
|
|
}
|
|
|
|
if (_purposeController.text.isEmpty) {
|
|
ScaffoldMessenger.of(context).showSnackBar(
|
|
const SnackBar(content: Text('Please enter a purpose')),
|
|
);
|
|
return;
|
|
}
|
|
|
|
setState(() => _isGenerating = true);
|
|
|
|
try {
|
|
final result = await ref.read(apiClientProvider).generateEmail(
|
|
clientId: _selectedClientId!,
|
|
purpose: _purposeController.text.trim(),
|
|
);
|
|
|
|
setState(() {
|
|
_subjectController.text = result['subject'] ?? '';
|
|
_contentController.text = result['content'] ?? '';
|
|
_hasGenerated = true;
|
|
});
|
|
} catch (e) {
|
|
if (mounted) {
|
|
ScaffoldMessenger.of(context).showSnackBar(
|
|
SnackBar(
|
|
content: Text('Failed to generate: $e'),
|
|
duration: const Duration(seconds: 10),
|
|
action: SnackBarAction(
|
|
label: 'Dismiss',
|
|
onPressed: () {},
|
|
),
|
|
),
|
|
);
|
|
}
|
|
} finally {
|
|
if (mounted) {
|
|
setState(() => _isGenerating = false);
|
|
}
|
|
}
|
|
}
|
|
|
|
@override
|
|
Widget build(BuildContext context) {
|
|
final clientsAsync = ref.watch(clientsProvider(null));
|
|
|
|
return Scaffold(
|
|
appBar: AppBar(
|
|
title: const Text('Compose Email'),
|
|
actions: [
|
|
if (_hasGenerated)
|
|
TextButton(
|
|
onPressed: () {
|
|
// Email was already saved as draft during generation
|
|
context.go('/emails');
|
|
},
|
|
child: const Text('Done'),
|
|
),
|
|
],
|
|
),
|
|
body: ListView(
|
|
padding: const EdgeInsets.all(16),
|
|
children: [
|
|
// Client selector
|
|
Text(
|
|
'Select Client',
|
|
style: Theme.of(context).textTheme.titleMedium?.copyWith(
|
|
fontWeight: FontWeight.bold,
|
|
),
|
|
),
|
|
const SizedBox(height: 8),
|
|
clientsAsync.when(
|
|
data: (clients) => DropdownButtonFormField<String>(
|
|
value: _selectedClientId,
|
|
decoration: const InputDecoration(
|
|
hintText: 'Choose a client',
|
|
),
|
|
items: clients.map((client) => DropdownMenuItem(
|
|
value: client['id'] as String,
|
|
child: Text('${client['firstName']} ${client['lastName']}'),
|
|
)).toList(),
|
|
onChanged: (value) {
|
|
setState(() {
|
|
_selectedClientId = value;
|
|
_hasGenerated = false;
|
|
});
|
|
},
|
|
),
|
|
loading: () => const LinearProgressIndicator(),
|
|
error: (e, s) => Text('Error loading clients: $e'),
|
|
),
|
|
|
|
const SizedBox(height: 24),
|
|
|
|
// Purpose input
|
|
Text(
|
|
'Email Purpose',
|
|
style: Theme.of(context).textTheme.titleMedium?.copyWith(
|
|
fontWeight: FontWeight.bold,
|
|
),
|
|
),
|
|
const SizedBox(height: 8),
|
|
TextFormField(
|
|
controller: _purposeController,
|
|
decoration: const InputDecoration(
|
|
hintText: 'e.g., Follow up after meeting, Birthday wishes, Market update',
|
|
),
|
|
maxLines: 2,
|
|
),
|
|
|
|
const SizedBox(height: 16),
|
|
|
|
// Generate button
|
|
FilledButton.icon(
|
|
onPressed: _isGenerating ? null : _generateEmail,
|
|
icon: _isGenerating
|
|
? const SizedBox(
|
|
width: 20,
|
|
height: 20,
|
|
child: CircularProgressIndicator(strokeWidth: 2),
|
|
)
|
|
: const Icon(Icons.auto_awesome),
|
|
label: Text(_isGenerating ? 'Generating...' : 'Generate with AI'),
|
|
),
|
|
|
|
if (_hasGenerated) ...[
|
|
const SizedBox(height: 32),
|
|
const Divider(),
|
|
const SizedBox(height: 16),
|
|
|
|
// Subject
|
|
Text(
|
|
'Subject',
|
|
style: Theme.of(context).textTheme.titleMedium?.copyWith(
|
|
fontWeight: FontWeight.bold,
|
|
),
|
|
),
|
|
const SizedBox(height: 8),
|
|
TextFormField(
|
|
controller: _subjectController,
|
|
decoration: const InputDecoration(
|
|
hintText: 'Email subject',
|
|
),
|
|
),
|
|
|
|
const SizedBox(height: 16),
|
|
|
|
// Content
|
|
Text(
|
|
'Content',
|
|
style: Theme.of(context).textTheme.titleMedium?.copyWith(
|
|
fontWeight: FontWeight.bold,
|
|
),
|
|
),
|
|
const SizedBox(height: 8),
|
|
TextFormField(
|
|
controller: _contentController,
|
|
decoration: const InputDecoration(
|
|
hintText: 'Email content',
|
|
),
|
|
maxLines: 12,
|
|
),
|
|
|
|
const SizedBox(height: 16),
|
|
|
|
Row(
|
|
children: [
|
|
Expanded(
|
|
child: OutlinedButton.icon(
|
|
onPressed: _generateEmail,
|
|
icon: const Icon(Icons.refresh),
|
|
label: const Text('Regenerate'),
|
|
),
|
|
),
|
|
],
|
|
),
|
|
],
|
|
],
|
|
),
|
|
);
|
|
}
|
|
}
|