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
7 changes: 7 additions & 0 deletions lib/features/main_screen/main_content_split.dart
Original file line number Diff line number Diff line change
Expand Up @@ -479,6 +479,13 @@ class MainContentSplitState extends State<MainContentSplit>
onRestoreLastSelectedObject: () {
unawaited(_restoreLastSelectedObject());
},
onOpenSqliteTable: ws.activeConnection == null
? null
: (name) => widget.onSqliteObjectSelected(
ws.activeConnection!,
name,
SqliteObjectKind.table,
),
isReadOnly: ws.isReadOnly,
onRequestNewConnection: widget.onRequestNewConnection,
onRequestNewConnectionFromUrl:
Expand Down
5 changes: 5 additions & 0 deletions lib/features/main_screen/workspace_panel.dart
Original file line number Diff line number Diff line change
Expand Up @@ -66,6 +66,7 @@ class WorkspacePanel extends StatefulWidget {
this.lastSelectedRedisKey,
this.onNavigateHome,
this.onRestoreLastSelectedObject,
this.onOpenSqliteTable,
this.isReadOnly = false,
this.onRequestNewConnection,
this.onRequestNewConnectionFromUrl,
Expand Down Expand Up @@ -189,6 +190,9 @@ class WorkspacePanel extends StatefulWidget {
/// Callback to restore the last selected table / object view.
final VoidCallback? onRestoreLastSelectedObject;

/// Opens a SQLite table chosen on the Overview page (#1816).
final void Function(String name)? onOpenSqliteTable;

/// Empty-state hero: primary CTA to add a connection.
final void Function()? onRequestNewConnection;
final void Function()? onRequestNewConnectionFromUrl;
Expand Down Expand Up @@ -378,6 +382,7 @@ class _WorkspacePanelState extends State<WorkspacePanel> {
sqlTabRequestToken: widget.sqliteSqlTabRequestToken,
lastSelectedSqliteObject: widget.lastSelectedSqliteObject,
onRestoreLastSelectedObject: widget.onRestoreLastSelectedObject,
onOpenTable: widget.onOpenSqliteTable,
isReadOnly: widget.isReadOnly,
),
object: sq == null
Expand Down
33 changes: 26 additions & 7 deletions lib/features/sqlite/sqlite_overview_tab.dart
Original file line number Diff line number Diff line change
@@ -1,3 +1,4 @@
import 'package:querya_desktop/core/motion/querya_hover_surface.dart';
import 'package:querya_desktop/core/util/log_ignored.dart';
import 'package:flutter/widgets.dart';
import 'dart:io';
Expand All @@ -17,10 +18,15 @@ class SqliteOverviewTab extends material.StatefulWidget {
const SqliteOverviewTab({
super.key,
required this.connectionRow,
this.onOpenTable,
});

final ConnectionRow connectionRow;

/// Opens a table in the table browser, as choosing it in the tree does
/// (#1816). Without it the chips are a plain list.
final void Function(String name)? onOpenTable;

@override
material.State<SqliteOverviewTab> createState() => _SqliteOverviewTabState();
}
Expand Down Expand Up @@ -391,15 +397,16 @@ class _SqliteOverviewTabState extends material.State<SqliteOverviewTab> {
spacing: 8,
runSpacing: 8,
children: _tables.map((t) {
return material.Container(
final open = widget.onOpenTable;
final chip = QueryaHoverSurface(
borderRadius: material.BorderRadius.circular(6),
padding: const material.EdgeInsets.symmetric(
horizontal: 10, vertical: 6),
decoration: material.BoxDecoration(
color: cs.muted.withValues(alpha: 0.4),
borderRadius: material.BorderRadius.circular(6),
border: material.Border.all(
color: cs.border.withValues(alpha: 0.3)),
),
idleColor: cs.muted.withValues(alpha: 0.4),
hoveredColor: cs.muted.withValues(alpha: 0.7),
border: material.Border.all(
color: cs.border.withValues(alpha: 0.3)),
onTap: open == null ? null : () => open(t),
child: material.Row(
mainAxisSize: material.MainAxisSize.min,
children: [
Expand All @@ -410,6 +417,18 @@ class _SqliteOverviewTabState extends material.State<SqliteOverviewTab> {
],
),
);
if (open == null) return chip;
return material.Tooltip(
message: 'Open $t',
excludeFromSemantics: true,
child: material.Semantics(
button: true,
label: t,
onTap: () => open(t),
excludeSemantics: true,
child: chip,
),
);
}).toList(),
),
],
Expand Down
5 changes: 5 additions & 0 deletions lib/features/sqlite/sqlite_workspace_home.dart
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,7 @@ class SqliteWorkspaceHome extends material.StatefulWidget {
this.sqlTabRequestToken = 0,
this.lastSelectedSqliteObject,
this.onRestoreLastSelectedObject,
this.onOpenTable,
this.isReadOnly = false,
});

Expand All @@ -28,6 +29,9 @@ class SqliteWorkspaceHome extends material.StatefulWidget {
final ({String name, SqliteObjectKind kind})? lastSelectedSqliteObject;
final VoidCallback? onRestoreLastSelectedObject;

/// Opens a table from the Overview page.
final void Function(String name)? onOpenTable;

@override
material.State<SqliteWorkspaceHome> createState() =>
_SqliteWorkspaceHomeState();
Expand Down Expand Up @@ -131,6 +135,7 @@ class _SqliteWorkspaceHomeState extends material.State<SqliteWorkspaceHome> {
SqliteOverviewTab(
key: ValueKey('sqlite_overview_${widget.connectionRow.id}'),
connectionRow: widget.connectionRow,
onOpenTable: widget.onOpenTable,
),
SqliteSqlWorkspace(
key: ValueKey('sqlite_sql_${widget.connectionRow.id}'),
Expand Down
82 changes: 82 additions & 0 deletions test/features/sqlite/sqlite_overview_chips_test.dart
Original file line number Diff line number Diff line change
@@ -0,0 +1,82 @@
import 'dart:io';

import 'package:flutter/widgets.dart';

import 'package:flutter_test/flutter_test.dart';
import 'package:path_provider_platform_interface/path_provider_platform_interface.dart';
import 'package:querya_desktop/core/storage/local_db.dart';
import 'package:querya_desktop/features/sqlite/sqlite_overview_tab.dart';
import 'package:sqflite_common_ffi/sqflite_ffi.dart';

import '../../support/querya_theme_test_shell.dart';

class _FakePathProvider extends PathProviderPlatform {
_FakePathProvider(this._root);
final String _root;

@override
Future<String?> getApplicationSupportPath() async => _root;
@override
Future<String?> getTemporaryPath() async => _root;
@override
Future<String?> getApplicationDocumentsPath() async => _root;
}

void main() {
TestWidgetsFlutterBinding.ensureInitialized();

late Directory tempDir;
late String dbPath;

setUpAll(() async {
sqfliteFfiInit();
tempDir = await Directory.systemTemp.createTemp('querya_overview_chips_');
PathProviderPlatform.instance = _FakePathProvider(tempDir.path);
await LocalDb.initFfi();
dbPath = '${tempDir.path}/chips.db';
final db = await databaseFactoryFfi.openDatabase(dbPath);
await db.execute('CREATE TABLE orders (id INTEGER PRIMARY KEY);');
await db.execute('CREATE TABLE users (id INTEGER PRIMARY KEY);');
await db.close();
});

tearDownAll(() async {
await LocalDb.instance.close();
try {
tempDir.deleteSync(recursive: true);
} catch (_) {}
});

testWidgets('a table chip opens that table (#1816)', (tester) async {
await tester.binding.setSurfaceSize(const Size(1000, 800));
addTearDown(() => tester.binding.setSurfaceSize(null));
final opened = <String>[];
final conn = ConnectionRow(
id: 601,
type: 'sqlite',
name: 'Chips',
host: dbPath,
createdAt: '2026-10-25T00:00:00Z',
);

await tester.pumpWidget(
queryaThemeTestShell(
child: SqliteOverviewTab(connectionRow: conn, onOpenTable: opened.add),
),
);
for (var i = 0; i < 10; i++) {
await tester.runAsync(
() => Future<void>.delayed(const Duration(milliseconds: 50)),
);
await tester.pump();
}

expect(find.text('users'), findsOneWidget);
await tester.tap(find.text('users'));
await tester.pump();
expect(opened, ['users']);

await tester.pumpWidget(const SizedBox());
await tester.pump(const Duration(seconds: 5));
});
}
Loading