From 44c85766fcf4def3b06531dbe54276efca55e8bd Mon Sep 17 00:00:00 2001 From: mark-cachy Date: Sat, 30 May 2026 18:06:43 +0200 Subject: [PATCH] notes cubit --- lib/features/notes/blocs/notes_bloc.dart | 80 ----------------- lib/features/notes/blocs/notes_cubit.dart | 94 ++++++++++++++++++++ lib/features/notes/blocs/notes_event.dart | 17 ---- lib/features/notes/blocs/notes_state.dart | 15 +--- lib/features/notes/ui/notes_form_screen.dart | 12 +-- lib/features/notes/ui/notes_list_screen.dart | 44 ++++++++- lib/main.dart | 4 +- 7 files changed, 146 insertions(+), 120 deletions(-) delete mode 100644 lib/features/notes/blocs/notes_bloc.dart create mode 100644 lib/features/notes/blocs/notes_cubit.dart delete mode 100644 lib/features/notes/blocs/notes_event.dart diff --git a/lib/features/notes/blocs/notes_bloc.dart b/lib/features/notes/blocs/notes_bloc.dart deleted file mode 100644 index c6112b7..0000000 --- a/lib/features/notes/blocs/notes_bloc.dart +++ /dev/null @@ -1,80 +0,0 @@ -import 'dart:async'; -import 'package:flutter/foundation.dart'; -import 'package:flutter_bloc/flutter_bloc.dart'; -import 'package:flux/core/blocs/session/session_cubit.dart'; -import 'package:flux/features/notes/data/notes_repository.dart'; -import 'package:flux/features/notes/models/note_model.dart'; -import 'package:get_it/get_it.dart'; -part 'notes_event.dart'; -part 'notes_state.dart'; - -class NotesBloc extends Bloc { - final NotesRepository _repository = GetIt.I.get(); - final String _companyId = GetIt.I.get().state.company!.id!; - final String _currentStaffId = GetIt.I - .get() - .state - .currentStaffMember! - .id!; - - NotesBloc() : super(const NotesState()) { - on(_onSubscribeToNotesRequested); - on(_onNoteSavedRequested); - on(_onNoteDeletedRequested); - - // Facciamo partire l'ascolto in tempo reale al boot del BLoC - add(SubscribeToNotesRequested()); - } - - Future _onSubscribeToNotesRequested( - SubscribeToNotesRequested event, - Emitter emit, - ) async { - emit(state.copyWith(status: NotesStatus.loading)); - - // Usiamo l'emit.forEach sullo stream pulito del repository - await emit.forEach>( - _repository.notesStream( - companyId: _companyId, - currentStaffId: _currentStaffId, - ), - onData: (notesList) { - return state.copyWith(status: NotesStatus.success, notes: notesList); - }, - onError: (error, stackTrace) { - return state.copyWith( - status: NotesStatus.failure, - errorMessage: 'Errore nello stream realtime: $error', - ); - }, - ); - } - - Future _onNoteSavedRequested( - NoteSavedRequested event, - Emitter emit, - ) async { - try { - await _repository.saveNote(event.note); - // Non serve fare l'emit! Ci pensa lo stream a far rimbalzare i dati aggiornati - } catch (e) { - emit( - state.copyWith(status: NotesStatus.failure, errorMessage: e.toString()), - ); - } - } - - Future _onNoteDeletedRequested( - NoteDeletedRequested event, - Emitter emit, - ) async { - try { - await _repository.deleteNote(event.noteId); - // Anche qui, lo stream rileva la cancellazione in automatico - } catch (e) { - emit( - state.copyWith(status: NotesStatus.failure, errorMessage: e.toString()), - ); - } - } -} diff --git a/lib/features/notes/blocs/notes_cubit.dart b/lib/features/notes/blocs/notes_cubit.dart new file mode 100644 index 0000000..7d085c1 --- /dev/null +++ b/lib/features/notes/blocs/notes_cubit.dart @@ -0,0 +1,94 @@ +import 'dart:async'; + +import 'package:equatable/equatable.dart'; +import 'package:flutter/widgets.dart'; +import 'package:flutter_bloc/flutter_bloc.dart'; +import 'package:flux/core/blocs/session/session_cubit.dart'; +import 'package:flux/features/notes/data/notes_repository.dart'; +import 'package:flux/features/notes/models/note_model.dart'; +import 'package:get_it/get_it.dart'; + +part 'notes_state.dart'; + +class NotesCubit extends Cubit { + final NotesRepository _repository = GetIt.I.get(); + String? get companyId => GetIt.I.get().state.company?.id; + String? get staffId => + GetIt.I.get().state.currentStaffMember?.id; + + StreamSubscription? _subscription; + + NotesCubit() : super(NotesState(status: NotesStatus.initial)); + + void stopListening() { + _subscription?.cancel(); + _subscription = null; + } + + void startListening() { + stopListening(); + + emit(state.copyWith(status: NotesStatus.loading)); + + // Primo caricamento + _loadNotesSilently(); + + // Inizio ascolto campanello + try { + _subscription = _repository + .notesStream(companyId: companyId!, currentStaffId: staffId!) + .listen((_) { + // Quando il campanello suona (qualcosa è cambiato a DB), ricarichiamo! + _loadNotesSilently(); + }); + } on Exception catch (e) { + debugPrint(e.toString()); + } + } + + Future _loadNotesSilently() async { + try { + final notes = await _repository.getNotes(); + + emit( + state.copyWith( + status: NotesStatus.success, + notes: notes, + errorMessage: null, + ), + ); + } catch (e) { + emit( + state.copyWith(status: NotesStatus.failure, errorMessage: e.toString()), + ); + } + } + + Future saveNote(NoteModel note) async { + try { + await _repository.saveNote(note); + // Non serve fare l'emit! Ci pensa lo stream a far rimbalzare i dati aggiornati + } catch (e) { + emit( + state.copyWith(status: NotesStatus.failure, errorMessage: e.toString()), + ); + } + } + + Future deleteNote(String noteId) async { + try { + await _repository.deleteNote(noteId); + // Non serve fare l'emit + } catch (e) { + emit( + state.copyWith(status: NotesStatus.failure, errorMessage: e.toString()), + ); + } + } + + @override + Future close() { + stopListening(); + return super.close(); + } +} diff --git a/lib/features/notes/blocs/notes_event.dart b/lib/features/notes/blocs/notes_event.dart deleted file mode 100644 index e4cd733..0000000 --- a/lib/features/notes/blocs/notes_event.dart +++ /dev/null @@ -1,17 +0,0 @@ -part of 'notes_bloc.dart'; - -sealed class NotesEvent {} - -/// Fa partire lo stream e gestisce sia il caricamento iniziale che il realtime -class SubscribeToNotesRequested extends NotesEvent {} - -class NoteDeletedRequested extends NotesEvent { - final String noteId; - NoteDeletedRequested(this.noteId); -} - -/// Salva o aggiorna una nota -class NoteSavedRequested extends NotesEvent { - final NoteModel note; - NoteSavedRequested(this.note); -} diff --git a/lib/features/notes/blocs/notes_state.dart b/lib/features/notes/blocs/notes_state.dart index 2ab983f..7dab428 100644 --- a/lib/features/notes/blocs/notes_state.dart +++ b/lib/features/notes/blocs/notes_state.dart @@ -1,8 +1,8 @@ -part of 'notes_bloc.dart'; +part of 'notes_cubit.dart'; enum NotesStatus { initial, loading, success, failure } -class NotesState { +class NotesState extends Equatable { final NotesStatus status; final List notes; final String? errorMessage; @@ -26,14 +26,5 @@ class NotesState { } @override - bool operator ==(Object other) { - if (identical(this, other)) return true; - return other is NotesState && - other.status == status && - listEquals(other.notes, notes) && - other.errorMessage == errorMessage; - } - - @override - int get hashCode => status.hashCode ^ notes.hashCode ^ errorMessage.hashCode; + List get props => [status, notes, errorMessage]; } diff --git a/lib/features/notes/ui/notes_form_screen.dart b/lib/features/notes/ui/notes_form_screen.dart index 9f86073..0d5afd4 100644 --- a/lib/features/notes/ui/notes_form_screen.dart +++ b/lib/features/notes/ui/notes_form_screen.dart @@ -3,7 +3,7 @@ import 'package:flutter_bloc/flutter_bloc.dart'; import 'package:flux/core/blocs/session/session_cubit.dart'; import 'package:flux/core/utils/debouncer.dart'; import 'package:flux/features/master_data/staff/blocs/staff_cubit.dart'; -import 'package:flux/features/notes/blocs/notes_bloc.dart'; +import 'package:flux/features/notes/blocs/notes_cubit.dart'; import 'package:flux/features/notes/models/note_model.dart'; class NoteFormScreen extends StatefulWidget { @@ -20,7 +20,7 @@ class _NoteFormScreenState extends State { NoteModel get _note => widget.note; late TextEditingController _titleController; late TextEditingController _contentController; - late final NotesBloc _notesBloc; + late final NotesCubit _notesCubit; late String _selectedColor; late bool _isPinned; late bool _isSharedAll; @@ -43,7 +43,7 @@ class _NoteFormScreenState extends State { super.initState(); _titleController = TextEditingController(text: widget.note.title ?? ''); _contentController = TextEditingController(text: widget.note.content ?? ''); - _notesBloc = context.read(); + _notesCubit = context.read(); _selectedColor = widget.note.color; _isPinned = widget.note.isPinned; _isSharedAll = widget.note.isSharedAll; @@ -90,7 +90,7 @@ class _NoteFormScreenState extends State { ); // Spariamo l'evento al Bloc, che salverà silente sul DB tramite Repository - _notesBloc.add(NoteSavedRequested(updatedNote)); + _notesCubit.saveNote(updatedNote); } /// Se l'utente esce e la nota è totalmente vuota, la eliminiamo dal DB "al secchio" @@ -103,12 +103,12 @@ class _NoteFormScreenState extends State { // Assumiamo che se non ha scritto testo ed è appena stata creata, sia vuota. if (titleEmpty && contentEmpty) { // Notifichiamo anche il Bloc dell'avvenuta cancellazione così pulisce lo stato locale - _notesBloc.add(NoteDeletedRequested(noteId)); + _notesCubit.deleteNote(noteId); } } void _deleteNote() { - _notesBloc.add(NoteDeletedRequested(widget.note.id!)); + _notesCubit.deleteNote(widget.note.id!); Navigator.pop(context); } diff --git a/lib/features/notes/ui/notes_list_screen.dart b/lib/features/notes/ui/notes_list_screen.dart index 06612ef..0f5a49b 100644 --- a/lib/features/notes/ui/notes_list_screen.dart +++ b/lib/features/notes/ui/notes_list_screen.dart @@ -2,16 +2,54 @@ import 'package:flutter/material.dart'; import 'package:flutter_bloc/flutter_bloc.dart'; import 'package:flutter_staggered_grid_view/flutter_staggered_grid_view.dart'; import 'package:flux/core/routes/routes.dart'; -import 'package:flux/features/notes/blocs/notes_bloc.dart'; +import 'package:flux/features/notes/blocs/notes_cubit.dart'; import 'package:flux/features/notes/data/notes_repository.dart'; import 'package:go_router/go_router.dart'; import 'package:get_it/get_it.dart'; import 'package:flux/core/blocs/session/session_cubit.dart'; import 'package:flux/features/notes/models/note_model.dart'; -class NotesListScreen extends StatelessWidget { +class NotesListScreen extends StatefulWidget { const NotesListScreen({super.key}); + @override + State createState() => _NotesListScreenState(); +} + +class _NotesListScreenState extends State { + late final AppLifecycleListener _lifecycleListener; + + @override + void initState() { + super.initState(); + + // Inizializziamo il sensore del ciclo di vita + _lifecycleListener = AppLifecycleListener( + onPause: () { + // L'utente ha messo l'app in background (es. per rispondere a un messaggio su WhatsApp) + // Chiudiamo i rubinetti per non sprecare risorse e prevenire crash + context.read().stopListening(); + debugPrint('App in background: Stream sospesi.'); + }, + onResume: () { + // L'utente è tornato sull'app! + // Riappriamo i rubinetti, Supabase ricreerà una connessione fresca + context.read().startListening(); + debugPrint('App in foreground: Stream riattivati.'); + }, + ); + + // Facciamo partire gli stream la primissima volta che la schermata si carica + context.read().startListening(); + } + + @override + void dispose() { + // Pulizia fondamentale + _lifecycleListener.dispose(); + super.dispose(); + } + /// Logica Ninja: Crea la nota vuota, prende l'ID, e apre il form Future _createNewNoteAndNavigate(BuildContext context) async { final sessionState = context.read().state; @@ -73,7 +111,7 @@ class NotesListScreen extends StatelessWidget { backgroundColor: Theme.of(context).colorScheme.primary, foregroundColor: Colors.white, ), - body: BlocBuilder( + body: BlocBuilder( builder: (context, state) { if (state.status == NotesStatus.loading && state.notes.isEmpty) { return const Center(child: CircularProgressIndicator()); diff --git a/lib/main.dart b/lib/main.dart index 07cb950..ad766d3 100644 --- a/lib/main.dart +++ b/lib/main.dart @@ -9,7 +9,7 @@ import 'package:flux/core/utils/version_check_service.dart'; import 'package:flux/features/attachments/data/attachments_repository.dart'; import 'package:flux/features/auth/bloc/auth_cubit.dart'; import 'package:flux/features/company/data/company_repository.dart'; -import 'package:flux/features/notes/blocs/notes_bloc.dart'; +import 'package:flux/features/notes/blocs/notes_cubit.dart'; import 'package:flux/features/notes/data/notes_repository.dart'; import 'package:flux/features/settings/data/settings_repository.dart'; import 'package:flux/features/tasks/data/task_repository.dart'; @@ -91,7 +91,7 @@ void main() async { BlocProvider(create: (_) => TicketListCubit()), BlocProvider(create: (_) => OperationListCubit()), BlocProvider(create: (_) => TrackingCubit()), - BlocProvider(create: (_) => NotesBloc()), + BlocProvider(create: (_) => NotesCubit()), ], child: const FluxApp(), ),