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
18 changes: 16 additions & 2 deletions lib/features/extensions/extension_sql_workspace.dart
Original file line number Diff line number Diff line change
Expand Up @@ -185,7 +185,14 @@ class _ExtensionSqlWorkspaceState
text: text,
selection: material.TextSelection.collapsed(offset: text.length),
);
} catch (_) {}
} catch (e) {
if (!mounted) return;
showAppToast(
context: context,
message: 'Failed to open SQL file: $e',
variant: AppToastVariant.error,
);
}
}

Future<void> _saveSqlFile() async {
Expand All @@ -201,7 +208,14 @@ class _ExtensionSqlWorkspaceState
final path = location?.path;
if (path == null || path.isEmpty) return;
await File(path).writeAsString(_sqlController.text);
} catch (_) {}
} catch (e) {
if (!mounted) return;
showAppToast(
context: context,
message: 'Failed to save SQL file: $e',
variant: AppToastVariant.error,
);
}
}

@override
Expand Down
3 changes: 2 additions & 1 deletion lib/features/main_screen/querya_window_title_bar.dart
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
import 'package:bitsdojo_window/bitsdojo_window.dart';
import 'package:flutter/material.dart' as material;
import 'package:querya_desktop/core/layout/ui_scale.dart';
import 'package:querya_desktop/core/storage/local_db.dart';
import 'package:querya_desktop/core/theme/querya_theme_scope.dart';
import 'package:querya_desktop/features/connections/driver_manager_dialog.dart';
Expand Down Expand Up @@ -75,7 +76,7 @@ class QueryaWindowTitleBar extends StatelessWidget {
final closeButtonColors = QueryaWindowTitleBar.closeButtonColors(context);

return material.Container(
height: 40,
height: context.scaled(40),
color: titleBarBackground(context),
child: WindowTitleBarBox(
child: Row(
Expand Down
4 changes: 4 additions & 0 deletions lib/features/main_screen/results_tab.dart
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,10 @@ import 'package:querya_desktop/shared/widgets/widgets.dart';

/// Query output: grid, loading, error, or placeholder.
///
/// Render order in [_buildBody]: **loading** first (only when [isLoading]),
/// then **error** when [errorMessage] is non-empty (including when
/// `isLoading` is false), then status / affected / idle / grid content.
///
/// Mode changes (idle / loading / error / status / grid) morph via
/// [QueryaFadeSlide]. Keys are per **mode**, not per row — so grid data updates
/// and scroll rebuilds do not re-trigger the transition.
Expand Down
26 changes: 25 additions & 1 deletion lib/features/mongodb/mongo_stats_view.dart
Original file line number Diff line number Diff line change
Expand Up @@ -249,7 +249,31 @@ class _MongoStatsViewState extends material.State<MongoStatsView> {
}

final status = _serverStatus;
if (status == null) return material.Container(color: cs.background);
if (status == null) {
return material.Center(
child: material.Padding(
padding: const material.EdgeInsets.all(32),
child: material.Column(
mainAxisSize: material.MainAxisSize.min,
children: [
material.Icon(material.Icons.error_outline_rounded,
size: 48, color: cs.destructive),
const Gap(16),
const Text('No stats available').large().semiBold(),
const Gap(8),
const Text('serverStatus returned no data.').muted().small(),
const Gap(24),
OutlineButton(
onPressed: _load,
leading: const material.Icon(material.Icons.refresh_rounded,
size: 18),
child: const Text('Retry'),
),
],
),
),
);
}

return material.Container(
color: cs.background,
Expand Down
18 changes: 16 additions & 2 deletions lib/features/mysql/mysql_sql_workspace.dart
Original file line number Diff line number Diff line change
Expand Up @@ -284,7 +284,14 @@ class _MysqlSqlWorkspaceState extends material.State<MysqlSqlWorkspace> {
text: text,
selection: material.TextSelection.collapsed(offset: text.length),
);
} catch (_) {}
} catch (e) {
if (!mounted) return;
showAppToast(
context: context,
message: 'Failed to open SQL file: $e',
variant: AppToastVariant.error,
);
}
}

Future<void> _saveSqlFile() async {
Expand All @@ -299,7 +306,14 @@ class _MysqlSqlWorkspaceState extends material.State<MysqlSqlWorkspace> {
final path = location?.path;
if (path == null || path.isEmpty) return;
await File(path).writeAsString(_sqlController.text);
} catch (_) {}
} catch (e) {
if (!mounted) return;
showAppToast(
context: context,
message: 'Failed to save SQL file: $e',
variant: AppToastVariant.error,
);
}
}

Future<void> _runTxCommand(String sql) async {
Expand Down
18 changes: 16 additions & 2 deletions lib/features/postgresql/postgres_sql_workspace.dart
Original file line number Diff line number Diff line change
Expand Up @@ -431,7 +431,14 @@ class _PostgresSqlWorkspaceState extends material.State<PostgresSqlWorkspace> {
text: text,
selection: material.TextSelection.collapsed(offset: text.length),
);
} catch (_) {}
} catch (e) {
if (!mounted) return;
showAppToast(
context: context,
message: 'Failed to open SQL file: $e',
variant: AppToastVariant.error,
);
}
}

