Initial commit

This commit is contained in:
2026-02-23 13:02:22 +03:00
commit 42d2900df9
108 changed files with 4491 additions and 0 deletions

View File

@@ -0,0 +1,202 @@
import 'package:flutter/material.dart';
import 'package:grpc/grpc.dart';
import '../gen/notes/v1/notes.pb.dart';
import '../services/grpc_client.dart';
class NotesScreen extends StatefulWidget {
const NotesScreen({super.key});
@override
State<NotesScreen> createState() => _NotesScreenState();
}
class _NotesScreenState extends State<NotesScreen> {
final _client = GrpcClient.instance.noteService;
List<Note> _notes = [];
bool _loading = true;
String? _error;
@override
void initState() {
super.initState();
_loadNotes();
}
Future<void> _loadNotes() async {
setState(() {
_loading = true;
_error = null;
});
try {
// Типизированный вызов — ListNotesRequest и ListNotesResponse
// сгенерированы из proto. Если на Go стороне изменится
// сигнатура, после перегенерации этот код не скомпилируется.
final response = await _client.listNotes(
ListNotesRequest(page: 1, pageSize: 50),
);
setState(() {
_notes = response.notes;
_loading = false;
});
} on GrpcError catch (e) {
setState(() {
_error = 'gRPC error: ${e.message}';
_loading = false;
});
} catch (e) {
setState(() {
_error = 'Connection error: $e';
_loading = false;
});
}
}
Future<void> _createNote() async {
final titleController = TextEditingController();
final contentController = TextEditingController();
final confirmed = await showDialog<bool>(
context: context,
builder: (ctx) => AlertDialog(
title: const Text('New Note'),
content: Column(
mainAxisSize: MainAxisSize.min,
children: [
TextField(
controller: titleController,
decoration: const InputDecoration(labelText: 'Title'),
autofocus: true,
),
const SizedBox(height: 8),
TextField(
controller: contentController,
decoration: const InputDecoration(labelText: 'Content'),
maxLines: 3,
),
],
),
actions: [
TextButton(
onPressed: () => Navigator.pop(ctx, false),
child: const Text('Cancel'),
),
FilledButton(
onPressed: () => Navigator.pop(ctx, true),
child: const Text('Create'),
),
],
),
);
if (confirmed != true) return;
try {
// Типизированный вызов CreateNote.
// CreateNoteRequest точно соответствует proto определению.
await _client.createNote(
CreateNoteRequest(
title: titleController.text,
content: contentController.text,
),
);
_loadNotes();
} on GrpcError catch (e) {
if (mounted) {
ScaffoldMessenger.of(context).showSnackBar(
SnackBar(content: Text('Error: ${e.message}')),
);
}
}
}
Future<void> _deleteNote(Note note) async {
try {
await _client.deleteNote(DeleteNoteRequest(id: note.id));
_loadNotes();
} on GrpcError catch (e) {
if (mounted) {
ScaffoldMessenger.of(context).showSnackBar(
SnackBar(content: Text('Error: ${e.message}')),
);
}
}
}
@override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(
title: const Text('Notes (Flutter + Go gRPC)'),
actions: [
IconButton(
icon: const Icon(Icons.refresh),
onPressed: _loadNotes,
),
],
),
floatingActionButton: FloatingActionButton(
onPressed: _createNote,
child: const Icon(Icons.add),
),
body: _buildBody(),
);
}
Widget _buildBody() {
if (_loading) {
return const Center(child: CircularProgressIndicator());
}
if (_error != null) {
return Center(
child: Column(
mainAxisSize: MainAxisSize.min,
children: [
Text(_error!, style: const TextStyle(color: Colors.red)),
const SizedBox(height: 16),
const Text(
'Make sure Go backend is running:\n'
'cd backend && go run main.go',
textAlign: TextAlign.center,
),
const SizedBox(height: 16),
FilledButton(
onPressed: _loadNotes,
child: const Text('Retry'),
),
],
),
);
}
if (_notes.isEmpty) {
return const Center(
child: Text('No notes yet. Tap + to create one.'),
);
}
return ListView.builder(
itemCount: _notes.length,
itemBuilder: (context, index) {
final note = _notes[index];
return Card(
margin: const EdgeInsets.symmetric(horizontal: 16, vertical: 4),
child: ListTile(
title: Text(note.title),
subtitle: Text(
note.content,
maxLines: 2,
overflow: TextOverflow.ellipsis,
),
trailing: IconButton(
icon: const Icon(Icons.delete_outline),
onPressed: () => _deleteNote(note),
),
),
);
},
);
}
}