Skip to content

Commit

Permalink
Export Selected Notes (#278)
Browse files Browse the repository at this point in the history
* Add export button

* Add export selected notes
  • Loading branch information
SankethBK authored Jan 12, 2025
1 parent 10a3f3c commit 8adff23
Show file tree
Hide file tree
Showing 9 changed files with 245 additions and 68 deletions.
1 change: 1 addition & 0 deletions assets/logo/file_export.svg
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
176 changes: 161 additions & 15 deletions lib/core/widgets/home_page_app_bar.dart
Original file line number Diff line number Diff line change
@@ -1,19 +1,28 @@
import 'dart:io';

import 'package:dairy_app/app/themes/theme_extensions/appbar_theme_extensions.dart';
import 'package:dairy_app/app/themes/theme_extensions/popup_theme_extensions.dart';
import 'package:dairy_app/core/dependency_injection/injection_container.dart';
import 'package:dairy_app/core/pages/settings_page.dart';
import 'package:dairy_app/core/utils/utils.dart';
import 'package:dairy_app/core/widgets/cancel_button.dart';
import 'package:dairy_app/core/widgets/date_input_field.dart';
import 'package:dairy_app/core/widgets/glass_dialog.dart';
import 'package:dairy_app/core/widgets/glassmorphism_cover.dart';
import 'package:dairy_app/core/widgets/settings_tile.dart';
import 'package:dairy_app/core/widgets/submit_button.dart';
import 'package:dairy_app/features/auth/data/models/user_config_model.dart';
import 'package:dairy_app/features/notes/domain/repositories/export_notes_repository.dart';
import 'package:dairy_app/features/notes/presentation/bloc/notes/notes_bloc.dart';
import 'package:dairy_app/features/notes/presentation/bloc/notes_fetch/notes_fetch_cubit.dart';
import 'package:dairy_app/features/notes/presentation/bloc/selectable_list/selectable_list_cubit.dart';
import 'package:dairy_app/generated/l10n.dart';
import 'package:flutter/material.dart';
import 'package:flutter_bloc/flutter_bloc.dart';
import 'package:flutter_svg/flutter_svg.dart';
import 'package:intl/intl.dart';
import 'package:path_provider/path_provider.dart';
import 'package:share_plus/share_plus.dart';

class HomePageAppBar extends StatefulWidget implements PreferredSizeWidget {
const HomePageAppBar({
Expand Down Expand Up @@ -195,8 +204,12 @@ class Action extends StatelessWidget {
} else if (selectableListState is SelectableListEnabled) {
return Row(
children: [
DeletionCount(
deletionCount: selectableListCubit.state.selectedItems.length,
SelectionCount(
selectionCount: selectableListCubit.state.selectedItems.length,
),
ExportIcon(
exportCount: selectableListCubit.state.selectedItems.length,
disableSelectedList: selectableListCubit.disableSelectableList,
),
DeleteIcon(
deletionCount: selectableListCubit.state.selectedItems.length,
Expand Down Expand Up @@ -446,9 +459,9 @@ class Title extends StatelessWidget {
}

class _DeleteButton extends StatelessWidget {
final int deleteCount;

const _DeleteButton({Key? key, required this.deleteCount}) : super(key: key);
const _DeleteButton({
Key? key,
}) : super(key: key);

@override
Widget build(BuildContext context) {
Expand Down Expand Up @@ -554,15 +567,13 @@ class DeleteIcon extends StatelessWidget {
),
),
const SizedBox(height: 15),
Row(
const Row(
mainAxisAlignment: MainAxisAlignment.end,
mainAxisSize: MainAxisSize.min,
children: [
const _CancelButton(),
const SizedBox(width: 10),
_DeleteButton(
deleteCount: deletionCount,
),
_CancelButton(),
SizedBox(width: 10),
_DeleteButton(),
],
),
],
Expand All @@ -588,17 +599,152 @@ class DeleteIcon extends StatelessWidget {
}
}

class DeletionCount extends StatelessWidget {
final int deletionCount;
const DeletionCount({Key? key, required this.deletionCount})
class ExportIcon extends StatelessWidget {
final int exportCount;
final Function() disableSelectedList;

const ExportIcon(
{Key? key, required this.exportCount, required this.disableSelectedList})
: super(key: key);

@override
Widget build(BuildContext context) {
return Padding(
padding: const EdgeInsets.only(right: 0.0),
child: IconButton(
icon: SvgPicture.asset(
'assets/logo/file_export.svg',
height: 25.0,
width: 25.0,
colorFilter: const ColorFilter.mode(
Colors.white,
BlendMode.srcIn,
),
),
onPressed: () async {
if (exportCount == 0) {
disableSelectedList();
return;
}

final mainTextColor = Theme.of(context)
.extension<PopupThemeExtensions>()!
.mainTextColor;

final selectableListCubit =
BlocProvider.of<SelectableListCubit>(context);

bool? result = await showCustomDialog(
context: context,
child: LayoutBuilder(
builder: (context, constraints) {
return Container(
color: Colors.transparent,
width: MediaQuery.of(context).size.width * 0.6,
padding:
const EdgeInsets.symmetric(horizontal: 35, vertical: 15),
child: Column(
crossAxisAlignment: CrossAxisAlignment.center,
mainAxisSize: MainAxisSize.min,
children: [
SettingsTile(
onTap: () async {
// create a text file from the notes
final directory =
await getApplicationDocumentsDirectory();

final now = DateTime.now();
final formattedTimestamp =
DateFormat('yyyyMMdd_HHmmss').format(now);

final file = File(
'${directory.path}/diaryvault_notes_export_$formattedTimestamp.txt',
);

try {
String filePath = await sl<IExportNotesRepository>()
.exportNotesToTextFile(
file: file,
noteList: selectableListCubit
.state.selectedItems);

// Share the file and await its completion
await Share.shareXFiles([XFile(filePath)],
text: 'diaryvault_notes_export');

await file.delete();
} on Exception catch (e) {
showToast(
e.toString().replaceAll("Exception: ", ""));
}
},
child: Text(
S.current.exportToPlainText,
style: TextStyle(
fontSize: 16.0,
color: mainTextColor,
),
),
),
const SizedBox(height: 15),
SettingsTile(
onTap: () async {
try {
String filePath = await sl<IExportNotesRepository>()
.exportNotesToPDF(
noteList: selectableListCubit
.state.selectedItems);

// Share the file and await its completion
await Share.shareXFiles([XFile(filePath)],
text: 'diaryvault_notes_export');
} on Exception catch (e) {
showToast(
e.toString().replaceAll("Exception: ", ""));
}
},
child: Text(
S.current.exportToPDF,
style: TextStyle(
fontSize: 16.0,
color: mainTextColor,
),
),
),
],
),
);
},
),
);

disableSelectedList();

if (result != null) {
if (result == true) {
showToast(
"$exportCount item${exportCount > 1 ? "s" : ""} deleted");
} else {
showToast(S.current.deletionFailed);
}
}
},
),
);
}
}

class SelectionCount extends StatelessWidget {
final int selectionCount;
const SelectionCount({Key? key, required this.selectionCount})
: super(key: key);

@override
Widget build(BuildContext context) {
return Padding(
padding: const EdgeInsets.only(right: 13.0),
child: Text(
"$deletionCount",
"$selectionCount",
style: const TextStyle(fontSize: 18.0),
),
);
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -103,14 +103,26 @@ class NotesLocalDataSource implements INotesLocalDataSource {
}

@override
Future<List<NoteModel>> fetchNotes(String authorId) async {
Future<List<NoteModel>> fetchNotes(
{required String authorId, List<String>? noteIds}) async {
List<Map<String, dynamic>> result;
try {
result = await database.query(
Notes.TABLE_NAME,
where:
"${Notes.DELETED} != 1 and ( ${Notes.AUTHOR_ID} = '$authorId' or ${Notes.AUTHOR_ID} = '${GuestUserDetails.guestUserId}' )",
);
if (noteIds != null && noteIds.isNotEmpty) {
// Format the noteIds list for the SQL query
String noteIdsString = noteIds.map((id) => "'$id'").join(', ');

result = await database.query(
Notes.TABLE_NAME,
where:
"${Notes.DELETED} != 1 and ( ${Notes.AUTHOR_ID} = '$authorId' or ${Notes.AUTHOR_ID} = '${GuestUserDetails.guestUserId}' ) AND ${Notes.ID} IN ($noteIdsString)",
);
} else {
result = await database.query(
Notes.TABLE_NAME,
where:
"${Notes.DELETED} != 1 and ( ${Notes.AUTHOR_ID} = '$authorId' or ${Notes.AUTHOR_ID} = '${GuestUserDetails.guestUserId}' )",
);
}
} catch (e) {
log.e("Local database query for fetching notes failed $e");
throw const DatabaseQueryException();
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -9,7 +9,8 @@ abstract class INotesLocalDataSource {
/// Fetches all notes
///
/// Throws [DatabaseQueryException] if something goes wrong
Future<List<NoteModel>> fetchNotes(String authorId);
Future<List<NoteModel>> fetchNotes(
{required String authorId, List<String>? noteIds});

// Fetch all notes with only columns required to diplay the preview
Future<List<NotePreviewModel>> fetchNotesPreview(String authorId);
Expand Down
Loading

0 comments on commit 8adff23

Please sign in to comment.