Dedupe native transfer/notification code and split ServerProvider into focused controllers
Build APK / build (push) Successful in 5m20s

Consolidates duplicated GET/PUT/notification-channel logic across
DownloadService/ShareUploadService/SyncEngine into shared Kotlin helpers,
gives upload/download real batch queueing instead of dropping a second
concurrent batch, and dedupes repeated Dart channel-argument boilerplate.

Replaces the 2300+ line ServerProvider god object with ten focused
ChangeNotifiers (SessionController, SettingsController, FilesController,
PhotosController, FavoritesController, TrashController, SharesController,
RecentController, SyncStatusController, PickController) plus ItemOperations,
a plain coordinator for cross-domain item mutations - fixing the coupling
where device-sync status, per-tab data, and global UI prefs all lived in
one object. Updates every view/widget call site accordingly and refreshes
the architecture/server/standards/styling docs to match.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
This commit is contained in:
2026-09-20 10:04:12 -04:00
co-authored by Claude Sonnet 5
parent faaec5365f
commit ff5f04e9a1
58 changed files with 4158 additions and 3594 deletions
+137 -137
View File
@@ -9,7 +9,12 @@ import 'package:provider/provider.dart';
import 'package:share_plus/share_plus.dart';
import '../models/nextcloud_item.dart';
import '../models/selection_action.dart';
import '../providers/server_provider.dart';
import '../providers/files_controller.dart';
import '../providers/item_operations.dart';
import '../providers/pick_controller.dart';
import '../providers/session_controller.dart';
import '../providers/settings_controller.dart';
import '../providers/sync_status_controller.dart';
import '../services/download_service.dart';
import '../services/share_intent_service.dart';
import '../widgets/breadcrumbs.dart';
@@ -204,16 +209,14 @@ class _FilesViewState extends State<FilesView>
/// folders are still browsable, a matching file either toggles selection
/// (multi-select requests) or immediately finishes the pick, and a
/// non-matching file (wrong mime type for the caller) is rejected.
void _handlePickTap(
BuildContext context,
ServerProvider provider,
NextcloudItem item,
) {
void _handlePickTap(BuildContext context, NextcloudItem item) {
final files = context.read<FilesController>();
final pick = context.read<PickController>();
if (item.isFolder) {
provider.navigateToFolder(item.path);
files.navigateToFolder(item.path);
return;
}
if (!provider.itemMatchesPickFilter(item)) {
if (!pick.itemMatchesPickFilter(item)) {
ScaffoldMessenger.of(context).showSnackBar(
const SnackBar(
content: Text("This app can't accept this file type"),
@@ -222,25 +225,27 @@ class _FilesViewState extends State<FilesView>
);
return;
}
if (provider.pickRequest!.allowMultiple) {
if (pick.pickRequest!.allowMultiple) {
_toggleSelection(item);
} else {
provider.confirmPick([item]);
pick.confirmPick([item]);
}
}
/// The bulk actions shown in the sticky selection toolbar for the
/// currently-selected items.
List<SelectionAction> _buildSelectionActions(
ServerProvider provider,
BuildContext context,
List<NextcloudItem> selected,
) {
if (provider.isPicking) {
final pick = context.watch<PickController>();
final sync = context.watch<SyncStatusController>();
if (pick.isPicking) {
return [
SelectionAction(
icon: Icons.check_rounded,
label: 'Use ${selected.length} item(s)',
onTap: () => provider.confirmPick(selected),
onTap: () => pick.confirmPick(selected),
),
];
}
@@ -252,52 +257,52 @@ class _FilesViewState extends State<FilesView>
label: selected.every((i) => i.isFavorite)
? 'Remove from favorites'
: 'Favorite',
onTap: () => _favoriteSelected(provider, selected),
onTap: () => _favoriteSelected(context, selected),
),
SelectionAction(
icon: Icons.share_rounded,
label: 'Share',
onTap: () => selected.length == 1
? ShareSheet.show(context, selected.single)
: _shareSelected(context, provider, selected),
: _shareSelected(context, selected),
),
SelectionAction(
icon: Icons.download_rounded,
label: 'Download',
onTap: () => _downloadSelected(context, provider, selected),
onTap: () => _downloadSelected(context, selected),
),
SelectionAction(
icon: Icons.delete_outline_rounded,
label: 'Delete',
onTap: () => _confirmDeleteSelected(context, provider, selected),
onTap: () => _confirmDeleteSelected(context, selected),
),
SelectionAction(
icon: Icons.copy_rounded,
label: 'Copy',
onTap: () => _moveOrCopySelected(provider, selected, copy: true),
onTap: () => _moveOrCopySelected(context, selected, copy: true),
),
SelectionAction(
icon: Icons.drive_file_move_rounded,
label: 'Move',
onTap: () => _moveOrCopySelected(provider, selected, copy: false),
onTap: () => _moveOrCopySelected(context, selected, copy: false),
),
if (selected.length == 1)
SelectionAction(
icon: Icons.drive_file_rename_outline_rounded,
label: 'Rename',
onTap: () => _renameItem(provider, selected.single),
onTap: () => _renameItem(selected.single),
),
if (selected.length == 1)
SelectionAction(
icon: provider.isPathSynced(selected.single.path)
icon: sync.isPathSynced(selected.single.path)
? Icons.sync_rounded
: Icons.sync_outlined,
label: provider.isPathSynced(selected.single.path)
label: sync.isPathSynced(selected.single.path)
? 'Stop syncing to device'
: 'Sync to device',
onTap: () => provider.isPathSynced(selected.single.path)
? provider.removeSyncedPath(selected.single.path)
: provider.addSyncedPath(selected.single.path),
onTap: () => sync.isPathSynced(selected.single.path)
? sync.removeSyncedPath(selected.single.path)
: sync.addSyncedPath(selected.single.path),
),
if (selected.length == 1)
SelectionAction(
@@ -308,7 +313,7 @@ class _FilesViewState extends State<FilesView>
];
}
Future<void> _renameItem(ServerProvider provider, NextcloudItem item) async {
Future<void> _renameItem(NextcloudItem item) async {
final controller = TextEditingController(text: item.name);
final newName = await showDialog<String>(
context: context,
@@ -345,7 +350,8 @@ class _FilesViewState extends State<FilesView>
if (!mounted) return;
final messenger = ScaffoldMessenger.of(context);
final success = await provider.renameItem(item, newName);
final ops = context.read<ItemOperations>();
final success = await ops.renameItem(item, newName);
_clearSelection();
messenger.showSnackBar(
SnackBar(
@@ -359,9 +365,9 @@ class _FilesViewState extends State<FilesView>
Future<void> _shareSelected(
BuildContext context,
ServerProvider provider,
List<NextcloudItem> items,
) async {
final ops = context.read<ItemOperations>();
final messenger = ScaffoldMessenger.of(context);
messenger.showSnackBar(
const SnackBar(
@@ -372,7 +378,7 @@ class _FilesViewState extends State<FilesView>
final lines = <String>[];
for (final item in items) {
final link = await provider.createShareLink(item);
final link = await ops.createShareLink(item);
if (link != null) {
lines.add(items.length > 1 ? '${item.name}: $link' : link);
}
@@ -408,14 +414,14 @@ class _FilesViewState extends State<FilesView>
Widget build(BuildContext context) {
final theme = Theme.of(context);
final colorScheme = theme.colorScheme;
final provider = context.watch<ServerProvider>();
final files = context.watch<FilesController>();
final pathDepth = provider.pathStack.length;
final pathDepth = files.pathStack.length;
final navigatingDeeper = pathDepth > _lastPathDepth;
_lastPathDepth = pathDepth;
final hasBreadcrumbs = provider.pathStack.length > 1;
final selectedItems = provider.items
final hasBreadcrumbs = files.pathStack.length > 1;
final selectedItems = files.items
.where((i) => _selectedIds.contains(i.id))
.toList();
@@ -445,16 +451,16 @@ class _FilesViewState extends State<FilesView>
children: [
IconButton(
icon: Icon(
provider.filesSortAscending
files.filesSortAscending
? Icons.arrow_upward_rounded
: Icons.arrow_downward_rounded,
size: 20,
),
visualDensity: VisualDensity.compact,
tooltip: provider.filesSortAscending
tooltip: files.filesSortAscending
? 'Ascending'
: 'Descending',
onPressed: provider.toggleFilesSortOrder,
onPressed: files.toggleFilesSortOrder,
),
// A plain width, not Expanded - this row scrolls
// horizontally now, which gives every control unbounded
@@ -462,16 +468,16 @@ class _FilesViewState extends State<FilesView>
SizedBox(
width: 130,
child: SortMenuButton(
field: provider.filesSortField,
onChanged: provider.setFilesSortField,
field: files.filesSortField,
onChanged: files.setFilesSortField,
),
),
ToggleIconButton(
icon: provider.showHiddenFiles
icon: files.showHiddenFiles
? Icons.visibility_rounded
: Icons.visibility_off_rounded,
isSelected: provider.showHiddenFiles,
onTap: () => provider.toggleShowHiddenFiles(),
isSelected: files.showHiddenFiles,
onTap: () => files.toggleShowHiddenFiles(),
tooltip: 'Show hidden files',
),
const SizedBox(width: 4),
@@ -479,17 +485,17 @@ class _FilesViewState extends State<FilesView>
children: [
ToggleIconButton(
icon: Symbols.circles_rounded,
isSelected: provider.storageScope == StorageScope.cloud,
isSelected: files.storageScope == StorageScope.cloud,
onTap: () =>
provider.setStorageScope(StorageScope.cloud),
files.setStorageScope(StorageScope.cloud),
tooltip: 'Cloud storage',
),
ToggleIconButton(
icon: Symbols.hard_drive_rounded,
isSelected:
provider.storageScope == StorageScope.external,
files.storageScope == StorageScope.external,
onTap: () =>
provider.setStorageScope(StorageScope.external),
files.setStorageScope(StorageScope.external),
tooltip: 'External storage',
),
],
@@ -500,17 +506,17 @@ class _FilesViewState extends State<FilesView>
ToggleIconButton(
icon: Icons.select_all_rounded,
isSelected:
provider.filesTypeFilter == FilesTypeFilter.all,
files.filesTypeFilter == FilesTypeFilter.all,
onTap: () =>
provider.setFilesTypeFilter(FilesTypeFilter.all),
files.setFilesTypeFilter(FilesTypeFilter.all),
tooltip: 'Files & folders',
),
ToggleIconButton(
icon: Icons.insert_drive_file_outlined,
isSelected:
provider.filesTypeFilter ==
files.filesTypeFilter ==
FilesTypeFilter.filesOnly,
onTap: () => provider.setFilesTypeFilter(
onTap: () => files.setFilesTypeFilter(
FilesTypeFilter.filesOnly,
),
tooltip: 'Files only',
@@ -518,9 +524,9 @@ class _FilesViewState extends State<FilesView>
ToggleIconButton(
icon: Icons.folder_outlined,
isSelected:
provider.filesTypeFilter ==
files.filesTypeFilter ==
FilesTypeFilter.foldersOnly,
onTap: () => provider.setFilesTypeFilter(
onTap: () => files.setFilesTypeFilter(
FilesTypeFilter.foldersOnly,
),
tooltip: 'Folders only',
@@ -532,14 +538,14 @@ class _FilesViewState extends State<FilesView>
children: [
ToggleIconButton(
icon: Icons.view_list_rounded,
isSelected: !provider.isGridView,
onTap: () => provider.setGridView(false),
isSelected: !files.isGridView,
onTap: () => files.setGridView(false),
tooltip: 'List view',
),
ToggleIconButton(
icon: Icons.grid_view_rounded,
isSelected: provider.isGridView,
onTap: () => provider.setGridView(true),
isSelected: files.isGridView,
onTap: () => files.setGridView(true),
tooltip: 'Grid view',
),
],
@@ -553,8 +559,8 @@ class _FilesViewState extends State<FilesView>
SizedBox(
height: 32,
child: Breadcrumbs(
pathStack: provider.pathStack,
onTap: (index) => provider.navigateToPathIndex(index),
pathStack: files.pathStack,
onTap: (index) => files.navigateToPathIndex(index),
),
),
],
@@ -574,12 +580,12 @@ class _FilesViewState extends State<FilesView>
),
),
// Files List / Grid
if (provider.isLoading)
if (files.isLoading)
const SliverFillRemaining(
hasScrollBody: false,
child: Center(child: CircularProgressIndicator()),
)
else if (provider.errorMessage != null)
else if (files.errorMessage != null)
SliverFillRemaining(
hasScrollBody: false,
child: Center(
@@ -603,7 +609,7 @@ class _FilesViewState extends State<FilesView>
),
const SizedBox(height: 8),
Text(
provider.errorMessage!,
files.errorMessage!,
textAlign: TextAlign.center,
style: theme.textTheme.bodyMedium?.copyWith(
color: colorScheme.onSurfaceVariant,
@@ -611,7 +617,7 @@ class _FilesViewState extends State<FilesView>
),
const SizedBox(height: 20),
FilledButton.icon(
onPressed: provider.refreshData,
onPressed: files.refreshData,
icon: const Icon(Icons.refresh_rounded),
label: const Text('Retry Connection'),
),
@@ -620,7 +626,7 @@ class _FilesViewState extends State<FilesView>
),
),
)
else if (provider.items.isEmpty)
else if (files.items.isEmpty)
SliverFillRemaining(
hasScrollBody: false,
child: Center(
@@ -643,7 +649,7 @@ class _FilesViewState extends State<FilesView>
),
),
)
else if (provider.isGridView)
else if (files.isGridView)
SliverPadding(
key: const ValueKey('files-grid'),
padding: const EdgeInsets.symmetric(horizontal: 20, vertical: 8),
@@ -655,14 +661,14 @@ class _FilesViewState extends State<FilesView>
mainAxisSpacing: 12,
),
delegate: SliverChildBuilderDelegate((context, index) {
final item = provider.items[index];
final item = files.items[index];
return _FolderEnterAnimation(
key: ValueKey('${provider.currentFolderPath}::${item.id}'),
key: ValueKey('${files.currentFolderPath}::${item.id}'),
index: index,
fromRight: navigatingDeeper,
child: _buildGridCard(context, item, provider),
child: _buildGridCard(context, item),
);
}, childCount: provider.items.length),
}, childCount: files.items.length),
),
)
else
@@ -671,14 +677,14 @@ class _FilesViewState extends State<FilesView>
padding: const EdgeInsets.symmetric(horizontal: 20, vertical: 8),
sliver: SliverList(
delegate: SliverChildBuilderDelegate((context, index) {
final item = provider.items[index];
final item = files.items[index];
return _FolderEnterAnimation(
key: ValueKey('${provider.currentFolderPath}::${item.id}'),
key: ValueKey('${files.currentFolderPath}::${item.id}'),
index: index,
fromRight: navigatingDeeper,
child: _buildListTile(context, item, provider),
child: _buildListTile(context, item),
);
}, childCount: provider.items.length),
}, childCount: files.items.length),
),
),
@@ -693,29 +699,28 @@ class _FilesViewState extends State<FilesView>
];
return PopScope(
canPop: !_isSelecting && provider.pathStack.length <= 1,
canPop: !_isSelecting && files.pathStack.length <= 1,
onPopInvokedWithResult: (didPop, result) {
if (didPop) return;
if (_isSelecting) {
_clearSelection();
} else {
provider.navigateUp();
files.navigateUp();
}
},
child: SyncedHeaderScaffold(
scrollController: widget.scrollController,
provider: provider,
actions: [
IconButton(
icon: const Icon(Icons.add_rounded),
tooltip: 'New',
onPressed: () => _showCreateMenu(context, provider),
onPressed: () => _showCreateMenu(context),
),
const MoreTabsButton(),
const ProfileAvatarButton(),
],
selectionBar: _isSelecting
? _buildSelectionBar(context, theme, provider, selectedItems)
? _buildSelectionBar(context, theme, selectedItems)
: null,
contentSlivers: contentSlivers,
),
@@ -728,7 +733,6 @@ class _FilesViewState extends State<FilesView>
Widget _buildSelectionBar(
BuildContext context,
ThemeData theme,
ServerProvider provider,
List<NextcloudItem> selectedItems,
) {
return Padding(
@@ -771,7 +775,7 @@ class _FilesViewState extends State<FilesView>
mainAxisSize: MainAxisSize.min,
children: [
for (final action in _buildSelectionActions(
provider,
context,
selectedItems,
))
IconButton(
@@ -790,13 +794,14 @@ class _FilesViewState extends State<FilesView>
}
Future<void> _favoriteSelected(
ServerProvider provider,
BuildContext context,
List<NextcloudItem> items,
) async {
final ops = context.read<ItemOperations>();
final allFavorited = items.every((i) => i.isFavorite);
for (final item in items) {
if (item.isFavorite == allFavorited) {
await provider.toggleItemFavorite(item);
await ops.toggleItemFavorite(item);
}
}
_clearSelection();
@@ -810,9 +815,10 @@ class _FilesViewState extends State<FilesView>
/// notification for the whole selection instead of blocking here.
Future<void> _downloadSelected(
BuildContext context,
ServerProvider provider,
List<NextcloudItem> items,
) async {
final session = context.read<SessionController>();
final sync = context.read<SyncStatusController>();
final files = items.where((i) => !i.isFolder).toList();
_clearSelection();
if (files.isEmpty) return;
@@ -822,9 +828,9 @@ class _FilesViewState extends State<FilesView>
// Already mirrored locally by device sync (every selected file, not
// just some)? Save straight from the local copy instead of a fresh
// network fetch through DownloadService - see
// ServerProvider.localSyncedFilePath.
// SyncStatusController.localSyncedFilePath.
final localPaths = await Future.wait(
files.map(provider.localSyncedFilePath),
files.map(sync.localSyncedFilePath),
);
if (localPaths.every((path) => path != null)) {
try {
@@ -859,7 +865,7 @@ class _FilesViewState extends State<FilesView>
}
try {
await DownloadService.startDownload(provider, files);
await DownloadService.startDownload(session, files);
messenger.showSnackBar(
SnackBar(
content: Text(
@@ -882,7 +888,6 @@ class _FilesViewState extends State<FilesView>
Future<void> _confirmDeleteSelected(
BuildContext context,
ServerProvider provider,
List<NextcloudItem> items,
) async {
final confirmed = await showDialog<bool>(
@@ -912,9 +917,10 @@ class _FilesViewState extends State<FilesView>
if (confirmed != true || !context.mounted) return;
final messenger = ScaffoldMessenger.of(context);
final ops = context.read<ItemOperations>();
var succeeded = 0;
for (final item in items) {
final success = await provider.deleteItem(item.path);
final success = await ops.deleteItem(item.path);
if (success) succeeded++;
}
@@ -928,7 +934,7 @@ class _FilesViewState extends State<FilesView>
}
Future<void> _moveOrCopySelected(
ServerProvider provider,
BuildContext context,
List<NextcloudItem> items, {
required bool copy,
}) async {
@@ -937,7 +943,7 @@ class _FilesViewState extends State<FilesView>
items,
copy: copy,
);
if (!mounted || result == null) return;
if (!context.mounted || result == null) return;
_clearSelection();
ScaffoldMessenger.of(context).showSnackBar(
SnackBar(
@@ -949,13 +955,15 @@ class _FilesViewState extends State<FilesView>
);
}
Widget _buildListTile(
BuildContext context,
NextcloudItem item,
ServerProvider provider,
) {
Widget _buildListTile(BuildContext context, NextcloudItem item) {
final theme = Theme.of(context);
final colorScheme = theme.colorScheme;
final files = context.watch<FilesController>();
final pick = context.watch<PickController>();
final sync = context.watch<SyncStatusController>();
final session = context.watch<SessionController>();
final settings = context.watch<SettingsController>();
final ops = context.read<ItemOperations>();
final isSelected = _selectedIds.contains(item.id);
// No border radius here — the outer ClipRRect below is the only place
@@ -968,17 +976,17 @@ class _FilesViewState extends State<FilesView>
: colorScheme.surfaceContainerLow,
child: InkWell(
onTap: () {
if (provider.isPicking) {
_handlePickTap(context, provider, item);
if (pick.isPicking) {
_handlePickTap(context, item);
} else if (_isSelecting) {
_toggleSelection(item);
} else if (item.isFolder) {
provider.navigateToFolder(item.path);
files.navigateToFolder(item.path);
} else {
_openFile(context, item, provider);
_openFile(context, item);
}
},
onLongPress: provider.isPicking && !provider.pickRequest!.allowMultiple
onLongPress: pick.isPicking && !pick.pickRequest!.allowMultiple
? null
: () => _toggleSelection(item),
child: Padding(
@@ -994,7 +1002,7 @@ class _FilesViewState extends State<FilesView>
checkmarkSize: 24,
child: ItemThumbnail(
item: item,
provider: provider,
service: session.service,
size: 44,
borderRadius: 12,
iconSize: 22,
@@ -1004,7 +1012,7 @@ class _FilesViewState extends State<FilesView>
right: -2,
bottom: -2,
child: SyncStatusBadge(
status: provider.syncStatusFor(item),
status: sync.syncStatusFor(item),
),
),
],
@@ -1039,15 +1047,15 @@ class _FilesViewState extends State<FilesView>
),
);
final content = _isSelecting || provider.isPicking
final content = _isSelecting || pick.isPicking
? card
: SwipeableItem(
itemKey: ValueKey('file-${item.id}'),
itemName: item.name,
provider: provider,
onFavorite: () => provider.toggleItemFavorite(item),
settings: settings,
onFavorite: () => ops.toggleItemFavorite(item),
onShare: () => ShareSheet.show(context, item),
onDelete: () => _deleteViaSwipe(provider, item),
onDelete: () => _deleteViaSwipe(item),
child: card,
);
@@ -1057,11 +1065,9 @@ class _FilesViewState extends State<FilesView>
);
}
Future<void> _deleteViaSwipe(
ServerProvider provider,
NextcloudItem item,
) async {
final success = await provider.deleteItem(item.path);
Future<void> _deleteViaSwipe(NextcloudItem item) async {
final ops = context.read<ItemOperations>();
final success = await ops.deleteItem(item.path);
if (!mounted) return;
ScaffoldMessenger.of(context).showSnackBar(
SnackBar(
@@ -1073,11 +1079,10 @@ class _FilesViewState extends State<FilesView>
);
}
Widget _buildGridCard(
BuildContext context,
NextcloudItem item,
ServerProvider provider,
) {
Widget _buildGridCard(BuildContext context, NextcloudItem item) {
final files = context.watch<FilesController>();
final pick = context.watch<PickController>();
final sync = context.watch<SyncStatusController>();
final isSelected = _selectedIds.contains(item.id);
final isMedia =
(item.type == NextcloudItemType.image ||
@@ -1090,17 +1095,17 @@ class _FilesViewState extends State<FilesView>
clipBehavior: Clip.antiAlias,
child: InkWell(
onTap: () {
if (provider.isPicking) {
_handlePickTap(context, provider, item);
if (pick.isPicking) {
_handlePickTap(context, item);
} else if (_isSelecting) {
_toggleSelection(item);
} else if (item.isFolder) {
provider.navigateToFolder(item.path);
files.navigateToFolder(item.path);
} else {
_openFile(context, item, provider);
_openFile(context, item);
}
},
onLongPress: provider.isPicking && !provider.pickRequest!.allowMultiple
onLongPress: pick.isPicking && !pick.pickRequest!.allowMultiple
? null
: () => _toggleSelection(item),
child: Stack(
@@ -1109,13 +1114,13 @@ class _FilesViewState extends State<FilesView>
isSelected: isSelected,
checkmarkSize: 32,
child: isMedia
? _buildMediaGridContent(context, item, provider)
? _buildMediaGridContent(context, item)
: _buildPlainGridContent(context, item),
),
Positioned(
right: 6,
bottom: 6,
child: SyncStatusBadge(status: provider.syncStatusFor(item)),
child: SyncStatusBadge(status: sync.syncStatusFor(item)),
),
],
),
@@ -1126,11 +1131,8 @@ class _FilesViewState extends State<FilesView>
/// Grid content for images/videos: the actual preview fills the whole
/// card as a background, with the name/size legible over a bottom scrim
/// — matching a Google Photos-style grid instead of a small icon badge.
Widget _buildMediaGridContent(
BuildContext context,
NextcloudItem item,
ServerProvider provider,
) {
Widget _buildMediaGridContent(BuildContext context, NextcloudItem item) {
final session = context.watch<SessionController>();
final theme = Theme.of(context);
return LayoutBuilder(
@@ -1143,7 +1145,7 @@ class _FilesViewState extends State<FilesView>
children: [
Image.network(
item.previewUrl!,
headers: provider.service?.authHeaders,
headers: session.service?.authHeaders,
fit: BoxFit.cover,
cacheWidth: cachePixels,
cacheHeight: cachePixels,
@@ -1253,18 +1255,15 @@ class _FilesViewState extends State<FilesView>
);
}
void _openFile(
BuildContext context,
NextcloudItem item,
ServerProvider provider,
) {
void _openFile(BuildContext context, NextcloudItem item) {
final files = context.read<FilesController>();
Navigator.push(
context,
FileViewerScreen.route(item: item, siblings: provider.items),
FileViewerScreen.route(item: item, siblings: files.items),
);
}
void _showCreateMenu(BuildContext context, ServerProvider provider) {
void _showCreateMenu(BuildContext context) {
final colorScheme = Theme.of(context).colorScheme;
showModalBottomSheet(
context: context,
@@ -1293,7 +1292,7 @@ class _FilesViewState extends State<FilesView>
title: const Text('New Folder'),
onTap: () {
Navigator.pop(sheetContext);
_showCreateFolderDialog(context, provider);
_showCreateFolderDialog(context);
},
),
],
@@ -1304,7 +1303,8 @@ class _FilesViewState extends State<FilesView>
);
}
void _showCreateFolderDialog(BuildContext context, ServerProvider provider) {
void _showCreateFolderDialog(BuildContext context) {
final files = context.read<FilesController>();
final controller = TextEditingController();
showDialog(
@@ -1329,7 +1329,7 @@ class _FilesViewState extends State<FilesView>
final name = controller.text.trim();
if (name.isNotEmpty) {
Navigator.pop(context);
final success = await provider.createFolder(name);
final success = await files.createFolder(name);
if (context.mounted) {
ScaffoldMessenger.of(context).showSnackBar(
SnackBar(