diff --git a/android/app/src/main/AndroidManifest.xml b/android/app/src/main/AndroidManifest.xml
index eed3fcc..b2db80c 100644
--- a/android/app/src/main/AndroidManifest.xml
+++ b/android/app/src/main/AndroidManifest.xml
@@ -2,6 +2,7 @@
package="eu.heili.notes">
+
shortVibrate() async {
+ if (defaultTargetPlatform == TargetPlatform.android ||
+ defaultTargetPlatform == TargetPlatform.iOS) {
+ if (await Vibration.hasVibrator() ?? false) {
+ Vibration.vibrate(duration: 50);
+ }
+ }
+}
diff --git a/lib/pages/all_notes_page.dart b/lib/pages/all_notes_page.dart
index 079c0e7..b452fe4 100644
--- a/lib/pages/all_notes_page.dart
+++ b/lib/pages/all_notes_page.dart
@@ -1,9 +1,12 @@
+import 'package:fluentui_system_icons/fluentui_system_icons.dart';
import 'package:flutter/material.dart';
import 'package:fluttertoast/fluttertoast.dart';
import 'package:provider/provider.dart';
import '../context/file_change_notifier.dart';
+import '../savesystem/note_file.dart';
import '../widgets/icon_material_button.dart';
+import '../widgets/icon_text_button.dart';
import '../widgets/note_tile.dart';
import '../widgets/wip_toast.dart';
@@ -16,6 +19,8 @@ class AllNotesPage extends StatefulWidget {
class _AllNotesPageState extends State {
FToast fToast = FToast();
+ bool selectionMode = false;
+ List selectionIdx = [];
@override
void initState() {
@@ -23,6 +28,78 @@ class _AllNotesPageState extends State {
fToast.init(context);
}
+ Widget _buildTopBar() {
+ if (selectionMode) {
+ return Row(
+ children: [
+ const SizedBox(
+ width: 20,
+ height: 40,
+ ),
+ Text(
+ '${selectionIdx.length} selected',
+ style: const TextStyle(
+ color: Color.fromRGBO(255, 255, 255, .85), fontSize: 21),
+ )
+ ],
+ );
+ } else {
+ return Row(
+ children: [
+ const SizedBox(
+ width: 20,
+ ),
+ const Text(
+ 'All notes',
+ style: TextStyle(
+ color: Color.fromRGBO(255, 255, 255, .85), fontSize: 21),
+ ),
+ Expanded(child: Container()),
+ IconMaterialButton(
+ icon: const Icon(Icons.picture_as_pdf_outlined),
+ color: const Color.fromRGBO(255, 255, 255, .85),
+ onPressed: () async {
+ // todo implement pdf import
+ fToast.showToast(
+ child: const WIPToast(),
+ gravity: ToastGravity.BOTTOM,
+ toastDuration: const Duration(seconds: 2),
+ );
+ },
+ iconSize: 22,
+ ),
+ IconMaterialButton(
+ icon: const Icon(Icons.search),
+ color: const Color.fromRGBO(255, 255, 255, .85),
+ onPressed: () {
+ fToast.showToast(
+ child: const WIPToast(),
+ gravity: ToastGravity.BOTTOM,
+ toastDuration: const Duration(seconds: 2),
+ );
+ },
+ iconSize: 22,
+ ),
+ IconMaterialButton(
+ icon: const Icon(Icons.more_vert),
+ color: const Color.fromRGBO(255, 255, 255, .85),
+ onPressed: () {
+ fToast.showToast(
+ child: const WIPToast(),
+ gravity: ToastGravity.BOTTOM,
+ toastDuration: const Duration(seconds: 2),
+ );
+ },
+ iconSize: 22,
+ ),
+ const SizedBox(
+ width: 15,
+ )
+ ],
+ );
+ }
+ }
+
@override
Widget build(BuildContext context) {
return Column(
@@ -30,59 +107,7 @@ class _AllNotesPageState extends State {
SizedBox(
height: 25 + MediaQuery.of(context).viewPadding.top,
),
- Row(
- children: [
- const SizedBox(
- width: 20,
- ),
- const Text(
- 'All notes',
- style: TextStyle(
- color: Color.fromRGBO(255, 255, 255, .85), fontSize: 21),
- ),
- Expanded(child: Container()),
- IconMaterialButton(
- icon: const Icon(Icons.picture_as_pdf_outlined),
- color: const Color.fromRGBO(255, 255, 255, .85),
- onPressed: () async {
- // todo implement pdf import
- fToast.showToast(
- child: const WIPToast(),
- gravity: ToastGravity.BOTTOM,
- toastDuration: const Duration(seconds: 2),
- );
- },
- iconSize: 22,
- ),
- IconMaterialButton(
- icon: const Icon(Icons.search),
- color: const Color.fromRGBO(255, 255, 255, .85),
- onPressed: () {
- fToast.showToast(
- child: const WIPToast(),
- gravity: ToastGravity.BOTTOM,
- toastDuration: const Duration(seconds: 2),
- );
- },
- iconSize: 22,
- ),
- IconMaterialButton(
- icon: const Icon(Icons.more_vert),
- color: const Color.fromRGBO(255, 255, 255, .85),
- onPressed: () {
- fToast.showToast(
- child: const WIPToast(),
- gravity: ToastGravity.BOTTOM,
- toastDuration: const Duration(seconds: 2),
- );
- },
- iconSize: 22,
- ),
- const SizedBox(
- width: 15,
- )
- ],
- ),
+ _buildTopBar(),
Row(
children: const [
SizedBox(
@@ -90,26 +115,142 @@ class _AllNotesPageState extends State {
)
],
),
- _buildNoteTiles()
+ _buildNoteTiles(),
+ if (selectionMode)
+ SizedBox(
+ height: 70,
+ child: Row(
+ mainAxisAlignment: MainAxisAlignment.spaceEvenly,
+ mainAxisSize: MainAxisSize.max,
+ children: [
+ IconTextButton(
+ icon: const Icon(Icons.drive_file_move_outline),
+ color: const Color.fromRGBO(255, 255, 255, .85),
+ onPressed: () {
+ fToast.showToast(
+ child: const WIPToast(),
+ gravity: ToastGravity.BOTTOM,
+ toastDuration: const Duration(seconds: 2),
+ );
+ },
+ iconSize: 24,
+ text: 'Move',
+ ),
+ IconTextButton(
+ icon: const Icon(Icons.lock_outline),
+ color: const Color.fromRGBO(255, 255, 255, .85),
+ onPressed: () {
+ fToast.showToast(
+ child: const WIPToast(),
+ gravity: ToastGravity.BOTTOM,
+ toastDuration: const Duration(seconds: 2),
+ );
+ },
+ iconSize: 24,
+ text: 'Lock',
+ ),
+ IconTextButton(
+ icon: const Icon(Icons.share),
+ color: const Color.fromRGBO(255, 255, 255, .85),
+ onPressed: () {
+ fToast.showToast(
+ child: const WIPToast(),
+ gravity: ToastGravity.BOTTOM,
+ toastDuration: const Duration(seconds: 2),
+ );
+ },
+ iconSize: 24,
+ text: 'Share',
+ ),
+ IconTextButton(
+ icon: const Icon(FluentIcons.delete_20_filled),
+ color: const Color.fromRGBO(255, 255, 255, .85),
+ onPressed: () async {
+ // todo add popup to ask if really delete
+
+ final filechangenotfier =
+ Provider.of(context, listen: false);
+ for (final s in selectionIdx) {
+ final dta = filechangenotfier.tiledata[s];
+ // todo maybe optimize a bit and create not always new notefile instance
+ await NoteFile(dta.relativePath).delete();
+ }
+
+ await filechangenotfier.loadAllNotes();
+
+ setState(() {
+ selectionIdx = [];
+ selectionMode = false;
+ });
+ },
+ iconSize: 24,
+ text: 'Delete',
+ ),
+ IconTextButton(
+ icon: const Icon(Icons.more_vert),
+ color: const Color.fromRGBO(255, 255, 255, .85),
+ onPressed: () {
+ fToast.showToast(
+ child: const WIPToast(),
+ gravity: ToastGravity.BOTTOM,
+ toastDuration: const Duration(seconds: 2),
+ );
+ },
+ iconSize: 24,
+ text: 'More',
+ ),
+ ],
+ ),
+ )
],
);
}
Widget _buildNoteTiles() {
- return Expanded(
- child: Consumer(
- builder: (BuildContext context, value, Widget? child) {
- return GridView.builder(
+ return Consumer(
+ builder: (BuildContext context, value, Widget? child) {
+ return Expanded(
+ child: GridView.builder(
gridDelegate: const SliverGridDelegateWithFixedCrossAxisCount(
- crossAxisCount: 5,
- ),
+ crossAxisCount: 6, childAspectRatio: 0.7),
+ physics: const ScrollPhysics(),
padding: const EdgeInsets.all(2),
- itemBuilder: (BuildContext context, int idx) =>
- NoteTile(data: value.tiledata[idx]),
+ itemBuilder: (BuildContext context, int idx) => NoteTile(
+ data: value.tiledata[idx],
+ selectionMode: selectionMode,
+ selected: selectionIdx.contains(idx),
+ onSelectionChange: (selection) {
+ if (selection) {
+ if (!selectionMode) {
+ setState(() {
+ selectionMode = true;
+ });
+ }
+ if (!selectionIdx.contains(idx)) {
+ final sel = selectionIdx;
+ sel.add(idx);
+ setState(() {
+ selectionIdx = sel;
+ });
+ }
+ } else {
+ final sel = selectionIdx;
+ sel.remove(idx);
+ if (sel.isEmpty) {
+ setState(() {
+ selectionMode = false;
+ });
+ }
+ setState(() {
+ selectionIdx = sel;
+ });
+ }
+ },
+ ),
itemCount: value.tiledata.length,
- );
- },
- ),
+ ),
+ );
+ },
);
}
}
diff --git a/lib/savesystem/note_file.dart b/lib/savesystem/note_file.dart
index 0d193d0..5bad148 100644
--- a/lib/savesystem/note_file.dart
+++ b/lib/savesystem/note_file.dart
@@ -6,21 +6,25 @@ import 'package:sqflite/sqflite.dart';
import 'path.dart';
class NoteFile {
- late Database _db;
+ Database? _db;
String filename;
- late String _basePath;
+ String? _basePath;
String? _newFileName;
Database db() {
- return _db;
+ assert(_db != null);
+ return _db!;
}
NoteFile(this.filename);
Future init() async {
- _basePath = (await getSavePath()).path;
- final path = _basePath + Platform.pathSeparator + filename;
+ if (_basePath == null) {
+ await _initBasePath();
+ }
+
+ final path = _basePath! + Platform.pathSeparator + filename;
_db = await openDatabase(
path,
onCreate: (db, version) async {
@@ -44,7 +48,11 @@ class NoteFile {
Future delete() async {
await close();
- await File(_basePath + Platform.pathSeparator + filename).delete();
+
+ if (_basePath == null) {
+ await _initBasePath();
+ }
+ await File(_basePath! + Platform.pathSeparator + filename).delete();
}
void rename(String newname) {
@@ -53,19 +61,27 @@ class NoteFile {
Future close() async {
// shrink the db file size
- if (_db.isOpen) {
- await _db.execute('VACUUM');
- await _db.close();
+ if (_db != null && _db!.isOpen) {
+ await _db!.execute('VACUUM');
+ await _db!.close();
} else {
debugPrint('db file unexpectedly closed before shrinking');
}
+ if (_basePath == null) {
+ await _initBasePath();
+ }
+
// perform qued file renaming operations
if (_newFileName != null) {
- File(_basePath + Platform.pathSeparator + filename)
- .rename(_basePath + Platform.pathSeparator + _newFileName!);
+ File(_basePath! + Platform.pathSeparator + filename)
+ .rename(_basePath! + Platform.pathSeparator + _newFileName!);
filename = _newFileName!;
_newFileName = null;
}
}
+
+ Future _initBasePath() async {
+ _basePath = (await getSavePath()).path;
+ }
}
diff --git a/lib/widgets/icon_text_button.dart b/lib/widgets/icon_text_button.dart
new file mode 100644
index 0000000..321e491
--- /dev/null
+++ b/lib/widgets/icon_text_button.dart
@@ -0,0 +1,33 @@
+import 'package:flutter/material.dart';
+
+import 'icon_material_button.dart';
+
+class IconTextButton extends StatelessWidget {
+ const IconTextButton(
+ {Key? key,
+ required this.icon,
+ required this.color,
+ required this.onPressed,
+ required this.text,
+ this.iconSize})
+ : super(key: key);
+ final Widget icon;
+ final Color color;
+ final void Function() onPressed;
+ final String text;
+ final double? iconSize;
+
+ @override
+ Widget build(BuildContext context) {
+ return Column(
+ children: [
+ IconMaterialButton(
+ icon: icon, color: color, onPressed: onPressed, iconSize: iconSize),
+ Text(
+ text,
+ style: TextStyle(color: color),
+ )
+ ],
+ );
+ }
+}
diff --git a/lib/widgets/note_tile.dart b/lib/widgets/note_tile.dart
index f49b651..ac781c9 100644
--- a/lib/widgets/note_tile.dart
+++ b/lib/widgets/note_tile.dart
@@ -1,35 +1,74 @@
+import 'dart:math';
+
import 'package:flutter/material.dart';
import '../canvas/document_types.dart';
import '../canvas/drawing_page.dart';
+import '../helpers/vibrate.dart';
class NoteTile extends StatelessWidget {
- const NoteTile({Key? key, required this.data}) : super(key: key);
+ const NoteTile(
+ {Key? key,
+ required this.data,
+ required this.selectionMode,
+ required this.selected,
+ required this.onSelectionChange})
+ : super(key: key);
final NoteMetaData data;
+ final bool selectionMode;
+ final bool selected;
+ final void Function(bool) onSelectionChange;
@override
Widget build(BuildContext context) {
return GestureDetector(
onTap: () {
- Navigator.push(
- context,
- MaterialPageRoute(
- builder: (context) => DrawingPage(meta: data),
- ),
- );
+ if (selectionMode) {
+ onSelectionChange(!selected);
+ } else {
+ Navigator.push(
+ context,
+ MaterialPageRoute(
+ builder: (context) => DrawingPage(meta: data),
+ ),
+ );
+ }
},
- child: SizedBox(
- width: 100,
+ onLongPress: () async {
+ shortVibrate();
+ onSelectionChange(!selected);
+ },
+ child: Padding(
+ padding: const EdgeInsets.all(20),
child: Column(
children: [
- SizedBox(
- height: 150,
- width: 100,
- child: Container(
- color: Colors.white,
+ Expanded(
+ child: Stack(
+ children: [
+ ClipRRect(
+ borderRadius: BorderRadius.circular(10),
+ child: AspectRatio(
+ aspectRatio: 1 / sqrt2,
+ child: Container(
+ color: Colors.white,
+ ),
+ ),
+ ),
+ if (selectionMode)
+ Padding(
+ padding: const EdgeInsets.only(left: 10, top: 10),
+ child: CustomPaint(
+ size: const Size(25, 25),
+ painter: _CirclePainter(selected),
+ ),
+ )
+ ],
),
),
+ const SizedBox(
+ height: 5,
+ ),
Text(
data.name,
style: const TextStyle(color: Colors.white),
@@ -43,3 +82,31 @@ class NoteTile extends StatelessWidget {
);
}
}
+
+class _CirclePainter extends CustomPainter {
+ final bool selected;
+
+ final _paint = Paint()..strokeWidth = .7;
+
+ _CirclePainter(this.selected) {
+ if (selected) {
+ _paint.color = Colors.orange;
+ _paint.style = PaintingStyle.fill;
+ } else {
+ _paint.color = Colors.black;
+ _paint.style = PaintingStyle.stroke;
+ }
+ }
+
+ @override
+ void paint(Canvas canvas, Size size) {
+ canvas.drawOval(
+ Rect.fromLTWH(0, 0, size.width, size.height),
+ _paint,
+ );
+ }
+
+ @override
+ bool shouldRepaint(_CirclePainter oldDelegate) =>
+ oldDelegate.selected != selected;
+}
diff --git a/pubspec.lock b/pubspec.lock
index 55c51a3..173518d 100644
--- a/pubspec.lock
+++ b/pubspec.lock
@@ -441,6 +441,13 @@ packages:
url: "https://pub.dartlang.org"
source: hosted
version: "2.1.2"
+ vibration:
+ dependency: "direct main"
+ description:
+ name: vibration
+ url: "https://pub.dartlang.org"
+ source: hosted
+ version: "1.7.6"
win32:
dependency: transitive
description:
diff --git a/pubspec.yaml b/pubspec.yaml
index 14f04a3..65dc836 100644
--- a/pubspec.yaml
+++ b/pubspec.yaml
@@ -39,6 +39,7 @@ dependencies:
pdf: ^3.8.4
permission_handler: ^10.2.0
fluttertoast: ^8.1.1
+ vibration: ^1.7.6
dev_dependencies:
flutter_test: