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
5 changes: 5 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -23,6 +23,11 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
- **Demo Marketplace themes were named `${manifest.name}`** — the generated theme.json now carries the real name (the demo catalog is only used with `--dart-define=QUERYA_MARKETPLACE_MOCK=true`).
- **In-app update of a package install (AUR, `.deb`, `.rpm`)** — the updater no longer downloads a zip and quits when the app lives in `/opt/querya-desktop` (owned by root): the update dialog says how to update it (`yay -Syu querya-desktop`, `apt`, `dnf`) with a *Copy update command* button, or offers the release page for any other folder the user cannot write. If an in-place replace fails anyway, the app is started again and the reason is logged to `~/.local/state/querya-update.log`, instead of staying closed.

- **Preferences no longer flash a wrong state** — the MCP Server pane showed the server switch off, "No SQL connections" and "No calls yet" for a moment on every visit before the real values jumped in; it now draws them only once loaded. Panes you visited stay as they were (no reload, their own scroll position: Shortcuts no longer opens half-scrolled after another pane).
- **Data Grid preferences: removed "Confirm DML staging applications"** — it changed the SQL destructive-query confirmation (DROP / TRUNCATE / DELETE) and controlled nothing else.
- **About shows the real version** instead of a hardcoded 0.4.17.
- **MCP Server pane** — MongoDB connections can be shared (the server already had MongoDB tools), type badges use product names, the `querya-mcp` path is on its own line, copying configs and regenerating the token are separate, and recent calls are rows with a status icon.

### Planned

