Files

185 lines
5.3 KiB
Dart

import 'package:flutter/material.dart';
import '../api/client.dart';
import '../api/models.dart';
import '../widgets/pipeline_card.dart';
import 'pipeline_editor.dart';
import 'pipeline_detail.dart';
class PipelineListScreen extends StatefulWidget {
const PipelineListScreen({super.key});
@override
State<PipelineListScreen> createState() => _PipelineListScreenState();
}
class _PipelineListScreenState extends State<PipelineListScreen> {
late APIClient _api;
List<Pipeline> _pipelines = [];
bool _loading = true;
String? _error;
@override
void initState() {
super.initState();
final url = _parseBaseUrl();
_api = APIClient(baseUrl: url);
_load();
}
static String _parseBaseUrl() {
final uri = Uri.base;
return '${uri.scheme}://${uri.host}${uri.port != 80 && uri.port != 443 ? ':${uri.port}' : ''}';
}
Future<void> _load() async {
setState(() {
_loading = true;
_error = null;
});
try {
final pipelines = await _api.listPipelines();
setState(() {
_pipelines = pipelines;
_loading = false;
});
} catch (e) {
setState(() {
_error = e.toString();
_loading = false;
});
}
}
Future<void> _createPipeline() async {
final result = await Navigator.push<Pipeline>(
context,
MaterialPageRoute(builder: (_) => const PipelineEditorScreen()),
);
if (result != null) {
_pipelines.add(result);
setState(() {});
}
}
void _navigateToDetail(Pipeline p) {
Navigator.push(
context,
MaterialPageRoute(builder: (_) => PipelineDetailScreen(pipeline: p)),
);
}
Future<void> _deletePipeline(Pipeline p) async {
final confirmed = await showDialog<bool>(
context: context,
builder: (ctx) => AlertDialog(
title: const Text('Delete Pipeline'),
content: Text('Are you sure you want to delete "${p.name}"?'),
actions: [
TextButton(
onPressed: () => Navigator.pop(ctx, false),
child: const Text('Cancel'),
),
FilledButton(
onPressed: () => Navigator.pop(ctx, true),
child: const Text('Delete'),
),
],
),
);
if (confirmed != true) return;
try {
await _api.deletePipeline(p.id);
setState(() {
_pipelines.removeWhere((pl) => pl.id == p.id);
});
} catch (e) {
if (mounted) {
ScaffoldMessenger.of(
context,
).showSnackBar(SnackBar(content: Text(e.toString())));
}
}
}
@override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(
title: const Text('Zaloopipe'),
actions: [
IconButton(
icon: const Icon(Icons.refresh),
onPressed: _load,
tooltip: 'Refresh',
),
],
),
body: _loading
? const Center(child: CircularProgressIndicator())
: _error != null
? Center(
child: Column(
mainAxisSize: MainAxisSize.min,
children: [
const Icon(Icons.error_outline, size: 48, color: Colors.red),
const SizedBox(height: 16),
Text(_error!, textAlign: TextAlign.center),
const SizedBox(height: 16),
FilledButton(onPressed: _load, child: const Text('Retry')),
],
),
)
: _pipelines.isEmpty
? Center(
child: Column(
mainAxisSize: MainAxisSize.min,
children: [
const Icon(Icons.api_outlined, size: 64, color: Colors.grey),
const SizedBox(height: 16),
const Text(
'No pipelines yet',
style: TextStyle(fontSize: 18, color: Colors.grey),
),
const SizedBox(height: 8),
FilledButton.icon(
onPressed: _createPipeline,
icon: const Icon(Icons.add),
label: const Text('Create Pipeline'),
),
],
),
)
: RefreshIndicator(
onRefresh: _load,
child: ListView.builder(
itemCount: _pipelines.length + 1,
padding: const EdgeInsets.symmetric(vertical: 8),
itemBuilder: (ctx, i) {
if (i == _pipelines.length) {
return Padding(
padding: const EdgeInsets.all(16),
child: SizedBox(
height: 48,
width: double.infinity,
child: FilledButton.icon(
onPressed: _createPipeline,
icon: const Icon(Icons.add),
label: const Text('Create Pipeline'),
),
),
);
}
final p = _pipelines[i];
return PipelineCard(
pipeline: p,
onTap: () => _navigateToDetail(p),
onDelete: () => _deletePipeline(p),
);
},
),
),
);
}
}