Skip to content

Commit 0df12c4

Browse files
committed
DC: impl file star/rename/move/delete
1 parent ce69b24 commit 0df12c4

File tree

9 files changed

+360
-11
lines changed

9 files changed

+360
-11
lines changed

lib/apps/file/list.dart

Lines changed: 285 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -8,6 +8,7 @@ import 'package:esse/widgets/button_text.dart';
88
import 'package:esse/widgets/input_text.dart';
99
import 'package:esse/widgets/shadow_dialog.dart';
1010
import 'package:esse/provider.dart';
11+
import 'package:esse/global.dart';
1112
import 'package:esse/rpc.dart';
1213

1314
import '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+
}

lib/apps/file/models.dart

Lines changed: 18 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -3,7 +3,7 @@ import 'package:flutter/material.dart';
33
import 'package:esse/utils/relative_time.dart';
44
import 'package:esse/l10n/localizations.dart';
55

6-
const List<RootDirectory> ROOT_DIRECTORY = [
6+
const List<RootDirectory> HOME_DIRECTORY = [
77
RootDirectory.Star,
88
RootDirectory.Document,
99
RootDirectory.Image,
@@ -13,6 +13,13 @@ const List<RootDirectory> ROOT_DIRECTORY = [
1313
RootDirectory.Trash,
1414
];
1515

16+
const List<RootDirectory> ROOT_DIRECTORY = [
17+
RootDirectory.Document,
18+
RootDirectory.Image,
19+
RootDirectory.Music,
20+
RootDirectory.Video,
21+
];
22+
1623
enum RootDirectory {
1724
Star,
1825
Document,
@@ -220,6 +227,16 @@ class FilePath {
220227
}
221228
}
222229

230+
String rename(String newName) {
231+
if (isDirectory()) {
232+
return newName + '.dir';
233+
} else if (isPost()){
234+
return newName + '.quill.json';
235+
} else {
236+
return newName;
237+
}
238+
}
239+
223240
FileType fileType() {
224241
return parseFileType(this.name);
225242
}

lib/l10n/localizations.dart

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -78,6 +78,7 @@ abstract class AppLocalizations {
7878
String get album;
7979
String get file;
8080
String get delete;
81+
String get deleteImmediate;
8182
String get open;
8283
String get unknown;
8384
String get create;
@@ -100,6 +101,8 @@ abstract class AppLocalizations {
100101
String get register;
101102
String get gallery;
102103
String get link;
104+
String get rename;
105+
String get moveTo;
103106

104107
// theme
105108
String get themeDark;
@@ -243,6 +246,9 @@ abstract class AppLocalizations {
243246
String get newFolder;
244247
String get uploadFile;
245248
String get trashClear;
249+
String get moveTrash;
250+
String get setstar;
251+
String get setunstar;
246252
}
247253

248254
class _AppLocalizationsDelegate

0 commit comments

Comments
 (0)