- Planned 0.5.0 — live Marketplace download and install
Expand Down
69 changes: 69 additions & 0 deletions benchmark/app_perf_bench.dart
Original file line number Diff line number Diff line change
Expand Up @@ -31,6 +31,7 @@ import 'package:querya_desktop/core/storage/local_db.dart';
import 'package:querya_desktop/core/theme/theme_controller.dart';
import 'package:querya_desktop/features/connections/connections_panel.dart';
import 'package:querya_desktop/features/erd/erd_view.dart';
import 'package:querya_desktop/features/settings/preferences_dialog.dart';
import 'package:sqflite_common_ffi/sqflite_ffi.dart';
import 'package:querya_desktop/features/workspace/result_grid_view.dart';
import 'package:vm_service/vm_service.dart' as vms;
Expand Down Expand Up @@ -108,6 +109,12 @@ Future<void> _runAll() async {
await _wait(1500);
await _scenario('idle_workspace', () => _wait(1500));

await _preferencesScenarios();
if (only.isNotEmpty &&
only.split(',').every((id) => id.trim().startsWith('prefs_'))) {
return; // nothing after this point was asked for
}

await _scenario('sidebar_toggle', () async {
for (var i = 0; i < 8; i++) {
(_findState((s) => s.runtimeType.toString() == '_MainContentSplitState')
Expand Down Expand Up @@ -268,6 +275,68 @@ Future<void> _runAll() async {
dynamic _workspace() =>
_findState((s) => s.runtimeType.toString() == 'GenericSqlWorkspaceState')!;

/// Preferences over the workspace: the dialog sits on a full-window frosted
/// backdrop, so these show what one repaint inside it costs.
Future<void> _preferencesScenarios() async {
Element dialog() => _find((e) =>
e.widget.runtimeType.toString() == '_PreferencesDialogContent')!;

await _scenario('prefs_open', () async {
showPreferencesDialog(_find((e) => e.widget is ConnectionsPanel)!);
await _waitFor(() => _findText('General Settings') != null, 'Preferences');
await _wait(600);
});
await _scenario('prefs_idle', () => _wait(1500));
await _scenario('prefs_hover', () => _hoverOver(dialog(), 3000));
// One scenario per category: switching to it builds its pane from scratch.
for (final label in const [
'Appearance', 'SQL & Editor', 'Data Grid', 'Extensions',
'MCP Server', 'Shortcuts', 'About & Storage', 'General',
]) {
final id = 'prefs_cat_${label.split(' ').first.toLowerCase()}';
await _scenario(id, () => _tap(_byText(label)));
}
await _scenario('prefs_reopen', () async {
await _tap(_byText('Close'));
await _wait(500);
showPreferencesDialog(_find((e) => e.widget is ConnectionsPanel)!);
await _waitFor(() => _findText('General Settings') != null, 'Preferences');
await _wait(600);
});
await _tap(_byText('Shortcuts'));
await _scenario('prefs_scroll', () => _scrollLoop(
dialog, Axis.vertical, 10, 3000, minExtent: 100));
await _scenario('prefs_typing', () async {
final search = _findState((s) =>
s is EditableTextState &&
s.widget.controller.runtimeType == TextEditingController) as EditableTextState;
var text = '';
for (final ch in 'mcp server token'.split('')) {
text += ch;
search.updateEditingValue(TextEditingValue(
text: text,
selection: TextSelection.collapsed(offset: text.length),
));
await _wait(90);
}
search.updateEditingValue(TextEditingValue.empty);
await _wait(300);
});
await _tap(_byText('MCP Server'));
await _scenario('prefs_toggle', () async {
final share = _find((e) => '${e.widget.key}'.contains('mcp_share_'));
if (share == null) {
stdout.writeln('BENCH skip: no shareable connection');
return;
}
for (var i = 0; i < 6; i++) {
await _tap(share);
}
});
await _tap(_byText('Close'));
await _wait(600);
}

/// Puts [sql] into the active query tab, runs it and waits for the run to end.
Future<void> _runInActiveTab(String sql) async {
final ws = _workspace();
Expand Down
33 changes: 27 additions & 6 deletions lib/features/settings/preferences_about_section.dart
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
import 'package:flutter/material.dart' as material;
import 'package:package_info_plus/package_info_plus.dart';
import 'package:querya_desktop/core/extensions/extension_paths.dart';
import 'package:querya_desktop/core/platform/open_directory.dart';
import 'package:querya_desktop/core/storage/app_data_root.dart';
Expand All @@ -8,13 +9,28 @@ import 'package:querya_desktop/shared/widgets/widgets.dart';
import 'package:url_launcher/url_launcher_string.dart';
import 'package:querya_desktop/core/ui/querya_control_tokens.dart';

class PreferencesAboutSection extends StatelessWidget {
class PreferencesAboutSection extends material.StatefulWidget {
const PreferencesAboutSection({super.key});

static const String appVersion = '0.4.17';
static const String githubRepoUrl =
'https://github.com/QueryaHub/Querya-Desktop';

@override
material.State<PreferencesAboutSection> createState() =>
_PreferencesAboutSectionState();
}

class _PreferencesAboutSectionState
extends material.State<PreferencesAboutSection> {
// Created once: a future made in build() would restart on every rebuild of
// the dialog (every keystroke in its search) and make these rows flicker.
late final Future<String> _version = PackageInfo.fromPlatform()
.then((i) => i.version)
.catchError((Object _) => '');
late final Future<bool> _portable = AppDataRoot.isPortableMode();

static const githubRepoUrl = PreferencesAboutSection.githubRepoUrl;

@override
Widget build(BuildContext context) {
final theme = Theme.of(context);
Expand Down Expand Up @@ -68,9 +84,14 @@ class PreferencesAboutSection extends StatelessWidget {
.large()
.foreground(),
const material.SizedBox(height: 2),
const Text('Version $appVersion (Desktop Build)')
.muted()
.small(),
FutureBuilder<String>(
future: _version,
builder: (context, snapshot) => Text(
snapshot.data?.isNotEmpty == true
? 'Version ${snapshot.data} (Desktop Build)'
: 'Desktop Build',
).muted().small(),
),
const material.SizedBox(height: 4),
const Text('Fast, native multi-database management studio.')
.muted()
Expand All @@ -91,7 +112,7 @@ class PreferencesAboutSection extends StatelessWidget {
const material.SizedBox(height: 12),

FutureBuilder<bool>(
future: AppDataRoot.isPortableMode(),
future: _portable,
builder: (context, snapshot) {
final isPortable = snapshot.data ?? false;
if (!isPortable) return const SizedBox.shrink();
Expand Down
Loading
Loading