Nikolas Rimikis
1 year ago
5 changed files with 329 additions and 237 deletions
@ -0,0 +1,163 @@ |
|||||||
|
import 'package:filesize/filesize.dart'; |
||||||
|
import 'package:flutter/material.dart'; |
||||||
|
import 'package:neon/utils.dart'; |
||||||
|
import 'package:neon_files/l10n/localizations.dart'; |
||||||
|
import 'package:neon_files/neon_files.dart'; |
||||||
|
import 'package:provider/provider.dart'; |
||||||
|
|
||||||
|
class FileActions extends StatelessWidget { |
||||||
|
const FileActions({ |
||||||
|
required this.details, |
||||||
|
super.key, |
||||||
|
}); |
||||||
|
|
||||||
|
final FileDetails details; |
||||||
|
|
||||||
|
Future<void> onSelected(final BuildContext context, final FilesFileAction action) async { |
||||||
|
final bloc = Provider.of<FilesBloc>(context, listen: false); |
||||||
|
final browserBloc = bloc.browser; |
||||||
|
switch (action) { |
||||||
|
case FilesFileAction.toggleFavorite: |
||||||
|
if (details.isFavorite ?? false) { |
||||||
|
bloc.removeFavorite(details.path); |
||||||
|
} else { |
||||||
|
bloc.addFavorite(details.path); |
||||||
|
} |
||||||
|
break; |
||||||
|
case FilesFileAction.details: |
||||||
|
await Navigator.of(context).push( |
||||||
|
MaterialPageRoute( |
||||||
|
builder: (final context) => FilesDetailsPage( |
||||||
|
bloc: bloc, |
||||||
|
details: details, |
||||||
|
), |
||||||
|
), |
||||||
|
); |
||||||
|
break; |
||||||
|
case FilesFileAction.rename: |
||||||
|
final result = await showRenameDialog( |
||||||
|
context: context, |
||||||
|
title: |
||||||
|
details.isDirectory ? AppLocalizations.of(context).folderRename : AppLocalizations.of(context).fileRename, |
||||||
|
value: details.name, |
||||||
|
); |
||||||
|
if (result != null) { |
||||||
|
bloc.rename(details.path, result); |
||||||
|
} |
||||||
|
break; |
||||||
|
case FilesFileAction.move: |
||||||
|
final b = bloc.getNewFilesBrowserBloc(); |
||||||
|
final originalPath = details.path.sublist(0, details.path.length - 1); |
||||||
|
b.setPath(originalPath); |
||||||
|
final result = await showDialog<List<String>?>( |
||||||
|
context: context, |
||||||
|
builder: (final context) => FilesChooseFolderDialog( |
||||||
|
bloc: b, |
||||||
|
filesBloc: bloc, |
||||||
|
originalPath: originalPath, |
||||||
|
), |
||||||
|
); |
||||||
|
b.dispose(); |
||||||
|
if (result != null) { |
||||||
|
bloc.move(details.path, result..add(details.name)); |
||||||
|
} |
||||||
|
break; |
||||||
|
case FilesFileAction.copy: |
||||||
|
final b = bloc.getNewFilesBrowserBloc(); |
||||||
|
final originalPath = details.path.sublist(0, details.path.length - 1); |
||||||
|
b.setPath(originalPath); |
||||||
|
final result = await showDialog<List<String>?>( |
||||||
|
context: context, |
||||||
|
builder: (final context) => FilesChooseFolderDialog( |
||||||
|
bloc: b, |
||||||
|
filesBloc: bloc, |
||||||
|
originalPath: originalPath, |
||||||
|
), |
||||||
|
); |
||||||
|
b.dispose(); |
||||||
|
if (result != null) { |
||||||
|
bloc.copy(details.path, result..add(details.name)); |
||||||
|
} |
||||||
|
break; |
||||||
|
case FilesFileAction.sync: |
||||||
|
final sizeWarning = browserBloc.options.downloadSizeWarning.value; |
||||||
|
if (sizeWarning != null && details.size != null && details.size! > sizeWarning) { |
||||||
|
if (!(await showConfirmationDialog( |
||||||
|
context, |
||||||
|
AppLocalizations.of(context).downloadConfirmSizeWarning( |
||||||
|
filesize(sizeWarning), |
||||||
|
filesize(details.size), |
||||||
|
), |
||||||
|
))) { |
||||||
|
return; |
||||||
|
} |
||||||
|
} |
||||||
|
bloc.syncFile(details.path); |
||||||
|
break; |
||||||
|
case FilesFileAction.delete: |
||||||
|
if (await showConfirmationDialog( |
||||||
|
context, |
||||||
|
details.isDirectory |
||||||
|
? AppLocalizations.of(context).folderDeleteConfirm(details.name) |
||||||
|
: AppLocalizations.of(context).fileDeleteConfirm(details.name), |
||||||
|
)) { |
||||||
|
bloc.delete(details.path); |
||||||
|
} |
||||||
|
break; |
||||||
|
} |
||||||
|
} |
||||||
|
|
||||||
|
@override |
||||||
|
Widget build(final BuildContext context) => PopupMenuButton<FilesFileAction>( |
||||||
|
itemBuilder: (final context) => [ |
||||||
|
if (details.isFavorite != null) ...[ |
||||||
|
PopupMenuItem( |
||||||
|
value: FilesFileAction.toggleFavorite, |
||||||
|
child: Text( |
||||||
|
details.isFavorite! |
||||||
|
? AppLocalizations.of(context).removeFromFavorites |
||||||
|
: AppLocalizations.of(context).addToFavorites, |
||||||
|
), |
||||||
|
), |
||||||
|
], |
||||||
|
PopupMenuItem( |
||||||
|
value: FilesFileAction.details, |
||||||
|
child: Text(AppLocalizations.of(context).details), |
||||||
|
), |
||||||
|
PopupMenuItem( |
||||||
|
value: FilesFileAction.rename, |
||||||
|
child: Text(AppLocalizations.of(context).actionRename), |
||||||
|
), |
||||||
|
PopupMenuItem( |
||||||
|
value: FilesFileAction.move, |
||||||
|
child: Text(AppLocalizations.of(context).actionMove), |
||||||
|
), |
||||||
|
PopupMenuItem( |
||||||
|
value: FilesFileAction.copy, |
||||||
|
child: Text(AppLocalizations.of(context).actionCopy), |
||||||
|
), |
||||||
|
// TODO: https://github.com/provokateurin/nextcloud-neon/issues/4 |
||||||
|
if (!details.isDirectory) ...[ |
||||||
|
PopupMenuItem( |
||||||
|
value: FilesFileAction.sync, |
||||||
|
child: Text(AppLocalizations.of(context).actionSync), |
||||||
|
), |
||||||
|
], |
||||||
|
PopupMenuItem( |
||||||
|
value: FilesFileAction.delete, |
||||||
|
child: Text(AppLocalizations.of(context).actionDelete), |
||||||
|
), |
||||||
|
], |
||||||
|
onSelected: (final action) async => onSelected(context, action), |
||||||
|
); |
||||||
|
} |
||||||
|
|
||||||
|
enum FilesFileAction { |
||||||
|
toggleFavorite, |
||||||
|
details, |
||||||
|
rename, |
||||||
|
move, |
||||||
|
copy, |
||||||
|
sync, |
||||||
|
delete, |
||||||
|
} |
@ -0,0 +1,155 @@ |
|||||||
|
import 'package:filesize/filesize.dart'; |
||||||
|
import 'package:flutter/material.dart'; |
||||||
|
import 'package:material_design_icons_flutter/material_design_icons_flutter.dart'; |
||||||
|
import 'package:neon/widgets.dart'; |
||||||
|
import 'package:neon_files/neon_files.dart'; |
||||||
|
import 'package:neon_files/widgets/actions.dart'; |
||||||
|
import 'package:provider/provider.dart'; |
||||||
|
|
||||||
|
class FileListTile extends StatelessWidget { |
||||||
|
const FileListTile({ |
||||||
|
required this.context, |
||||||
|
required this.details, |
||||||
|
required this.uploadProgress, |
||||||
|
required this.downloadProgress, |
||||||
|
this.enableFileActions = true, |
||||||
|
this.onPickFile, |
||||||
|
super.key, |
||||||
|
}); |
||||||
|
|
||||||
|
final BuildContext context; |
||||||
|
final FileDetails details; |
||||||
|
final int? uploadProgress; |
||||||
|
final int? downloadProgress; |
||||||
|
final bool enableFileActions; |
||||||
|
final Function(FileDetails)? onPickFile; |
||||||
|
|
||||||
|
bool get _isUploading => uploadProgress != null; |
||||||
|
|
||||||
|
bool get _hasProgress => uploadProgress != null || downloadProgress != null; |
||||||
|
|
||||||
|
double? get _progress { |
||||||
|
if (!_hasProgress) { |
||||||
|
return null; |
||||||
|
} |
||||||
|
|
||||||
|
return (uploadProgress ?? downloadProgress)! / 100; |
||||||
|
} |
||||||
|
|
||||||
|
@override |
||||||
|
Widget build(final BuildContext context) { |
||||||
|
final bloc = Provider.of<FilesBloc>(context); |
||||||
|
final browserBloc = bloc.browser; |
||||||
|
|
||||||
|
// When the ETag is null it means we are uploading this file right now |
||||||
|
final onTap = details.isDirectory || details.etag != null |
||||||
|
? () { |
||||||
|
if (details.isDirectory) { |
||||||
|
browserBloc.setPath(details.path); |
||||||
|
} else { |
||||||
|
onPickFile?.call(details); |
||||||
|
} |
||||||
|
} |
||||||
|
: null; |
||||||
|
|
||||||
|
return ListTile( |
||||||
|
onTap: onTap, |
||||||
|
title: Text( |
||||||
|
details.name, |
||||||
|
overflow: TextOverflow.ellipsis, |
||||||
|
), |
||||||
|
subtitle: Row( |
||||||
|
children: [ |
||||||
|
if (details.lastModified != null) ...[ |
||||||
|
RelativeTime( |
||||||
|
date: details.lastModified!, |
||||||
|
), |
||||||
|
], |
||||||
|
if (details.size != null && details.size! > 0) ...[ |
||||||
|
const SizedBox( |
||||||
|
width: 10, |
||||||
|
), |
||||||
|
Text( |
||||||
|
filesize(details.size, 1), |
||||||
|
style: DefaultTextStyle.of(context).style.copyWith( |
||||||
|
color: Colors.grey, |
||||||
|
), |
||||||
|
), |
||||||
|
], |
||||||
|
], |
||||||
|
), |
||||||
|
leading: _FileIcon( |
||||||
|
hasProgress: _hasProgress, |
||||||
|
isUploading: _isUploading, |
||||||
|
progress: _progress, |
||||||
|
details: details, |
||||||
|
), |
||||||
|
trailing: _hasProgress && enableFileActions |
||||||
|
? FileActions(details: details) |
||||||
|
: const SizedBox.square( |
||||||
|
dimension: 48, |
||||||
|
), |
||||||
|
); |
||||||
|
} |
||||||
|
} |
||||||
|
|
||||||
|
class _FileIcon extends StatelessWidget { |
||||||
|
const _FileIcon({ |
||||||
|
required this.details, |
||||||
|
required this.hasProgress, |
||||||
|
required this.isUploading, |
||||||
|
this.progress, |
||||||
|
}); |
||||||
|
|
||||||
|
final bool hasProgress; |
||||||
|
final bool isUploading; |
||||||
|
final double? progress; |
||||||
|
final FileDetails details; |
||||||
|
|
||||||
|
@override |
||||||
|
Widget build(final BuildContext context) { |
||||||
|
final bloc = Provider.of<FilesBloc>(context); |
||||||
|
|
||||||
|
Widget icon = Center( |
||||||
|
child: hasProgress |
||||||
|
? Column( |
||||||
|
children: [ |
||||||
|
Icon( |
||||||
|
isUploading ? MdiIcons.upload : MdiIcons.download, |
||||||
|
color: Theme.of(context).colorScheme.primary, |
||||||
|
), |
||||||
|
LinearProgressIndicator( |
||||||
|
value: progress, |
||||||
|
), |
||||||
|
], |
||||||
|
) |
||||||
|
: FilePreview( |
||||||
|
bloc: bloc, |
||||||
|
details: details, |
||||||
|
withBackground: true, |
||||||
|
borderRadius: const BorderRadius.all(Radius.circular(8)), |
||||||
|
), |
||||||
|
); |
||||||
|
|
||||||
|
if (details.isFavorite ?? false) { |
||||||
|
icon = Stack( |
||||||
|
children: [ |
||||||
|
icon, |
||||||
|
const Align( |
||||||
|
alignment: Alignment.bottomRight, |
||||||
|
child: Icon( |
||||||
|
Icons.star, |
||||||
|
size: 14, |
||||||
|
color: Colors.yellow, |
||||||
|
), |
||||||
|
) |
||||||
|
], |
||||||
|
); |
||||||
|
} |
||||||
|
|
||||||
|
return SizedBox.square( |
||||||
|
dimension: 40, |
||||||
|
child: icon, |
||||||
|
); |
||||||
|
} |
||||||
|
} |
Loading…
Reference in new issue