@@ -8,6 +8,7 @@ import 'package:esse/widgets/button_text.dart';
88import 'package:esse/widgets/input_text.dart' ;
99import 'package:esse/widgets/shadow_dialog.dart' ;
1010import 'package:esse/provider.dart' ;
11+ import 'package:esse/global.dart' ;
1112import 'package:esse/rpc.dart' ;
1213
1314import 'package:esse/apps/file/models.dart' ;
@@ -100,10 +101,94 @@ class _FilesListState extends State<FilesList> {
100101 return widgets;
101102 }
102103
103- Widget _item (FilePath file) {
104+ _showItemMenu (details, lang, FilePath file) async {
105+ final screenSize = MediaQuery .of (context).size;
106+ double left = details.globalPosition.dx;
107+ double top = details.globalPosition.dy;
108+ await showMenu (
109+ context: context,
110+ position: RelativeRect .fromLTRB (
111+ left,
112+ top,
113+ screenSize.width - left,
114+ screenSize.height - top,
115+ ),
116+ items: [
117+ file.starred ? PopupMenuItem <int >(
118+ value: 0 ,
119+ child: Text (lang.setunstar, style: TextStyle (color: Color (0xFF6174FF ))),
120+ ) : PopupMenuItem <int >(
121+ value: 0 ,
122+ child: Text (lang.setstar, style: TextStyle (color: Color (0xFF6174FF ))),
123+ ),
124+ PopupMenuItem <int >(
125+ value: 1 ,
126+ child: Text (lang.rename, style: TextStyle (color: Color (0xFF6174FF ))),
127+ ),
128+ PopupMenuItem <int >(
129+ value: 2 ,
130+ child: Text (lang.moveTo, style: TextStyle (color: Color (0xFF6174FF ))),
131+ ),
132+ PopupMenuItem <int >(
133+ value: 8 ,
134+ child: Text (lang.moveTrash, style: TextStyle (color: Color (0xFF6174FF ))),
135+ ),
136+ PopupMenuItem <int >(
137+ value: 9 ,
138+ child: Text (lang.deleteImmediate, style: TextStyle (color: Colors .red)),
139+ ),
140+ ],
141+ elevation: 8.0 ,
142+ ).then ((value) {
143+ if (value == 0 ) {
144+ // star/unstar
145+ rpc.send ('dc-file-star' , [file.id, ! file.starred]);
146+ _loadDirectory (widget.path.last);
147+ } else if (value == 1 ) {
148+ // rename
149+ showShadowDialog (context, Icons .edit_rounded, lang.rename,
150+ _RenameScreen (file: file), 20.0
151+ );
152+ } else if (value == 2 ) {
153+ // moveTo
154+ showShadowDialog (context, Icons .drive_file_move_rounded, lang.moveTo,
155+ _MoveToScreen (file: file, path: widget.path),
156+ );
157+ } else if (value == 8 ) {
158+ // trash
159+ rpc.send ('dc-file-trash' , [file.id]);
160+ _loadDirectory (widget.path.last);
161+ } else if (value == 9 ) {
162+ // delete
163+ showDialog (
164+ context: context,
165+ builder: (BuildContext context) {
166+ return AlertDialog (
167+ title: Text (lang.delete + " ${file .showName ()} ?" ),
168+ actions: [
169+ TextButton (child: Text (lang.cancel), onPressed: () => Navigator .pop (context)),
170+ TextButton (child: Text (lang.ok),
171+ onPressed: () {
172+ Navigator .pop (context);
173+ rpc.send ('dc-file-delete' , [file.id]);
174+ _loadDirectory (widget.path.last);
175+ },
176+ ),
177+ ]
178+ );
179+ },
180+ );
181+ }
182+ });
183+ }
184+
185+ Widget _item (FilePath file, AppLocalizations lang, bool desktop, ColorScheme color) {
104186 final trueName = file.showName ();
105187 final params = file.fileType ().params ();
106- return InkWell (
188+
189+ return GestureDetector (
190+ onLongPressDown: desktop ? null : (details) => _showItemMenu (details, lang, file),
191+ onSecondaryLongPressDown: desktop ? (details) => _showItemMenu (details, lang, file) : null ,
107192 onTap: () {
108193 if (file.isDirectory ()) {
109194 _nextDirectory (file);
@@ -117,10 +202,18 @@ class _FilesListState extends State<FilesList> {
117202 mainAxisAlignment: MainAxisAlignment .center,
118203 crossAxisAlignment: CrossAxisAlignment .center,
119204 children: [
120- Container (
121- height: 55.0 ,
122- width: 60.0 ,
123- child: Icon (params[0 ], color: params[1 ], size: 48.0 ),
205+ Stack (
206+ alignment: Alignment .center,
207+ children: [
208+ Icon (params[0 ], color: params[1 ], size: 48.0 ),
209+ if (file.starred)
210+ Positioned (bottom: 2.0 , right: 2.0 ,
211+ child: Container (
212+ decoration: ShapeDecoration (color: color.background, shape: CircleBorder ()),
213+ child: Icon (Icons .star_rounded, color: Color (0xFF6174FF ), size: 16.0 ),
214+ ),
215+ ),
216+ ]
124217 ),
125218 Tooltip (
126219 message: trueName,
@@ -141,6 +234,7 @@ class _FilesListState extends State<FilesList> {
141234 final color = Theme .of (context).colorScheme;
142235 final lang = AppLocalizations .of (context);
143236 this ._isDesktop = isDisplayDesktop (context);
237+ final desktopDevice = isDesktop ();
144238
145239 return Scaffold (
146240 appBar: AppBar (
@@ -223,7 +317,7 @@ class _FilesListState extends State<FilesList> {
223317 child: GridView .extent (
224318 maxCrossAxisExtent: 75.0 ,
225319 childAspectRatio: 0.8 ,
226- children: this ._children.map ((file) => _item (file)).toList ()
320+ children: this ._children.map ((file) => _item (file, lang, desktopDevice, color )).toList ()
227321 ),
228322 )
229323 ]
@@ -272,3 +366,187 @@ class _CreateFolder extends StatelessWidget {
272366 );
273367 }
274368}
369+
370+ class _RenameScreen extends StatelessWidget {
371+ final FilePath file;
372+ TextEditingController _nameController = TextEditingController ();
373+ FocusNode _nameFocus = FocusNode ();
374+
375+ _RenameScreen ({Key ? key, required this .file}) : super (key: key);
376+
377+ @override
378+ Widget build (BuildContext context) {
379+ final color = Theme .of (context).colorScheme;
380+ final lang = AppLocalizations .of (context);
381+
382+ _nameFocus.requestFocus ();
383+ if (_nameController.text.trim ().length == 0 ) {
384+ _nameController.text = file.showName ();
385+ }
386+
387+ return Column (
388+ children: [
389+ Container (
390+ padding: EdgeInsets .only (bottom: 20.0 ),
391+ child: InputText (
392+ icon: Icons .folder_rounded,
393+ text: lang.newFolder,
394+ controller: _nameController,
395+ focus: _nameFocus),
396+ ),
397+ ButtonText (
398+ text: lang.send,
399+ action: () {
400+ final name = _nameController.text.trim ();
401+ if (name.length == 0 ) {
402+ return ;
403+ }
404+ rpc.send ('dc-file-update' ,
405+ [file.id, file.root.toInt (), file.parent, file.rename (name)]
406+ );
407+ rpc.send ('dc-list' , [file.root.toInt (), file.parent]);
408+ Navigator .pop (context);
409+ }),
410+ ]
411+ );
412+ }
413+ }
414+
415+ class _MoveToScreen extends StatefulWidget {
416+ final List <FilePath > path;
417+ final FilePath file;
418+ _MoveToScreen ({Key ? key, required this .file, required this .path}) : super (key: key);
419+
420+ @override
421+ _MoveToScreenState createState () => _MoveToScreenState (List .from (this .path));
422+ }
423+
424+ class _MoveToScreenState extends State <_MoveToScreen > {
425+ List <FilePath > path = [];
426+ List <FilePath > _list = [];
427+ int ? _selected;
428+
429+ _MoveToScreenState (this .path);
430+
431+ @override
432+ void initState () {
433+ super .initState ();
434+ _loadDirectories ();
435+ }
436+
437+ _loadDirectories () async {
438+ final res = await httpPost (Global .httpRpc, 'dc-list' ,
439+ [this .path.last.root.toInt (), this .path.last.id]);
440+ if (res.isOk) {
441+ this ._list.clear ();
442+ this ._selected = null ;
443+ res.params.forEach ((param) {
444+ final f = FilePath .fromList (param);
445+ if (f.isDirectory ()) {
446+ this ._list.add (f);
447+ }
448+ });
449+ setState (() {});
450+ } else {
451+ // TODO toast.
452+ print (res.error);
453+ }
454+ }
455+
456+ List <Widget > _pathWidget (AppLocalizations lang) {
457+ List <Widget > widgets = [];
458+
459+ if (this .path.length > 0 ) {
460+ widgets.add (IconButton (
461+ icon: Icon (Icons .arrow_upward_rounded, size: 20.0 , color: Color (0xFF6174FF )),
462+ onPressed: () {
463+ this .path.removeLast ();
464+ if (this .path.length > 0 ) {
465+ _loadDirectories ();
466+ } else {
467+ this ._list.clear ();
468+ this ._selected = null ;
469+ ROOT_DIRECTORY .forEach ((root) {
470+ final name = root.params (lang)[1 ];
471+ this ._list.add (FilePath .root (root, name));
472+ });
473+ setState (() {});
474+ }
475+ }
476+ ));
477+ }
478+
479+ final n = this .path.length;
480+ for (int i = 0 ; i < n; i++ ) {
481+ widgets.add (InkWell (
482+ onTap: () {
483+ this .path = List .generate (i+ 1 , (j) => this .path[j]);
484+ _loadDirectories ();
485+ },
486+ child: Text ('/' + this .path[i].directoryName (),
487+ style: TextStyle (fontSize: 14.0 , color: Color (0xFFADB0BB )))
488+ ));
489+ }
490+
491+ return widgets;
492+ }
493+
494+ Widget _item (FilePath file, int index) {
495+ return ListTile (
496+ leading: Icon (Icons .folder_rounded),
497+ title: InkWell (
498+ child: Padding (
499+ padding: const EdgeInsets .all (2.0 ),
500+ child: Text (file.showName (), style: TextStyle (fontSize: 15.0 )),
501+ ),
502+ onTap: () {
503+ this .path.add (file);
504+ _loadDirectories ();
505+ }
506+ ),
507+ trailing: Radio (
508+ value: index,
509+ groupValue: _selected,
510+ onChanged: (int ? n) => setState (() {
511+ _selected = index;
512+ }),
513+ ),
514+ );
515+ }
516+
517+ @override
518+ Widget build (BuildContext context) {
519+ final color = Theme .of (context).colorScheme;
520+ final lang = AppLocalizations .of (context);
521+
522+ double maxHeight = MediaQuery .of (context).size.height - 400 ;
523+ if (maxHeight < 100.0 ) {
524+ maxHeight = 100.0 ;
525+ }
526+
527+ return Column (
528+ children: [
529+ Row (children: this ._pathWidget (lang)),
530+ Container (
531+ height: maxHeight,
532+ child: SingleChildScrollView (
533+ child: Column (
534+ children: List <Widget >.generate (_list.length, (i) => _item (_list[i], i),
535+ ))
536+ )
537+ ),
538+ ButtonText (
539+ text: lang.ok,
540+ enable: this ._selected != null ,
541+ action: () {
542+ final parent = this ._list[this ._selected! ];
543+ rpc.send ('dc-file-update' ,
544+ [widget.file.id, parent.root.toInt (), parent.id, widget.file.name]
545+ );
546+ Navigator .pop (context);
547+ rpc.send ('dc-list' , [widget.path.last.root.toInt (), widget.path.last.parent]);
548+ }),
549+ ]
550+ );
551+ }
552+ }
0 commit comments