Skip to content

Commit

Permalink
auto save notes #29 (#44)
Browse files Browse the repository at this point in the history
* [Binni] Add. Auto Save Notes after X seconds.
  • Loading branch information
droidbg authored Oct 2, 2023
1 parent 634353f commit fc8e20f
Show file tree
Hide file tree
Showing 9 changed files with 229 additions and 33 deletions.
5 changes: 4 additions & 1 deletion lib/core/pages/settings_page.dart
Original file line number Diff line number Diff line change
Expand Up @@ -11,10 +11,11 @@ import 'package:dairy_app/features/auth/presentation/bloc/auth_session/auth_sess
import 'package:dairy_app/features/auth/presentation/widgets/security_settings.dart';
import 'package:dairy_app/features/auth/presentation/widgets/setup_account.dart';
import 'package:dairy_app/features/sync/presentation/widgets/sync_settings.dart';

import 'package:flutter/material.dart';
import 'package:flutter_bloc/flutter_bloc.dart';

import '../widgets/auto_save_enable.dart';

class SettingsPage extends StatefulWidget {
static String get route => '/settings';

Expand Down Expand Up @@ -124,6 +125,8 @@ class _SettingsPageState extends State<SettingsPage> {
const SizedBox(height: 25.0),
SecuritySettings(),
const SizedBox(height: 15),
const AutoSaveToggleButton(),
const SizedBox(height: 15),
const ThemeDropdown(),
const SizedBox(height: 15),
const SendFeedBack(),
Expand Down
53 changes: 53 additions & 0 deletions lib/core/widgets/auto_save_enable.dart
Original file line number Diff line number Diff line change
@@ -0,0 +1,53 @@
import 'package:dairy_app/app/themes/theme_extensions/note_create_page_theme_extensions.dart';
import 'package:flutter/material.dart';
import 'package:flutter_bloc/flutter_bloc.dart';

import '../../app/themes/theme_extensions/settings_page_theme_extensions.dart';
import '../../features/auth/core/constants.dart';
import '../../features/auth/presentation/bloc/user_config/user_config_cubit.dart';
import '../utils/utils.dart';

class AutoSaveToggleButton extends StatelessWidget {
const AutoSaveToggleButton({Key? key}) : super(key: key);

@override
Widget build(BuildContext context) {
final inactiveTrackColor = Theme.of(context)
.extension<SettingsPageThemeExtensions>()!
.inactiveTrackColor;

final activeColor =
Theme.of(context).extension<SettingsPageThemeExtensions>()!.activeColor;

final mainTextColor = Theme.of(context)
.extension<NoteCreatePageThemeExtensions>()!
.mainTextColor;
final userConfigCubit = BlocProvider.of<UserConfigCubit>(context);

return BlocBuilder<UserConfigCubit, UserConfigState>(
builder: (context, state) {
var isAutoSaveEnabled = state.userConfigModel!.isAutoSaveEnabled;
return SwitchListTile(
inactiveTrackColor: inactiveTrackColor,
activeColor: activeColor,
contentPadding: const EdgeInsets.all(0.0),
title: Text(
"Enable Auto Save",
style: TextStyle(color: mainTextColor),
),
value: isAutoSaveEnabled ?? false,
onChanged: (value) async {
try {
userConfigCubit.setUserConfig(
UserConfigConstants.isAutoSaveEnabled,
value,
);
} on Exception catch (e) {
showToast(e.toString().replaceAll("Exception: ", ""));
}
},
);
},
);
}
}
1 change: 1 addition & 0 deletions lib/features/auth/core/constants.dart
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,7 @@ class UserConfigConstants {
static String dropBoxUserInfo = "dropbox_user_info";
static String isAutoSyncEnabled = "is_auto_sync_enabled";
static String isFingerPrintLoginEnabled = "is_finger_print_log_enabled";
static String isAutoSaveEnabled = "is_auto_save_enabled";
}

class SyncConstants {
Expand Down
24 changes: 14 additions & 10 deletions lib/features/auth/data/models/user_config_model.dart
Original file line number Diff line number Diff line change
Expand Up @@ -12,17 +12,18 @@ class UserConfigModel extends Equatable {
final String? dropBoxUserInfo;
final bool? isAutoSyncEnabled;
final bool? isFingerPrintLoginEnabled;
final bool? isAutoSaveEnabled;

const UserConfigModel({
required this.userId,
this.preferredSyncOption,
this.lastGoogleDriveSync,
this.lastDropboxSync,
this.googleDriveUserInfo,
this.dropBoxUserInfo,
this.isAutoSyncEnabled,
this.isFingerPrintLoginEnabled,
});
const UserConfigModel(
{required this.userId,
this.preferredSyncOption,
this.lastGoogleDriveSync,
this.lastDropboxSync,
this.googleDriveUserInfo,
this.dropBoxUserInfo,
this.isAutoSyncEnabled,
this.isFingerPrintLoginEnabled,
this.isAutoSaveEnabled});

@override
List<Object?> get props => [
Expand All @@ -34,6 +35,7 @@ class UserConfigModel extends Equatable {
dropBoxUserInfo,
isAutoSyncEnabled,
isFingerPrintLoginEnabled,
isAutoSaveEnabled
];

factory UserConfigModel.fromJson(Map<String, dynamic> jsonMap) {
Expand All @@ -54,6 +56,7 @@ class UserConfigModel extends Equatable {
isAutoSyncEnabled: jsonMap[UserConfigConstants.isAutoSyncEnabled],
isFingerPrintLoginEnabled:
jsonMap[UserConfigConstants.isFingerPrintLoginEnabled],
isAutoSaveEnabled: jsonMap[UserConfigConstants.isAutoSaveEnabled],
); // default theme is coral bubbles
}

Expand All @@ -69,6 +72,7 @@ class UserConfigModel extends Equatable {
UserConfigConstants.dropBoxUserInfo: dropBoxUserInfo,
UserConfigConstants.isAutoSyncEnabled: isAutoSyncEnabled,
UserConfigConstants.isFingerPrintLoginEnabled: isFingerPrintLoginEnabled,
UserConfigConstants.isAutoSaveEnabled: isAutoSaveEnabled,
};
}
}
85 changes: 71 additions & 14 deletions lib/features/notes/presentation/bloc/notes/notes_bloc.dart
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,6 @@ import 'package:dairy_app/features/notes/data/models/notes_model.dart';
import 'package:dairy_app/features/notes/domain/repositories/notes_repository.dart';
import 'package:dartz/dartz.dart';
import 'package:equatable/equatable.dart';
import 'package:flutter/services.dart';
import 'package:flutter/widgets.dart';
import 'package:flutter_quill/flutter_quill.dart';
import 'package:uuid/uuid.dart';
Expand Down Expand Up @@ -68,20 +67,18 @@ class NotesBloc extends Bloc<NotesEvent, NotesState> {

on<UpdateNote>((event, emit) {
// we don't want to update when something is getting saved or deleted
// so we need to process the body afterwards to get current list of assets, and suitably delete removed ones

if (state is NoteInitialState || state is NoteUpdatedState) {
// so we need to process the body afterwards to get current list of assets, and suitably delete removed ones
emit(NoteUpdatedState(
newNote: state.newNote!,
id: state.id,
title: event.title ?? state.title!,
controller: state.controller!,
createdAt: event.createdAt ?? state.createdAt!,
allNoteAssets: event.noteAsset != null
? [...state.allNoteAssets!, event.noteAsset!]
: state.allNoteAssets!,
));
}
emit(NoteUpdatedState(
newNote: state.newNote!,
id: state.id,
title: event.title ?? state.title!,
controller: state.controller!,
createdAt: event.createdAt ?? state.createdAt!,
allNoteAssets: event.noteAsset != null
? [...state.allNoteAssets!, event.noteAsset!]
: state.allNoteAssets!,
));
});

on<SaveNote>((event, emit) async {
Expand Down Expand Up @@ -143,6 +140,62 @@ class NotesBloc extends Bloc<NotesEvent, NotesState> {
});
});

on<AutoSaveNote>((event, emit) async {
emit(NoteSaveLoading(
newNote: state.newNote!,
id: state.id,
title: state.title!,
controller: state.controller!,
createdAt: state.createdAt!,
noteAssets: state.allNoteAssets!,
));

var _body = jsonEncode(state.controller!.document.toDelta().toJson());

var _plainText = state.controller!.document.toPlainText();

var noteMap = {
"id": state.id,
"created_at": state.createdAt!.millisecondsSinceEpoch,
"title": state.title!,
"body": _body,
"last_modified": DateTime.now().millisecondsSinceEpoch,
"plain_text": _plainText,
"asset_dependencies": state.allNoteAssets,
"deleted": 0,
};

Either<NotesFailure, void> result;

// For smooth UX, it displays CIrcularProgressindicator till then
await Future.delayed(const Duration(seconds: 1));

if (state.newNote!) {
result = await notesRepository.saveNote(noteMap);
} else {
result = await notesRepository.updateNote(noteMap);
}
result.fold((error) {
emit(NotesAutoSavingFailed(
newNote: false,
id: state.id,
title: state.title!,
controller: state.controller!,
createdAt: state.createdAt!,
noteAssets: state.allNoteAssets!,
));
}, (_) {
emit(NoteAutoSavedSuccesfully(
newNote: false,
id: state.id,
title: state.title!,
controller: state.controller!,
createdAt: state.createdAt!,
noteAssets: state.allNoteAssets!,
));
});
});

on<DeleteNote>((event, emit) async {
emit(const NoteDeleteLoading(id: ""));
var result = await notesRepository.deleteNotes(event.noteList);
Expand All @@ -157,6 +210,10 @@ class NotesBloc extends Bloc<NotesEvent, NotesState> {
on<RefreshNote>((event, emit) {
emit(const NoteDummyState(id: ""));
});

on<FetchNote>((event, emit) {
emit(const FetchAfterAutoSave(id: ""));
});
}

// helper methods
Expand Down
4 changes: 4 additions & 0 deletions lib/features/notes/presentation/bloc/notes/notes_event.dart
Original file line number Diff line number Diff line change
Expand Up @@ -27,6 +27,8 @@ class UpdateNote extends NotesEvent {
/// if newNote is true, then create a new note, otherwise update the existing note
class SaveNote extends NotesEvent {}

class AutoSaveNote extends NotesEvent {}

class DeleteNote extends NotesEvent {
final List<String> noteList;

Expand All @@ -39,3 +41,5 @@ class DiscardNote extends NotesEvent {}

/// Removes all the info stored in state and starts with NoteDummyState again
class RefreshNote extends NotesEvent {}

class FetchNote extends NotesEvent {}
43 changes: 43 additions & 0 deletions lib/features/notes/presentation/bloc/notes/notes_state.dart
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,7 @@ abstract class NotesState extends Equatable {
final QuillController? controller;
final DateTime? createdAt;
final List<NoteAssetModel>? allNoteAssets;

// tells if it is safe to access the properties of this state
final bool safe;

Expand Down Expand Up @@ -185,6 +186,44 @@ class NotesSavingFailed extends NotesState {
);
}

class NoteAutoSavedSuccesfully extends NotesState {
const NoteAutoSavedSuccesfully(
{required bool newNote,
required QuillController controller,
required DateTime createdAt,
required String title,
required List<NoteAssetModel> noteAssets,
required String id})
: super(
newNote: newNote,
controller: controller,
id: id,
title: title,
createdAt: createdAt,
allNoteAssets: noteAssets,
safe: true,
);
}

class NotesAutoSavingFailed extends NotesState {
const NotesAutoSavingFailed(
{required bool newNote,
required QuillController controller,
required DateTime createdAt,
required String title,
required List<NoteAssetModel> noteAssets,
required String id})
: super(
newNote: newNote,
controller: controller,
id: id,
title: title,
createdAt: createdAt,
allNoteAssets: noteAssets,
safe: true,
);
}

class NoteDeleteLoading extends NotesState {
const NoteDeleteLoading({required String id}) : super(id: id, safe: false);
}
Expand All @@ -197,3 +236,7 @@ class NoteDeletionSuccesful extends NotesState {
class NoteDeletionFailed extends NotesState {
const NoteDeletionFailed({required String id}) : super(id: id, safe: false);
}

class FetchAfterAutoSave extends NotesState {
const FetchAfterAutoSave({required String id}) : super(id: id, safe: false);
}
Original file line number Diff line number Diff line change
Expand Up @@ -26,6 +26,9 @@ class NotesFetchCubit extends Cubit<NotesFetchState> {
if (state is NoteSavedSuccesfully) {
fetchNotes();
}
if (state is FetchAfterAutoSave) {
fetchNotes();
}
if (state is NoteDeletionSuccesful) {
fetchNotes();
}
Expand Down
Loading

0 comments on commit fc8e20f

Please sign in to comment.