Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
46 changes: 33 additions & 13 deletions lib/widgets/folder_content.dart
Original file line number Diff line number Diff line change
Expand Up @@ -23,9 +23,14 @@ import 'package:icarus/widgets/strategy_tile/strategy_tile.dart';
import 'package:shadcn_ui/shadcn_ui.dart';

class FolderContent extends ConsumerWidget {
FolderContent({super.key, this.folder});
FolderContent({
super.key,
this.folder,
required this.onCreateStrategy,
});

final Folder? folder;
final VoidCallback onCreateStrategy;
final TextEditingController searchController = TextEditingController();

static final strategiesListenable =
Expand Down Expand Up @@ -129,14 +134,31 @@ class FolderContent extends ConsumerWidget {
localStrategies: const [],
cloudStrategies: _filterCloudStrategies(ref, strategies),
isCloud: true,
showAddSharedItemAction: isSharedWithMe,
emptyStateKey: ValueKey(
isSharedWithMe ? 'shared-empty-state' : 'cloud-empty-state',
),
emptyStateIcon:
isSharedWithMe ? Icons.people_outline : Icons.cloud_outlined,
emptyStateTitle:
isSharedWithMe ? 'No shared items yet' : 'No cloud strategies yet',
emptyStateTitle: isSharedWithMe
? 'Nothing shared with you yet'
: 'Your cloud library is empty',
emptyStateSubtitle: isSharedWithMe
? 'Shared folders and strategies will appear here'
: 'Create a cloud strategy to start your online workspace',
? 'Add a share link or code from a teammate to keep it here.'
: 'Create your first cloud strategy to keep it available across '
'your Icarus clients.',
emptyStateAction: isSharedWithMe
? ShadButton(
key: const ValueKey('shared-empty-add-item'),
onPressed: () => showAddSharedItemDialog(context),
leading: const Icon(LucideIcons.link),
child: const Text('Add by Link or Code'),
)
: ShadButton(
key: const ValueKey('cloud-empty-create-strategy'),
onPressed: onCreateStrategy,
leading: const Icon(Icons.add),
child: const Text('Create Cloud Strategy'),
),
),
);
}
Expand Down Expand Up @@ -210,14 +232,16 @@ class FolderContent extends ConsumerWidget {
required List<StrategyData> localStrategies,
required List<CloudStrategySummary> cloudStrategies,
required bool isCloud,
bool showAddSharedItemAction = false,
Key? emptyStateKey,
IconData? emptyStateIcon,
required String emptyStateTitle,
required String emptyStateSubtitle,
Widget? emptyStateAction,
}) {
final hasStrategies =
localStrategies.isNotEmpty || cloudStrategies.isNotEmpty;
final Widget emptyState = Center(
key: emptyStateKey,
child: ConstrainedBox(
constraints: const BoxConstraints(maxWidth: 420),
child: Column(
Expand All @@ -244,13 +268,9 @@ class FolderContent extends ConsumerWidget {
color: Settings.tacticalVioletTheme.mutedForeground,
),
),
if (showAddSharedItemAction) ...[
if (emptyStateAction != null) ...[
const SizedBox(height: 18),
ShadButton(
onPressed: () => showAddSharedItemDialog(context),
leading: const Icon(LucideIcons.link),
child: const Text('Add by Link or Code'),
),
emptyStateAction,
],
],
),
Expand Down
18 changes: 14 additions & 4 deletions lib/widgets/folder_navigator.dart
Original file line number Diff line number Diff line change
Expand Up @@ -432,7 +432,10 @@ class _FolderNavigatorState extends ConsumerState<FolderNavigator> {
switchOutCurve: Curves.easeOutCubic,
child: KeyedSubtree(
key: ValueKey('$workspace/$cloudSection'),
child: FolderContent(folder: currentFolder),
child: FolderContent(
folder: currentFolder,
onCreateStrategy: showCreateDialog,
),
),
),
),
Expand Down Expand Up @@ -521,7 +524,7 @@ class _LibraryNavigationRailState extends ConsumerState<LibraryNavigationRail> {
: 'Log in to sync strategies',
selected: workspace == LibraryWorkspace.cloud &&
cloudSection == CloudLibrarySection.home,
onTap: cloudAvailable ? () => _selectCloudHome() : null,
onTap: cloudAvailable ? () => _selectCloudHome() : _showAuthDialog,
),
_LibraryRailItemData(
key: const ValueKey('library-shared'),
Expand All @@ -530,10 +533,10 @@ class _LibraryNavigationRailState extends ConsumerState<LibraryNavigationRail> {
semanticsLabel: 'Shared library',
description: cloudAvailable
? 'Strategies shared with you'
: 'Log in to view shared strats',
: 'Log in to view shared strategies',
selected: workspace == LibraryWorkspace.cloud &&
cloudSection == CloudLibrarySection.sharedWithMe,
onTap: cloudAvailable ? () => _selectShared() : null,
onTap: cloudAvailable ? () => _selectShared() : _showAuthDialog,
),
_LibraryRailItemData(
key: const ValueKey('library-community'),
Expand Down Expand Up @@ -687,6 +690,13 @@ class _LibraryNavigationRailState extends ConsumerState<LibraryNavigationRail> {
ref.read(folderProvider.notifier).updateID(null);
}

void _showAuthDialog() {
showDialog<void>(
context: context,
builder: (_) => const AuthDialog(),
);
}

void _selectCloudHome() {
ref.read(libraryWorkspaceProvider.notifier).select(LibraryWorkspace.cloud);
ref
Expand Down
26 changes: 26 additions & 0 deletions test/widgets/cloud_beta_automation_semantics_test.dart
Original file line number Diff line number Diff line change
Expand Up @@ -72,6 +72,32 @@ void main() {
expect(_semantics('Community library').properties.onTap, isNotNull);
expect(_semantics('Log in to Icarus').properties.onTap, isNotNull);
});

testWidgets('signed-out cloud destinations open login', (tester) async {
await tester.pumpWidget(
_testApp(
const SizedBox(
width: 220,
height: 800,
child: LibraryNavigationRail(),
),
),
);

await tester.tap(find.byKey(const ValueKey('library-cloud')));
await tester.pumpAndSettle();

expect(find.byType(AuthDialog), findsOneWidget);
expect(find.text('Sign in'), findsAtLeastNWidgets(1));

Navigator.of(tester.element(find.byType(AuthDialog))).pop();
await tester.pumpAndSettle();

await tester.tap(find.byKey(const ValueKey('library-shared')));
await tester.pumpAndSettle();

expect(find.byType(AuthDialog), findsOneWidget);
});
}

Semantics _semantics(String label) {
Expand Down
108 changes: 108 additions & 0 deletions test/widgets/cloud_library_empty_states_test.dart
Original file line number Diff line number Diff line change
@@ -0,0 +1,108 @@
import 'package:flutter/material.dart';
import 'package:flutter_riverpod/flutter_riverpod.dart';
import 'package:flutter_test/flutter_test.dart';
import 'package:icarus/collab/collab_models.dart';
import 'package:icarus/const/coordinate_system.dart';
import 'package:icarus/providers/auth_provider.dart';
import 'package:icarus/providers/collab/remote_library_provider.dart';
import 'package:icarus/providers/library_workspace_provider.dart';
import 'package:icarus/widgets/dialogs/share_links_dialog.dart';
import 'package:icarus/widgets/folder_content.dart';
import 'package:shadcn_ui/shadcn_ui.dart';

void main() {
setUp(() {
CoordinateSystem(playAreaSize: const Size(1280, 720));
});

testWidgets('empty Cloud leads to creating a cloud strategy', (tester) async {
var createCount = 0;
await tester.pumpWidget(
_cloudApp(
FolderContent(onCreateStrategy: () => createCount++),
),
);
await tester.pumpAndSettle();

expect(find.byKey(const ValueKey('cloud-empty-state')), findsOneWidget);
expect(find.text('Your cloud library is empty'), findsOneWidget);
expect(
find.byKey(const ValueKey('cloud-empty-create-strategy')),
findsOneWidget,
);

await tester.tap(
find.byKey(const ValueKey('cloud-empty-create-strategy')),
);
expect(createCount, 1);
});

testWidgets('empty Shared leads to adding a link or code', (tester) async {
await tester.pumpWidget(
_cloudApp(
FolderContent(onCreateStrategy: () {}),
section: CloudLibrarySection.sharedWithMe,
),
);
await tester.pumpAndSettle();

expect(find.byKey(const ValueKey('shared-empty-state')), findsOneWidget);
expect(find.text('Nothing shared with you yet'), findsOneWidget);
expect(
find.byKey(const ValueKey('shared-empty-add-item')),
findsOneWidget,
);

await tester.tap(find.byKey(const ValueKey('shared-empty-add-item')));
await tester.pumpAndSettle();

expect(find.byType(AddSharedItemDialog), findsOneWidget);
});
}

Widget _cloudApp(
Widget child, {
CloudLibrarySection section = CloudLibrarySection.home,
}) {
return ProviderScope(
overrides: [
authProvider.overrideWith(_CloudReadyAuthProvider.new),
libraryWorkspaceProvider.overrideWith(_CloudWorkspaceNotifier.new),
cloudLibrarySectionProvider.overrideWith(
() => _CloudSectionNotifier(section),
),
cloudFoldersProvider.overrideWith(
(_) => Stream.value(const <CloudFolderSummary>[]),
),
cloudStrategiesProvider.overrideWith(
(_) => Stream.value(const <CloudStrategySummary>[]),
),
],
child: ShadApp(home: Scaffold(body: child)),
);
}

class _CloudReadyAuthProvider extends AuthProvider {
@override
AppAuthState build() => const AppAuthState(
isLoading: false,
isAuthenticated: true,
isConvexUserReady: true,
convexAuthStatus: ConvexAuthStatus.ready,
user: null,
);
}

class _CloudWorkspaceNotifier extends LibraryWorkspaceNotifier {
@override
LibraryWorkspace build() => LibraryWorkspace.cloud;
}

class _CloudSectionNotifier extends CloudLibrarySectionNotifier {
_CloudSectionNotifier(this._section);

final CloudLibrarySection _section;

@override
CloudLibrarySection build() => _section;
}
Loading