Merge branch 'selection_mode' into 'master'
Selection mode See merge request lukas/notes!3
This commit is contained in:
		| @@ -2,6 +2,7 @@ | |||||||
|     package="eu.heili.notes"> |     package="eu.heili.notes"> | ||||||
|     <uses-permission android:name="android.permission.WRITE_EXTERNAL_STORAGE"/> |     <uses-permission android:name="android.permission.WRITE_EXTERNAL_STORAGE"/> | ||||||
|     <uses-permission android:name="android.permission.READ_EXTERNAL_STORAGE"/> |     <uses-permission android:name="android.permission.READ_EXTERNAL_STORAGE"/> | ||||||
|  |     <uses-permission android:name="android.permission.VIBRATE"/> | ||||||
|    <application |    <application | ||||||
|            android:requestLegacyExternalStorage="true" |            android:requestLegacyExternalStorage="true" | ||||||
|         android:label="notes" |         android:label="notes" | ||||||
|   | |||||||
							
								
								
									
										11
									
								
								lib/helpers/vibrate.dart
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										11
									
								
								lib/helpers/vibrate.dart
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,11 @@ | |||||||
|  | import 'package:flutter/foundation.dart'; | ||||||
|  | import 'package:vibration/vibration.dart'; | ||||||
|  |  | ||||||
|  | Future<void> shortVibrate() async { | ||||||
|  |   if (defaultTargetPlatform == TargetPlatform.android || | ||||||
|  |       defaultTargetPlatform == TargetPlatform.iOS) { | ||||||
|  |     if (await Vibration.hasVibrator() ?? false) { | ||||||
|  |       Vibration.vibrate(duration: 50); | ||||||
|  |     } | ||||||
|  |   } | ||||||
|  | } | ||||||
| @@ -1,9 +1,12 @@ | |||||||
|  | import 'package:fluentui_system_icons/fluentui_system_icons.dart'; | ||||||
| import 'package:flutter/material.dart'; | import 'package:flutter/material.dart'; | ||||||
| import 'package:fluttertoast/fluttertoast.dart'; | import 'package:fluttertoast/fluttertoast.dart'; | ||||||
| import 'package:provider/provider.dart'; | import 'package:provider/provider.dart'; | ||||||
|  |  | ||||||
| import '../context/file_change_notifier.dart'; | import '../context/file_change_notifier.dart'; | ||||||
|  | import '../savesystem/note_file.dart'; | ||||||
| import '../widgets/icon_material_button.dart'; | import '../widgets/icon_material_button.dart'; | ||||||
|  | import '../widgets/icon_text_button.dart'; | ||||||
| import '../widgets/note_tile.dart'; | import '../widgets/note_tile.dart'; | ||||||
| import '../widgets/wip_toast.dart'; | import '../widgets/wip_toast.dart'; | ||||||
|  |  | ||||||
| @@ -16,6 +19,8 @@ class AllNotesPage extends StatefulWidget { | |||||||
|  |  | ||||||
| class _AllNotesPageState extends State<AllNotesPage> { | class _AllNotesPageState extends State<AllNotesPage> { | ||||||
|   FToast fToast = FToast(); |   FToast fToast = FToast(); | ||||||
|  |   bool selectionMode = false; | ||||||
|  |   List<int> selectionIdx = []; | ||||||
|  |  | ||||||
|   @override |   @override | ||||||
|   void initState() { |   void initState() { | ||||||
| @@ -23,6 +28,78 @@ class _AllNotesPageState extends State<AllNotesPage> { | |||||||
|     fToast.init(context); |     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 |   @override | ||||||
|   Widget build(BuildContext context) { |   Widget build(BuildContext context) { | ||||||
|     return Column( |     return Column( | ||||||
| @@ -30,59 +107,7 @@ class _AllNotesPageState extends State<AllNotesPage> { | |||||||
|         SizedBox( |         SizedBox( | ||||||
|           height: 25 + MediaQuery.of(context).viewPadding.top, |           height: 25 + MediaQuery.of(context).viewPadding.top, | ||||||
|         ), |         ), | ||||||
|         Row( |         _buildTopBar(), | ||||||
|           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, |  | ||||||
|             ) |  | ||||||
|           ], |  | ||||||
|         ), |  | ||||||
|         Row( |         Row( | ||||||
|           children: const [ |           children: const [ | ||||||
|             SizedBox( |             SizedBox( | ||||||
| @@ -90,26 +115,142 @@ class _AllNotesPageState extends State<AllNotesPage> { | |||||||
|             ) |             ) | ||||||
|           ], |           ], | ||||||
|         ), |         ), | ||||||
|         _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<FileChangeNotifier>(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() { |   Widget _buildNoteTiles() { | ||||||
|     return Expanded( |     return Consumer<FileChangeNotifier>( | ||||||
|       child: Consumer<FileChangeNotifier>( |       builder: (BuildContext context, value, Widget? child) { | ||||||
|         builder: (BuildContext context, value, Widget? child) { |         return Expanded( | ||||||
|           return GridView.builder( |           child: GridView.builder( | ||||||
|             gridDelegate: const SliverGridDelegateWithFixedCrossAxisCount( |             gridDelegate: const SliverGridDelegateWithFixedCrossAxisCount( | ||||||
|               crossAxisCount: 5, |                 crossAxisCount: 6, childAspectRatio: 0.7), | ||||||
|             ), |             physics: const ScrollPhysics(), | ||||||
|             padding: const EdgeInsets.all(2), |             padding: const EdgeInsets.all(2), | ||||||
|             itemBuilder: (BuildContext context, int idx) => |             itemBuilder: (BuildContext context, int idx) => NoteTile( | ||||||
|                 NoteTile(data: value.tiledata[idx]), |               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, |             itemCount: value.tiledata.length, | ||||||
|           ); |           ), | ||||||
|         }, |         ); | ||||||
|       ), |       }, | ||||||
|     ); |     ); | ||||||
|   } |   } | ||||||
| } | } | ||||||
|   | |||||||
| @@ -6,21 +6,25 @@ import 'package:sqflite/sqflite.dart'; | |||||||
| import 'path.dart'; | import 'path.dart'; | ||||||
|  |  | ||||||
| class NoteFile { | class NoteFile { | ||||||
|   late Database _db; |   Database? _db; | ||||||
|   String filename; |   String filename; | ||||||
|   late String _basePath; |   String? _basePath; | ||||||
|  |  | ||||||
|   String? _newFileName; |   String? _newFileName; | ||||||
|  |  | ||||||
|   Database db() { |   Database db() { | ||||||
|     return _db; |     assert(_db != null); | ||||||
|  |     return _db!; | ||||||
|   } |   } | ||||||
|  |  | ||||||
|   NoteFile(this.filename); |   NoteFile(this.filename); | ||||||
|  |  | ||||||
|   Future<void> init() async { |   Future<void> init() async { | ||||||
|     _basePath = (await getSavePath()).path; |     if (_basePath == null) { | ||||||
|     final path = _basePath + Platform.pathSeparator + filename; |       await _initBasePath(); | ||||||
|  |     } | ||||||
|  |  | ||||||
|  |     final path = _basePath! + Platform.pathSeparator + filename; | ||||||
|     _db = await openDatabase( |     _db = await openDatabase( | ||||||
|       path, |       path, | ||||||
|       onCreate: (db, version) async { |       onCreate: (db, version) async { | ||||||
| @@ -44,7 +48,11 @@ class NoteFile { | |||||||
|  |  | ||||||
|   Future<void> delete() async { |   Future<void> delete() async { | ||||||
|     await close(); |     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) { |   void rename(String newname) { | ||||||
| @@ -53,19 +61,27 @@ class NoteFile { | |||||||
|  |  | ||||||
|   Future<void> close() async { |   Future<void> close() async { | ||||||
|     // shrink the db file size |     // shrink the db file size | ||||||
|     if (_db.isOpen) { |     if (_db != null && _db!.isOpen) { | ||||||
|       await _db.execute('VACUUM'); |       await _db!.execute('VACUUM'); | ||||||
|       await _db.close(); |       await _db!.close(); | ||||||
|     } else { |     } else { | ||||||
|       debugPrint('db file unexpectedly closed before shrinking'); |       debugPrint('db file unexpectedly closed before shrinking'); | ||||||
|     } |     } | ||||||
|  |  | ||||||
|  |     if (_basePath == null) { | ||||||
|  |       await _initBasePath(); | ||||||
|  |     } | ||||||
|  |  | ||||||
|     // perform qued file renaming operations |     // perform qued file renaming operations | ||||||
|     if (_newFileName != null) { |     if (_newFileName != null) { | ||||||
|       File(_basePath + Platform.pathSeparator + filename) |       File(_basePath! + Platform.pathSeparator + filename) | ||||||
|           .rename(_basePath + Platform.pathSeparator + _newFileName!); |           .rename(_basePath! + Platform.pathSeparator + _newFileName!); | ||||||
|       filename = _newFileName!; |       filename = _newFileName!; | ||||||
|       _newFileName = null; |       _newFileName = null; | ||||||
|     } |     } | ||||||
|   } |   } | ||||||
|  |  | ||||||
|  |   Future<void> _initBasePath() async { | ||||||
|  |     _basePath = (await getSavePath()).path; | ||||||
|  |   } | ||||||
| } | } | ||||||
|   | |||||||
							
								
								
									
										33
									
								
								lib/widgets/icon_text_button.dart
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										33
									
								
								lib/widgets/icon_text_button.dart
									
									
									
									
									
										Normal file
									
								
							| @@ -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), | ||||||
|  |         ) | ||||||
|  |       ], | ||||||
|  |     ); | ||||||
|  |   } | ||||||
|  | } | ||||||
| @@ -1,35 +1,74 @@ | |||||||
|  | import 'dart:math'; | ||||||
|  |  | ||||||
| import 'package:flutter/material.dart'; | import 'package:flutter/material.dart'; | ||||||
|  |  | ||||||
| import '../canvas/document_types.dart'; | import '../canvas/document_types.dart'; | ||||||
| import '../canvas/drawing_page.dart'; | import '../canvas/drawing_page.dart'; | ||||||
|  | import '../helpers/vibrate.dart'; | ||||||
|  |  | ||||||
| class NoteTile extends StatelessWidget { | 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 NoteMetaData data; | ||||||
|  |   final bool selectionMode; | ||||||
|  |   final bool selected; | ||||||
|  |   final void Function(bool) onSelectionChange; | ||||||
|  |  | ||||||
|   @override |   @override | ||||||
|   Widget build(BuildContext context) { |   Widget build(BuildContext context) { | ||||||
|     return GestureDetector( |     return GestureDetector( | ||||||
|       onTap: () { |       onTap: () { | ||||||
|         Navigator.push( |         if (selectionMode) { | ||||||
|           context, |           onSelectionChange(!selected); | ||||||
|           MaterialPageRoute( |         } else { | ||||||
|             builder: (context) => DrawingPage(meta: data), |           Navigator.push( | ||||||
|           ), |             context, | ||||||
|         ); |             MaterialPageRoute( | ||||||
|  |               builder: (context) => DrawingPage(meta: data), | ||||||
|  |             ), | ||||||
|  |           ); | ||||||
|  |         } | ||||||
|       }, |       }, | ||||||
|       child: SizedBox( |       onLongPress: () async { | ||||||
|         width: 100, |         shortVibrate(); | ||||||
|  |         onSelectionChange(!selected); | ||||||
|  |       }, | ||||||
|  |       child: Padding( | ||||||
|  |         padding: const EdgeInsets.all(20), | ||||||
|         child: Column( |         child: Column( | ||||||
|           children: [ |           children: [ | ||||||
|             SizedBox( |             Expanded( | ||||||
|               height: 150, |               child: Stack( | ||||||
|               width: 100, |                 children: [ | ||||||
|               child: Container( |                   ClipRRect( | ||||||
|                 color: Colors.white, |                     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( |             Text( | ||||||
|               data.name, |               data.name, | ||||||
|               style: const TextStyle(color: Colors.white), |               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; | ||||||
|  | } | ||||||
|   | |||||||
| @@ -441,6 +441,13 @@ packages: | |||||||
|       url: "https://pub.dartlang.org" |       url: "https://pub.dartlang.org" | ||||||
|     source: hosted |     source: hosted | ||||||
|     version: "2.1.2" |     version: "2.1.2" | ||||||
|  |   vibration: | ||||||
|  |     dependency: "direct main" | ||||||
|  |     description: | ||||||
|  |       name: vibration | ||||||
|  |       url: "https://pub.dartlang.org" | ||||||
|  |     source: hosted | ||||||
|  |     version: "1.7.6" | ||||||
|   win32: |   win32: | ||||||
|     dependency: transitive |     dependency: transitive | ||||||
|     description: |     description: | ||||||
|   | |||||||
| @@ -39,6 +39,7 @@ dependencies: | |||||||
|   pdf: ^3.8.4 |   pdf: ^3.8.4 | ||||||
|   permission_handler: ^10.2.0 |   permission_handler: ^10.2.0 | ||||||
|   fluttertoast: ^8.1.1 |   fluttertoast: ^8.1.1 | ||||||
|  |   vibration: ^1.7.6 | ||||||
|  |  | ||||||
| dev_dependencies: | dev_dependencies: | ||||||
|   flutter_test: |   flutter_test: | ||||||
|   | |||||||
		Reference in New Issue
	
	Block a user