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