Future<void> _saveSqlFile() async {
Expand All @@ -446,7 +453,14 @@ class _PostgresSqlWorkspaceState extends material.State<PostgresSqlWorkspace> {
final path = location?.path;
if (path == null || path.isEmpty) return;
await File(path).writeAsString(_sqlController.text);
} catch (_) {}
} catch (e) {
if (!mounted) return;
showAppToast(
context: context,
message: 'Failed to save SQL file: $e',
variant: AppToastVariant.error,
);
}
}

@override
Expand Down
12 changes: 12 additions & 0 deletions lib/features/redis/redis_key_editor.dart
Original file line number Diff line number Diff line change
Expand Up @@ -114,9 +114,11 @@ class _RedisKeyEditorState extends material.State<RedisKeyEditor> {
try {
await widget.connection.selectDatabase(widget.database);
await widget.connection.set(widget.keyName, _stringController.text);
if (!mounted) return;
setState(() => _success = 'Value saved');
_clearSuccessAfterDelay();
} catch (e) {
if (!mounted) return;
setState(() => _error = 'Save failed: $e');
}
}
Expand All @@ -127,6 +129,7 @@ class _RedisKeyEditorState extends material.State<RedisKeyEditor> {
await widget.connection.del(widget.keyName);
widget.onKeyDeleted?.call();
} catch (e) {
if (!mounted) return;
setState(() => _error = 'Delete failed: $e');
}
}
Expand All @@ -140,11 +143,13 @@ class _RedisKeyEditorState extends material.State<RedisKeyEditor> {
await widget.connection.persist(widget.keyName);
}
_ttl = await widget.connection.ttl(widget.keyName);
if (!mounted) return;
setState(() {
_success = seconds > 0 ? 'TTL set to $seconds seconds' : 'TTL removed';
});
_clearSuccessAfterDelay();
} catch (e) {
if (!mounted) return;
setState(() => _error = 'TTL failed: $e');
}
}
Expand All @@ -156,6 +161,7 @@ class _RedisKeyEditorState extends material.State<RedisKeyEditor> {
await widget.connection.hset(widget.keyName, field, value);
await _load();
} catch (e) {
if (!mounted) return;
setState(() => _error = 'HSET failed: $e');
}
}
Expand All @@ -166,6 +172,7 @@ class _RedisKeyEditorState extends material.State<RedisKeyEditor> {
await widget.connection.hdel(widget.keyName, field);
await _load();
} catch (e) {
if (!mounted) return;
setState(() => _error = 'HDEL failed: $e');
}
}
Expand All @@ -177,6 +184,7 @@ class _RedisKeyEditorState extends material.State<RedisKeyEditor> {
await widget.connection.rpush(widget.keyName, value);
await _load();
} catch (e) {
if (!mounted) return;
setState(() => _error = 'RPUSH failed: $e');
}
}
Expand All @@ -188,6 +196,7 @@ class _RedisKeyEditorState extends material.State<RedisKeyEditor> {
await widget.connection.sadd(widget.keyName, member);
await _load();
} catch (e) {
if (!mounted) return;
setState(() => _error = 'SADD failed: $e');
}
}
Expand All @@ -198,6 +207,7 @@ class _RedisKeyEditorState extends material.State<RedisKeyEditor> {
await widget.connection.srem(widget.keyName, member);
await _load();
} catch (e) {
if (!mounted) return;
setState(() => _error = 'SREM failed: $e');
}
}
Expand All @@ -209,6 +219,7 @@ class _RedisKeyEditorState extends material.State<RedisKeyEditor> {
await widget.connection.zadd(widget.keyName, score, member);
await _load();
} catch (e) {
if (!mounted) return;
setState(() => _error = 'ZADD failed: $e');
}
}
Expand All @@ -219,6 +230,7 @@ class _RedisKeyEditorState extends material.State<RedisKeyEditor> {
await widget.connection.zrem(widget.keyName, member);
await _load();
} catch (e) {
if (!mounted) return;
setState(() => _error = 'ZREM failed: $e');
}
}
Expand Down
6 changes: 5 additions & 1 deletion lib/features/redis/redis_keys_view.dart
Original file line number Diff line number Diff line change
Expand Up @@ -80,6 +80,7 @@ class _RedisKeysViewState extends material.State<RedisKeysView> {

// One pipelined burst of TYPE+TTL (not N× Future.wait round-trips).
List<_KeyInfo> infos;
String? typeTtlError;
try {
final metas = await widget.connection.typesAndTtls(keyNames);
infos = [
Expand All @@ -90,18 +91,21 @@ class _RedisKeysViewState extends material.State<RedisKeysView> {
ttl: metas[i].ttl,
),
];
} catch (_) {
} catch (e) {
// Still show keys with unknown type/TTL; surface the failure non-blocking.
infos = [
for (final name in keyNames)
_KeyInfo(name: name, type: 'unknown', ttl: -1),
];
typeTtlError = 'Failed to load key types/TTLs: $e';
}

if (!mounted) return;
setState(() {
_keys.addAll(infos);
_cursor = nextCursor;
_hasMore = nextCursor != 0;
if (typeTtlError != null) _error = typeTtlError;
});
}

