notes cubit
This commit is contained in:
@@ -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<NotesEvent, NotesState> {
|
|
||||||
final NotesRepository _repository = GetIt.I.get<NotesRepository>();
|
|
||||||
final String _companyId = GetIt.I.get<SessionCubit>().state.company!.id!;
|
|
||||||
final String _currentStaffId = GetIt.I
|
|
||||||
.get<SessionCubit>()
|
|
||||||
.state
|
|
||||||
.currentStaffMember!
|
|
||||||
.id!;
|
|
||||||
|
|
||||||
NotesBloc() : super(const NotesState()) {
|
|
||||||
on<SubscribeToNotesRequested>(_onSubscribeToNotesRequested);
|
|
||||||
on<NoteSavedRequested>(_onNoteSavedRequested);
|
|
||||||
on<NoteDeletedRequested>(_onNoteDeletedRequested);
|
|
||||||
|
|
||||||
// Facciamo partire l'ascolto in tempo reale al boot del BLoC
|
|
||||||
add(SubscribeToNotesRequested());
|
|
||||||
}
|
|
||||||
|
|
||||||
Future<void> _onSubscribeToNotesRequested(
|
|
||||||
SubscribeToNotesRequested event,
|
|
||||||
Emitter<NotesState> emit,
|
|
||||||
) async {
|
|
||||||
emit(state.copyWith(status: NotesStatus.loading));
|
|
||||||
|
|
||||||
// Usiamo l'emit.forEach sullo stream pulito del repository
|
|
||||||
await emit.forEach<List<NoteModel>>(
|
|
||||||
_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<void> _onNoteSavedRequested(
|
|
||||||
NoteSavedRequested event,
|
|
||||||
Emitter<NotesState> 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<void> _onNoteDeletedRequested(
|
|
||||||
NoteDeletedRequested event,
|
|
||||||
Emitter<NotesState> 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()),
|
|
||||||
);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
94
lib/features/notes/blocs/notes_cubit.dart
Normal file
94
lib/features/notes/blocs/notes_cubit.dart
Normal file
@@ -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<NotesState> {
|
||||||
|
final NotesRepository _repository = GetIt.I.get<NotesRepository>();
|
||||||
|
String? get companyId => GetIt.I.get<SessionCubit>().state.company?.id;
|
||||||
|
String? get staffId =>
|
||||||
|
GetIt.I.get<SessionCubit>().state.currentStaffMember?.id;
|
||||||
|
|
||||||
|
StreamSubscription<void>? _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<void> _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<void> 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<void> 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<void> close() {
|
||||||
|
stopListening();
|
||||||
|
return super.close();
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -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);
|
|
||||||
}
|
|
||||||
@@ -1,8 +1,8 @@
|
|||||||
part of 'notes_bloc.dart';
|
part of 'notes_cubit.dart';
|
||||||
|
|
||||||
enum NotesStatus { initial, loading, success, failure }
|
enum NotesStatus { initial, loading, success, failure }
|
||||||
|
|
||||||
class NotesState {
|
class NotesState extends Equatable {
|
||||||
final NotesStatus status;
|
final NotesStatus status;
|
||||||
final List<NoteModel> notes;
|
final List<NoteModel> notes;
|
||||||
final String? errorMessage;
|
final String? errorMessage;
|
||||||
@@ -26,14 +26,5 @@ class NotesState {
|
|||||||
}
|
}
|
||||||
|
|
||||||
@override
|
@override
|
||||||
bool operator ==(Object other) {
|
List<Object?> get props => [status, notes, errorMessage];
|
||||||
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;
|
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -3,7 +3,7 @@ import 'package:flutter_bloc/flutter_bloc.dart';
|
|||||||
import 'package:flux/core/blocs/session/session_cubit.dart';
|
import 'package:flux/core/blocs/session/session_cubit.dart';
|
||||||
import 'package:flux/core/utils/debouncer.dart';
|
import 'package:flux/core/utils/debouncer.dart';
|
||||||
import 'package:flux/features/master_data/staff/blocs/staff_cubit.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';
|
import 'package:flux/features/notes/models/note_model.dart';
|
||||||
|
|
||||||
class NoteFormScreen extends StatefulWidget {
|
class NoteFormScreen extends StatefulWidget {
|
||||||
@@ -20,7 +20,7 @@ class _NoteFormScreenState extends State<NoteFormScreen> {
|
|||||||
NoteModel get _note => widget.note;
|
NoteModel get _note => widget.note;
|
||||||
late TextEditingController _titleController;
|
late TextEditingController _titleController;
|
||||||
late TextEditingController _contentController;
|
late TextEditingController _contentController;
|
||||||
late final NotesBloc _notesBloc;
|
late final NotesCubit _notesCubit;
|
||||||
late String _selectedColor;
|
late String _selectedColor;
|
||||||
late bool _isPinned;
|
late bool _isPinned;
|
||||||
late bool _isSharedAll;
|
late bool _isSharedAll;
|
||||||
@@ -43,7 +43,7 @@ class _NoteFormScreenState extends State<NoteFormScreen> {
|
|||||||
super.initState();
|
super.initState();
|
||||||
_titleController = TextEditingController(text: widget.note.title ?? '');
|
_titleController = TextEditingController(text: widget.note.title ?? '');
|
||||||
_contentController = TextEditingController(text: widget.note.content ?? '');
|
_contentController = TextEditingController(text: widget.note.content ?? '');
|
||||||
_notesBloc = context.read<NotesBloc>();
|
_notesCubit = context.read<NotesCubit>();
|
||||||
_selectedColor = widget.note.color;
|
_selectedColor = widget.note.color;
|
||||||
_isPinned = widget.note.isPinned;
|
_isPinned = widget.note.isPinned;
|
||||||
_isSharedAll = widget.note.isSharedAll;
|
_isSharedAll = widget.note.isSharedAll;
|
||||||
@@ -90,7 +90,7 @@ class _NoteFormScreenState extends State<NoteFormScreen> {
|
|||||||
);
|
);
|
||||||
|
|
||||||
// Spariamo l'evento al Bloc, che salverà silente sul DB tramite Repository
|
// 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"
|
/// Se l'utente esce e la nota è totalmente vuota, la eliminiamo dal DB "al secchio"
|
||||||
@@ -103,12 +103,12 @@ class _NoteFormScreenState extends State<NoteFormScreen> {
|
|||||||
// Assumiamo che se non ha scritto testo ed è appena stata creata, sia vuota.
|
// Assumiamo che se non ha scritto testo ed è appena stata creata, sia vuota.
|
||||||
if (titleEmpty && contentEmpty) {
|
if (titleEmpty && contentEmpty) {
|
||||||
// Notifichiamo anche il Bloc dell'avvenuta cancellazione così pulisce lo stato locale
|
// Notifichiamo anche il Bloc dell'avvenuta cancellazione così pulisce lo stato locale
|
||||||
_notesBloc.add(NoteDeletedRequested(noteId));
|
_notesCubit.deleteNote(noteId);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
void _deleteNote() {
|
void _deleteNote() {
|
||||||
_notesBloc.add(NoteDeletedRequested(widget.note.id!));
|
_notesCubit.deleteNote(widget.note.id!);
|
||||||
Navigator.pop(context);
|
Navigator.pop(context);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -2,16 +2,54 @@ import 'package:flutter/material.dart';
|
|||||||
import 'package:flutter_bloc/flutter_bloc.dart';
|
import 'package:flutter_bloc/flutter_bloc.dart';
|
||||||
import 'package:flutter_staggered_grid_view/flutter_staggered_grid_view.dart';
|
import 'package:flutter_staggered_grid_view/flutter_staggered_grid_view.dart';
|
||||||
import 'package:flux/core/routes/routes.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:flux/features/notes/data/notes_repository.dart';
|
||||||
import 'package:go_router/go_router.dart';
|
import 'package:go_router/go_router.dart';
|
||||||
import 'package:get_it/get_it.dart';
|
import 'package:get_it/get_it.dart';
|
||||||
import 'package:flux/core/blocs/session/session_cubit.dart';
|
import 'package:flux/core/blocs/session/session_cubit.dart';
|
||||||
import 'package:flux/features/notes/models/note_model.dart';
|
import 'package:flux/features/notes/models/note_model.dart';
|
||||||
|
|
||||||
class NotesListScreen extends StatelessWidget {
|
class NotesListScreen extends StatefulWidget {
|
||||||
const NotesListScreen({super.key});
|
const NotesListScreen({super.key});
|
||||||
|
|
||||||
|
@override
|
||||||
|
State<NotesListScreen> createState() => _NotesListScreenState();
|
||||||
|
}
|
||||||
|
|
||||||
|
class _NotesListScreenState extends State<NotesListScreen> {
|
||||||
|
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<NotesCubit>().stopListening();
|
||||||
|
debugPrint('App in background: Stream sospesi.');
|
||||||
|
},
|
||||||
|
onResume: () {
|
||||||
|
// L'utente è tornato sull'app!
|
||||||
|
// Riappriamo i rubinetti, Supabase ricreerà una connessione fresca
|
||||||
|
context.read<NotesCubit>().startListening();
|
||||||
|
debugPrint('App in foreground: Stream riattivati.');
|
||||||
|
},
|
||||||
|
);
|
||||||
|
|
||||||
|
// Facciamo partire gli stream la primissima volta che la schermata si carica
|
||||||
|
context.read<NotesCubit>().startListening();
|
||||||
|
}
|
||||||
|
|
||||||
|
@override
|
||||||
|
void dispose() {
|
||||||
|
// Pulizia fondamentale
|
||||||
|
_lifecycleListener.dispose();
|
||||||
|
super.dispose();
|
||||||
|
}
|
||||||
|
|
||||||
/// Logica Ninja: Crea la nota vuota, prende l'ID, e apre il form
|
/// Logica Ninja: Crea la nota vuota, prende l'ID, e apre il form
|
||||||
Future<void> _createNewNoteAndNavigate(BuildContext context) async {
|
Future<void> _createNewNoteAndNavigate(BuildContext context) async {
|
||||||
final sessionState = context.read<SessionCubit>().state;
|
final sessionState = context.read<SessionCubit>().state;
|
||||||
@@ -73,7 +111,7 @@ class NotesListScreen extends StatelessWidget {
|
|||||||
backgroundColor: Theme.of(context).colorScheme.primary,
|
backgroundColor: Theme.of(context).colorScheme.primary,
|
||||||
foregroundColor: Colors.white,
|
foregroundColor: Colors.white,
|
||||||
),
|
),
|
||||||
body: BlocBuilder<NotesBloc, NotesState>(
|
body: BlocBuilder<NotesCubit, NotesState>(
|
||||||
builder: (context, state) {
|
builder: (context, state) {
|
||||||
if (state.status == NotesStatus.loading && state.notes.isEmpty) {
|
if (state.status == NotesStatus.loading && state.notes.isEmpty) {
|
||||||
return const Center(child: CircularProgressIndicator());
|
return const Center(child: CircularProgressIndicator());
|
||||||
|
|||||||
@@ -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/attachments/data/attachments_repository.dart';
|
||||||
import 'package:flux/features/auth/bloc/auth_cubit.dart';
|
import 'package:flux/features/auth/bloc/auth_cubit.dart';
|
||||||
import 'package:flux/features/company/data/company_repository.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/notes/data/notes_repository.dart';
|
||||||
import 'package:flux/features/settings/data/settings_repository.dart';
|
import 'package:flux/features/settings/data/settings_repository.dart';
|
||||||
import 'package:flux/features/tasks/data/task_repository.dart';
|
import 'package:flux/features/tasks/data/task_repository.dart';
|
||||||
@@ -91,7 +91,7 @@ void main() async {
|
|||||||
BlocProvider<TicketListCubit>(create: (_) => TicketListCubit()),
|
BlocProvider<TicketListCubit>(create: (_) => TicketListCubit()),
|
||||||
BlocProvider<OperationListCubit>(create: (_) => OperationListCubit()),
|
BlocProvider<OperationListCubit>(create: (_) => OperationListCubit()),
|
||||||
BlocProvider<TrackingCubit>(create: (_) => TrackingCubit()),
|
BlocProvider<TrackingCubit>(create: (_) => TrackingCubit()),
|
||||||
BlocProvider<NotesBloc>(create: (_) => NotesBloc()),
|
BlocProvider<NotesCubit>(create: (_) => NotesCubit()),
|
||||||
],
|
],
|
||||||
child: const FluxApp(),
|
child: const FluxApp(),
|
||||||
),
|
),
|
||||||
|
|||||||
Reference in New Issue
Block a user