Browse Source

DC: impl file star/rename/move/delete

pull/18/head
Sun 4 years ago
parent
commit
0df12c4e1f
  1. 292
      lib/apps/file/list.dart
  2. 19
      lib/apps/file/models.dart
  3. 6
      lib/l10n/localizations.dart
  4. 12
      lib/l10n/localizations_en.dart
  5. 12
      lib/l10n/localizations_zh.dart
  6. 4
      lib/pages/home.dart
  7. 7
      lib/utils/adaptive.dart
  8. 6
      src/apps/file/models.rs
  9. 13
      src/apps/file/rpc.rs

292
lib/apps/file/list.dart

@ -8,6 +8,7 @@ import 'package:esse/widgets/button_text.dart'; @@ -8,6 +8,7 @@ import 'package:esse/widgets/button_text.dart';
import 'package:esse/widgets/input_text.dart';
import 'package:esse/widgets/shadow_dialog.dart';
import 'package:esse/provider.dart';
import 'package:esse/global.dart';
import 'package:esse/rpc.dart';
import 'package:esse/apps/file/models.dart';
@ -100,10 +101,94 @@ class _FilesListState extends State<FilesList> { @@ -100,10 +101,94 @@ class _FilesListState extends State<FilesList> {
return widgets;
}
Widget _item(FilePath file) {
_showItemMenu(details, lang, FilePath file) async {
final screenSize = MediaQuery.of(context).size;
double left = details.globalPosition.dx;
double top = details.globalPosition.dy;
await showMenu(
context: context,
position: RelativeRect.fromLTRB(
left,
top,
screenSize.width - left,
screenSize.height - top,
),
items: [
file.starred ? PopupMenuItem<int>(
value: 0,
child: Text(lang.setunstar, style: TextStyle(color: Color(0xFF6174FF))),
) : PopupMenuItem<int>(
value: 0,
child: Text(lang.setstar, style: TextStyle(color: Color(0xFF6174FF))),
),
PopupMenuItem<int>(
value: 1,
child: Text(lang.rename, style: TextStyle(color: Color(0xFF6174FF))),
),
PopupMenuItem<int>(
value: 2,
child: Text(lang.moveTo, style: TextStyle(color: Color(0xFF6174FF))),
),
PopupMenuItem<int>(
value: 8,
child: Text(lang.moveTrash, style: TextStyle(color: Color(0xFF6174FF))),
),
PopupMenuItem<int>(
value: 9,
child: Text(lang.deleteImmediate, style: TextStyle(color: Colors.red)),
),
],
elevation: 8.0,
).then((value) {
if (value == 0) {
// star/unstar
rpc.send('dc-file-star', [file.id, !file.starred]);
_loadDirectory(widget.path.last);
} else if (value == 1) {
// rename
showShadowDialog(context, Icons.edit_rounded, lang.rename,
_RenameScreen(file: file), 20.0
);
} else if (value == 2) {
// moveTo
showShadowDialog(context, Icons.drive_file_move_rounded, lang.moveTo,
_MoveToScreen(file: file, path: widget.path),
);
} else if (value == 8) {
// trash
rpc.send('dc-file-trash', [file.id]);
_loadDirectory(widget.path.last);
} else if (value == 9) {
// delete
showDialog(
context: context,
builder: (BuildContext context) {
return AlertDialog(
title: Text(lang.delete + " ${file.showName()} ?"),
actions: [
TextButton(child: Text(lang.cancel), onPressed: () => Navigator.pop(context)),
TextButton(child: Text(lang.ok),
onPressed: () {
Navigator.pop(context);
rpc.send('dc-file-delete', [file.id]);
_loadDirectory(widget.path.last);
},
),
]
);
},
);
}
});
}
Widget _item(FilePath file, AppLocalizations lang, bool desktop, ColorScheme color) {
final trueName = file.showName();
final params = file.fileType().params();
return InkWell(
return GestureDetector(
onLongPressDown: desktop ? null : (details) => _showItemMenu(details, lang, file),
onSecondaryLongPressDown: desktop ? (details) => _showItemMenu(details, lang, file) : null,
onTap: () {
if (file.isDirectory()) {
_nextDirectory(file);
@ -117,10 +202,18 @@ class _FilesListState extends State<FilesList> { @@ -117,10 +202,18 @@ class _FilesListState extends State<FilesList> {
mainAxisAlignment: MainAxisAlignment.center,
crossAxisAlignment: CrossAxisAlignment.center,
children: [
Container(
height: 55.0,
width: 60.0,
child: Icon(params[0], color: params[1], size: 48.0),
Stack(
alignment: Alignment.center,
children: [
Icon(params[0], color: params[1], size: 48.0),
if (file.starred)
Positioned(bottom: 2.0, right: 2.0,
child: Container(
decoration: ShapeDecoration(color: color.background, shape: CircleBorder()),
child: Icon(Icons.star_rounded, color: Color(0xFF6174FF), size: 16.0),
),
),
]
),
Tooltip(
message: trueName,
@ -141,6 +234,7 @@ class _FilesListState extends State<FilesList> { @@ -141,6 +234,7 @@ class _FilesListState extends State<FilesList> {
final color = Theme.of(context).colorScheme;
final lang = AppLocalizations.of(context);
this._isDesktop = isDisplayDesktop(context);
final desktopDevice = isDesktop();
return Scaffold(
appBar: AppBar(
@ -223,7 +317,7 @@ class _FilesListState extends State<FilesList> { @@ -223,7 +317,7 @@ class _FilesListState extends State<FilesList> {
child: GridView.extent(
maxCrossAxisExtent: 75.0,
childAspectRatio: 0.8,
children: this._children.map((file) => _item(file)).toList()
children: this._children.map((file) => _item(file, lang, desktopDevice, color)).toList()
),
)
]
@ -272,3 +366,187 @@ class _CreateFolder extends StatelessWidget { @@ -272,3 +366,187 @@ class _CreateFolder extends StatelessWidget {
);
}
}
class _RenameScreen extends StatelessWidget {
final FilePath file;
TextEditingController _nameController = TextEditingController();
FocusNode _nameFocus = FocusNode();
_RenameScreen({Key? key, required this.file}) : super(key: key);
@override
Widget build(BuildContext context) {
final color = Theme.of(context).colorScheme;
final lang = AppLocalizations.of(context);
_nameFocus.requestFocus();
if (_nameController.text.trim().length == 0) {
_nameController.text = file.showName();
}
return Column(
children: [
Container(
padding: EdgeInsets.only(bottom: 20.0),
child: InputText(
icon: Icons.folder_rounded,
text: lang.newFolder,
controller: _nameController,
focus: _nameFocus),
),
ButtonText(
text: lang.send,
action: () {
final name = _nameController.text.trim();
if (name.length == 0) {
return;
}
rpc.send('dc-file-update',
[file.id, file.root.toInt(), file.parent, file.rename(name)]
);
rpc.send('dc-list', [file.root.toInt(), file.parent]);
Navigator.pop(context);
}),
]
);
}
}
class _MoveToScreen extends StatefulWidget {
final List<FilePath> path;
final FilePath file;
_MoveToScreen({Key? key, required this.file, required this.path}) : super(key: key);
@override
_MoveToScreenState createState() => _MoveToScreenState(List.from(this.path));
}
class _MoveToScreenState extends State<_MoveToScreen> {
List<FilePath> path = [];
List<FilePath> _list = [];
int? _selected;
_MoveToScreenState(this.path);
@override
void initState() {
super.initState();
_loadDirectories();
}
_loadDirectories() async {
final res = await httpPost(Global.httpRpc, 'dc-list',
[this.path.last.root.toInt(), this.path.last.id]);
if (res.isOk) {
this._list.clear();
this._selected = null;
res.params.forEach((param) {
final f = FilePath.fromList(param);
if (f.isDirectory()) {
this._list.add(f);
}
});
setState(() {});
} else {
// TODO toast.
print(res.error);
}
}
List<Widget> _pathWidget(AppLocalizations lang) {
List<Widget> widgets = [];
if (this.path.length > 0) {
widgets.add(IconButton(
icon: Icon(Icons.arrow_upward_rounded, size: 20.0, color: Color(0xFF6174FF)),
onPressed: () {
this.path.removeLast();
if (this.path.length > 0) {
_loadDirectories();
} else {
this._list.clear();
this._selected = null;
ROOT_DIRECTORY.forEach((root) {
final name = root.params(lang)[1];
this._list.add(FilePath.root(root, name));
});
setState(() {});
}
}
));
}
final n = this.path.length;
for (int i = 0; i < n; i++) {
widgets.add(InkWell(
onTap: () {
this.path = List.generate(i+1, (j) => this.path[j]);
_loadDirectories();
},
child: Text('/'+this.path[i].directoryName(),
style: TextStyle(fontSize: 14.0, color: Color(0xFFADB0BB)))
));
}
return widgets;
}
Widget _item(FilePath file, int index) {
return ListTile(
leading: Icon(Icons.folder_rounded),
title: InkWell(
child: Padding(
padding: const EdgeInsets.all(2.0),
child: Text(file.showName(), style: TextStyle(fontSize: 15.0)),
),
onTap: () {
this.path.add(file);
_loadDirectories();
}
),
trailing: Radio(
value: index,
groupValue: _selected,
onChanged: (int? n) => setState(() {
_selected = index;
}),
),
);
}
@override
Widget build(BuildContext context) {
final color = Theme.of(context).colorScheme;
final lang = AppLocalizations.of(context);
double maxHeight = MediaQuery.of(context).size.height - 400;
if (maxHeight < 100.0) {
maxHeight = 100.0;
}
return Column(
children: [
Row(children: this._pathWidget(lang)),
Container(
height: maxHeight,
child: SingleChildScrollView(
child: Column(
children: List<Widget>.generate(_list.length, (i) => _item(_list[i], i),
))
)
),
ButtonText(
text: lang.ok,
enable: this._selected != null,
action: () {
final parent = this._list[this._selected!];
rpc.send('dc-file-update',
[widget.file.id, parent.root.toInt(), parent.id, widget.file.name]
);
Navigator.pop(context);
rpc.send('dc-list', [widget.path.last.root.toInt(), widget.path.last.parent]);
}),
]
);
}
}

19
lib/apps/file/models.dart

@ -3,7 +3,7 @@ import 'package:flutter/material.dart'; @@ -3,7 +3,7 @@ import 'package:flutter/material.dart';
import 'package:esse/utils/relative_time.dart';
import 'package:esse/l10n/localizations.dart';
const List<RootDirectory> ROOT_DIRECTORY = [
const List<RootDirectory> HOME_DIRECTORY = [
RootDirectory.Star,
RootDirectory.Document,
RootDirectory.Image,
@ -13,6 +13,13 @@ const List<RootDirectory> ROOT_DIRECTORY = [ @@ -13,6 +13,13 @@ const List<RootDirectory> ROOT_DIRECTORY = [
RootDirectory.Trash,
];
const List<RootDirectory> ROOT_DIRECTORY = [
RootDirectory.Document,
RootDirectory.Image,
RootDirectory.Music,
RootDirectory.Video,
];
enum RootDirectory {
Star,
Document,
@ -220,6 +227,16 @@ class FilePath { @@ -220,6 +227,16 @@ class FilePath {
}
}
String rename(String newName) {
if (isDirectory()) {
return newName + '.dir';
} else if (isPost()){
return newName + '.quill.json';
} else {
return newName;
}
}
FileType fileType() {
return parseFileType(this.name);
}

6
lib/l10n/localizations.dart

@ -78,6 +78,7 @@ abstract class AppLocalizations { @@ -78,6 +78,7 @@ abstract class AppLocalizations {
String get album;
String get file;
String get delete;
String get deleteImmediate;
String get open;
String get unknown;
String get create;
@ -100,6 +101,8 @@ abstract class AppLocalizations { @@ -100,6 +101,8 @@ abstract class AppLocalizations {
String get register;
String get gallery;
String get link;
String get rename;
String get moveTo;
// theme
String get themeDark;
@ -243,6 +246,9 @@ abstract class AppLocalizations { @@ -243,6 +246,9 @@ abstract class AppLocalizations {
String get newFolder;
String get uploadFile;
String get trashClear;
String get moveTrash;
String get setstar;
String get setunstar;
}
class _AppLocalizationsDelegate

12
lib/l10n/localizations_en.dart

@ -81,6 +81,8 @@ class AppLocalizationsEn extends AppLocalizations { @@ -81,6 +81,8 @@ class AppLocalizationsEn extends AppLocalizations {
@override
String get delete => 'Delete';
@override
String get deleteImmediate => 'Delete Immediately';
@override
String get open => 'Open';
@override
String get unknown => 'Unknown';
@ -124,6 +126,10 @@ class AppLocalizationsEn extends AppLocalizations { @@ -124,6 +126,10 @@ class AppLocalizationsEn extends AppLocalizations {
String get gallery => 'Gallery';
@override
String get link => 'Link';
@override
String get rename => 'Rename';
@override
String get moveTo => 'Move to';
// theme
@override
@ -390,4 +396,10 @@ class AppLocalizationsEn extends AppLocalizations { @@ -390,4 +396,10 @@ class AppLocalizationsEn extends AppLocalizations {
String get uploadFile => 'Upload File';
@override
String get trashClear => 'Empty the trash, Remove completely?';
@override
String get moveTrash => 'Move to trash';
@override
String get setstar => 'Star';
@override
String get setunstar => 'Unstar';
}

12
lib/l10n/localizations_zh.dart

@ -81,6 +81,8 @@ class AppLocalizationsZh extends AppLocalizations { @@ -81,6 +81,8 @@ class AppLocalizationsZh extends AppLocalizations {
@override
String get delete => '删除';
@override
String get deleteImmediate => '立即删除';
@override
String get open => '打开';
@override
String get unknown => '未知';
@ -124,6 +126,10 @@ class AppLocalizationsZh extends AppLocalizations { @@ -124,6 +126,10 @@ class AppLocalizationsZh extends AppLocalizations {
String get gallery => '图库';
@override
String get link => '链接';
@override
String get rename => '重命名';
@override
String get moveTo => '移动到';
// theme
@override
@ -390,4 +396,10 @@ class AppLocalizationsZh extends AppLocalizations { @@ -390,4 +396,10 @@ class AppLocalizationsZh extends AppLocalizations {
String get uploadFile => '上传文件';
@override
String get trashClear => '清空垃圾箱,彻底删除?';
@override
String get moveTrash => '扔垃圾箱';
@override
String get setstar => '标星';
@override
String get setunstar => '取消标星';
}

4
lib/pages/home.dart

@ -259,9 +259,9 @@ class _HomeListState extends State<HomeList> { @@ -259,9 +259,9 @@ class _HomeListState extends State<HomeList> {
}
),
ListView.builder(
itemCount: ROOT_DIRECTORY.length,
itemCount: HOME_DIRECTORY.length,
itemBuilder: (BuildContext ctx, int index) {
final params = ROOT_DIRECTORY[index].params(lang);
final params = HOME_DIRECTORY[index].params(lang);
return ListTile(
leading: Icon(params[0], color: Color(0xFF6174FF)),
title: Text(params[1], style: TextStyle(fontSize: 16.0)),

7
lib/utils/adaptive.dart

@ -1,3 +1,4 @@ @@ -1,3 +1,4 @@
import 'dart:io' show Platform;
import 'package:flutter/material.dart';
enum DisplayType {
@ -34,5 +35,9 @@ bool isDisplayDesktop(BuildContext context) { @@ -34,5 +35,9 @@ bool isDisplayDesktop(BuildContext context) {
/// than [_desktopLandscapeBreakpoint] width. Used to build adaptive and responsive layouts.
bool isDisplaySmallDesktop(BuildContext context) {
return isDisplayDesktop(context) &&
MediaQuery.of(context).size.width < _desktopLandscapeBreakpoint;
MediaQuery.of(context).size.width < _desktopLandscapeBreakpoint;
}
bool isDesktop() {
return Platform.isLinux || Platform.isMacOS || Platform.isWindows;
}

6
src/apps/file/models.rs

@ -205,6 +205,12 @@ impl File { @@ -205,6 +205,12 @@ impl File {
Ok(())
}
pub fn delete(db: &DStorage, id: &i64) -> Result<()> {
let sql = format!("DELETE FROM files WHERE id = {}", id);
db.delete(&sql)?;
Ok(())
}
pub fn update(&self, db: &DStorage) -> Result<()> {
let sql = format!(
"UPDATE files SET parent = {}, root = {}, name = '{}' WHERE id = {}",

13
src/apps/file/rpc.rs

@ -135,4 +135,17 @@ pub(crate) fn new_rpc_handler(handler: &mut RpcHandler<RpcState>) { @@ -135,4 +135,17 @@ pub(crate) fn new_rpc_handler(handler: &mut RpcHandler<RpcState>) {
Ok(HandleResult::new())
},
);
handler.add_method(
"dc-file-delete",
|gid: GroupId, params: Vec<RpcParam>, state: Arc<RpcState>| async move {
let id = params[0].as_i64().ok_or(RpcError::ParseError)?;
// TODO deleted file & directory.
let db = file_db(state.layer.read().await.base(), &gid)?;
File::delete(&db, &id)?;
Ok(HandleResult::new())
},
);
}

Loading…
Cancel
Save