Expand Down
26 changes: 25 additions & 1 deletion lib/features/redis/redis_view.dart
Original file line number Diff line number Diff line change
Expand Up @@ -226,7 +226,31 @@ class _RedisViewState extends material.State<RedisView> {
}

final info = _info;
if (info == null) return material.Container(color: cs.background);
if (info == null) {
return material.Center(
child: material.Padding(
padding: const material.EdgeInsets.all(32),
child: material.Column(
mainAxisSize: material.MainAxisSize.min,
children: [
material.Icon(material.Icons.error_outline_rounded,
size: 48, color: cs.destructive),
const Gap(16),
const Text('No stats available').large().semiBold(),
const Gap(8),
const Text('Redis INFO returned no data.').muted().small(),
const Gap(24),
OutlineButton(
onPressed: _load,
leading: const material.Icon(material.Icons.refresh_rounded,
size: 18),
child: const Text('Retry'),
),
],
),
),
);
}

return material.Container(
color: cs.background,
Expand Down
18 changes: 16 additions & 2 deletions lib/features/sqlite/sqlite_sql_workspace.dart
Original file line number Diff line number Diff line change
Expand Up @@ -240,7 +240,14 @@ class _SqliteSqlWorkspaceState extends material.State<SqliteSqlWorkspace> {
text: text,
selection: material.TextSelection.collapsed(offset: text.length),
);
} catch (_) {}
} catch (e) {
if (!mounted) return;
showAppToast(
context: context,
message: 'Failed to open SQL file: $e',
variant: AppToastVariant.error,
);
}
}

Future<void> _saveSqlFile() async {
Expand All @@ -255,7 +262,14 @@ class _SqliteSqlWorkspaceState extends material.State<SqliteSqlWorkspace> {
final path = location?.path;
if (path == null || path.isEmpty) return;
await File(path).writeAsString(_sqlController.text);
} catch (_) {}
} catch (e) {
if (!mounted) return;
showAppToast(
context: context,
message: 'Failed to save SQL file: $e',
variant: AppToastVariant.error,
);
}
}

@override
Expand Down
23 changes: 23 additions & 0 deletions test/features/main_screen/results_tab_test.dart
Original file line number Diff line number Diff line change
Expand Up @@ -237,6 +237,29 @@ void main() {
expect(find.text('created_at'), findsOneWidget);
});

testWidgets('shows error when isLoading is false', (tester) async {
await tester.pumpWidget(
resultsShell(
child: const material.Scaffold(
body: ResultsTab(
isLoading: false,
errorMessage: 'connection refused',
),
),
),
);
await tester.pumpAndSettle();
expect(
find.byKey(const material.ValueKey('results_mode_error')),
findsOneWidget,
);
expect(find.textContaining('connection refused'), findsOneWidget);
expect(
find.byKey(const material.ValueKey('results_mode_loading')),
findsNothing,
);
});

testWidgets('shows idle / loading / error / grid mode keys', (tester) async {
await tester.pumpWidget(
resultsShell(
Expand Down
Loading