diff --git a/client/lib/libphylum/repositories/resource_repository.dart b/client/lib/libphylum/repositories/resource_repository.dart index 4fb5510f..b35235d3 100644 --- a/client/lib/libphylum/repositories/resource_repository.dart +++ b/client/lib/libphylum/repositories/resource_repository.dart @@ -15,6 +15,8 @@ import 'package:phylum/libphylum/requests/resource_detail_request.dart'; import 'package:phylum/libphylum/responses/responses.dart'; import 'package:phylum/libphylum/util/uuid.dart'; +const performLocalNameCheck = true; + class ResourceRepository { final PhylumAccount account; @@ -33,27 +35,29 @@ class ResourceRepository { required NameConflictResolution conflictResolution, String? id, }) async { - var existing = await account.db.getResourcesByName(parent: parent, name: name); - String? deletedId; + id ??= generateUuid(); String localName = name; - if (existing.isNotEmpty) { - switch (conflictResolution) { - case nameConflictError: - throw NameConflictException(existing.first); - case nameConflictRename: - localName = await _createUniqueName(existing, parent, name); - break; - case nameConflictOverwrite: - case nameConflictDelete: - conflictResolution = nameConflictDelete; - deletedId = existing.first.id; - break; - default: - throw UnsupportedError('Unsupported conflict resolution'); + String? deletedId; + if (performLocalNameCheck) { + var existing = await account.db.getResourcesByName(parent: parent, name: name); + if (existing.isNotEmpty) { + switch (conflictResolution) { + case nameConflictError: + throw NameConflictException(existing.first); + case nameConflictRename: + localName = await _createUniqueName(existing, parent, name); + break; + case nameConflictOverwrite: + case nameConflictDelete: + conflictResolution = nameConflictDelete; + deletedId = existing.first.id; + break; + default: + throw UnsupportedError('Unsupported conflict resolution'); + } } } - id ??= generateUuid(); await account.addAction(ResourceMkdirAction( parent: parent, resourceName: name, @@ -73,25 +77,27 @@ class ResourceRepository { required String name, NameConflictResolution conflictResolution = nameConflictError, }) async { - var existing = await account.db.getResourcesByName(parent: parent, name: name); - String localName = name; String id = generateUuid(); + String localName = name; String? deletedId; - if (existing.isNotEmpty) { - switch (conflictResolution) { - case nameConflictError: - throw NameConflictException(existing.first); - case nameConflictRename: - localName = await _createUniqueName(existing, parent, name); - break; - case nameConflictOverwrite: - id = existing.first.id; - break; - case nameConflictDelete: - deletedId = existing.first.id; - break; - default: - throw UnsupportedError('Unsupported conflict resolution'); + if (performLocalNameCheck) { + var existing = await account.db.getResourcesByName(parent: parent, name: name); + if (existing.isNotEmpty) { + switch (conflictResolution) { + case nameConflictError: + throw NameConflictException(existing.first); + case nameConflictRename: + localName = await _createUniqueName(existing, parent, name); + break; + case nameConflictOverwrite: + id = existing.first.id; + break; + case nameConflictDelete: + deletedId = existing.first.id; + break; + default: + throw UnsupportedError('Unsupported conflict resolution'); + } } } final f = File(path); @@ -118,23 +124,25 @@ class ResourceRepository { required String parent, required NameConflictResolution conflictResolution, }) async { - var existing = await account.db.getResourcesByName(parent: parent, name: name); - String? deletedId; String localName = name; - if (existing.isNotEmpty) { - switch (conflictResolution) { - case nameConflictError: - throw NameConflictException(existing.first); - case nameConflictRename: - localName = await _createUniqueName(existing, parent, name); - break; - case nameConflictOverwrite: - case nameConflictDelete: - conflictResolution = nameConflictDelete; - deletedId = existing.first.id; - break; - default: - throw UnsupportedError('Unsupported conflict resolution'); + String? deletedId; + if (performLocalNameCheck) { + var existing = await account.db.getResourcesByName(parent: parent, name: name); + if (existing.isNotEmpty) { + switch (conflictResolution) { + case nameConflictError: + throw NameConflictException(existing.first); + case nameConflictRename: + localName = await _createUniqueName(existing, parent, name); + break; + case nameConflictOverwrite: + case nameConflictDelete: + conflictResolution = nameConflictDelete; + deletedId = existing.first.id; + break; + default: + throw UnsupportedError('Unsupported conflict resolution'); + } } } await account.addAction(ResourceMoveAction( @@ -162,23 +170,25 @@ class ResourceRepository { } String id = generateUuid(); - var existing = await account.db.getResourcesByName(parent: parent, name: name); String? deletedId; String localName = name; - if (existing.isNotEmpty) { - switch (conflictResolution) { - case nameConflictError: - throw NameConflictException(existing.first); - case nameConflictRename: - localName = await _createUniqueName(existing, parent, name); - break; - case nameConflictOverwrite: - case nameConflictDelete: - conflictResolution = nameConflictDelete; - deletedId = existing.first.id; - break; - default: - throw UnsupportedError('Unsupported conflict resolution'); + if (performLocalNameCheck) { + var existing = await account.db.getResourcesByName(parent: parent, name: name); + if (existing.isNotEmpty) { + switch (conflictResolution) { + case nameConflictError: + throw NameConflictException(existing.first); + case nameConflictRename: + localName = await _createUniqueName(existing, parent, name); + break; + case nameConflictOverwrite: + case nameConflictDelete: + conflictResolution = nameConflictDelete; + deletedId = existing.first.id; + break; + default: + throw UnsupportedError('Unsupported conflict resolution'); + } } } await account.addAction(ResourceCopyAction( diff --git a/client/lib/libphylum/responses/responses.dart b/client/lib/libphylum/responses/responses.dart index 0de12307..656a1541 100644 --- a/client/lib/libphylum/responses/responses.dart +++ b/client/lib/libphylum/responses/responses.dart @@ -41,7 +41,7 @@ class PhylumApiErrorResponse extends ApiResponse { final String message; @override - String get description => 'Api Error: $message'; + String get description => message; @override bool get success => false; diff --git a/client/lib/ui/explorer/resource_details_row.dart b/client/lib/ui/explorer/resource_details_row.dart index 8582ce3a..6f3d8388 100644 --- a/client/lib/ui/explorer/resource_details_row.dart +++ b/client/lib/ui/explorer/resource_details_row.dart @@ -1,4 +1,5 @@ import 'package:flutter/material.dart'; +import 'package:offtheline/offtheline.dart'; import 'package:phylum/libphylum/actions/action_resource.dart'; import 'package:phylum/libphylum/db/db.dart'; import 'package:phylum/libphylum/phylum_api_types.dart'; @@ -9,6 +10,30 @@ import 'package:provider/provider.dart'; import '../../libphylum/explorer/explorer_controller.dart'; +enum _SyncState { + none(Icons.block), + waiting(Icons.cloud_queue), + error(Icons.sync_problem), + syncing(Icons.sync); + + const _SyncState(this.icon); + + final IconData icon; + + factory _SyncState.fromStatus(ActionStatus status) { + if (status is ActionStatusReady || status is ActionStatusWaiting) { + return _SyncState.waiting; + } + if (status is ActionStatusUploading) { + return _SyncState.syncing; + } + if (status is ActionStatusError) { + return _SyncState.error; + } + return _SyncState.none; + } +} + class ResourceDetailsRow extends StatelessWidget { final Resource resource; final bool dropTargetActive; @@ -20,8 +45,15 @@ class ResourceDetailsRow extends StatelessWidget { final showBorder = context.select((state) => state.focusId == resource.id && state.showFocus); final highlight = context.select((state) => state.isSelected(resource.id)); final dim = context.select((state) => state.isSelected(resource.id) && state.dragging); - final hasChanges = context.select( - (state) => state.actions.any((action) => action is ResourceAction && action.resourceId == resource.id)); + final sync = context.select((state) { + return state.actions.fold(_SyncState.none, (acc, action) { + if (action is ResourceAction && action.resourceId == resource.id) { + final actionState = _SyncState.fromStatus(action.status); + if (actionState.index > acc.index) return actionState; + } + return acc; + }); + }); final theme = Theme.of(context); final colorScheme = theme.colorScheme; final border = dropTargetActive @@ -83,10 +115,10 @@ class ResourceDetailsRow extends StatelessWidget { Row( crossAxisAlignment: CrossAxisAlignment.center, children: [ - if (hasChanges) - const Padding( - padding: EdgeInsets.only(right: 6.0), - child: Icon(Icons.sync, size: 14), + if (sync != _SyncState.none) + Padding( + padding: const EdgeInsets.only(right: 6.0), + child: Icon(sync.icon, size: 14), ), if (resource.permissions != null) const Padding(