From 8adff2343f62a72e1595d7ee91b26af6ae544371 Mon Sep 17 00:00:00 2001
From: Sanketh B K <51091231+SankethBK@users.noreply.github.com>
Date: Sun, 12 Jan 2025 20:32:24 +0530
Subject: [PATCH] Export Selected Notes (#278)
* Add export button
* Add export selected notes
---
assets/logo/file_export.svg | 1 +
lib/core/widgets/home_page_app_bar.dart | 176 ++++++++++++++++--
.../local data sources/local_data_source.dart | 24 ++-
.../local_data_source_template.dart | 3 +-
.../repositories/export_notes_repository.dart | 88 +++++----
.../data/repositories/notes_repository.dart | 6 +-
.../domain/repositories/notes_repository.dart | 3 +-
pubspec.lock | 2 +-
pubspec.yaml | 10 +-
9 files changed, 245 insertions(+), 68 deletions(-)
create mode 100644 assets/logo/file_export.svg
diff --git a/assets/logo/file_export.svg b/assets/logo/file_export.svg
new file mode 100644
index 00000000..9def0bcb
--- /dev/null
+++ b/assets/logo/file_export.svg
@@ -0,0 +1 @@
+
\ No newline at end of file
diff --git a/lib/core/widgets/home_page_app_bar.dart b/lib/core/widgets/home_page_app_bar.dart
index 4cd3ea29..a18dad6a 100644
--- a/lib/core/widgets/home_page_app_bar.dart
+++ b/lib/core/widgets/home_page_app_bar.dart
@@ -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({
@@ -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,
@@ -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) {
@@ -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(),
],
),
],
@@ -588,9 +599,144 @@ 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()!
+ .mainTextColor;
+
+ final selectableListCubit =
+ BlocProvider.of(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()
+ .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()
+ .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
@@ -598,7 +744,7 @@ class DeletionCount extends StatelessWidget {
return Padding(
padding: const EdgeInsets.only(right: 13.0),
child: Text(
- "$deletionCount",
+ "$selectionCount",
style: const TextStyle(fontSize: 18.0),
),
);
diff --git a/lib/features/notes/data/datasources/local data sources/local_data_source.dart b/lib/features/notes/data/datasources/local data sources/local_data_source.dart
index 01d8c785..828bab8d 100644
--- a/lib/features/notes/data/datasources/local data sources/local_data_source.dart
+++ b/lib/features/notes/data/datasources/local data sources/local_data_source.dart
@@ -103,14 +103,26 @@ class NotesLocalDataSource implements INotesLocalDataSource {
}
@override
- Future> fetchNotes(String authorId) async {
+ Future> fetchNotes(
+ {required String authorId, List? noteIds}) async {
List