From a73e6c9564c99ac60219bd47ba9d3ff5513348ed Mon Sep 17 00:00:00 2001 From: ZhuchkaTriplesix Date: Wed, 24 Jun 2026 15:33:09 +0300 Subject: [PATCH 1/2] feat(ui): add Extension Card Component UI-EXT-2 --- .../pages/extension_manager_dialog.dart | 51 +++++++- .../presentation/widgets/extension_card.dart | 122 ++++++++++++++++++ 2 files changed, 170 insertions(+), 3 deletions(-) create mode 100644 lib/features/extensions/presentation/widgets/extension_card.dart diff --git a/lib/features/extensions/presentation/pages/extension_manager_dialog.dart b/lib/features/extensions/presentation/pages/extension_manager_dialog.dart index 8c87346..323847c 100644 --- a/lib/features/extensions/presentation/pages/extension_manager_dialog.dart +++ b/lib/features/extensions/presentation/pages/extension_manager_dialog.dart @@ -1,4 +1,7 @@ import 'package:flutter/material.dart' as material; +import 'package:querya_desktop/core/extensions/models/extension_manifest.dart'; +import 'package:querya_desktop/core/extensions/models/extension_type.dart'; +import 'package:querya_desktop/features/extensions/presentation/widgets/extension_card.dart'; import 'package:querya_desktop/core/layout/window_layout.dart'; import 'package:querya_desktop/shared/widgets/widgets.dart'; @@ -24,6 +27,30 @@ class _ExtensionManagerContent extends material.StatefulWidget { class _ExtensionManagerContentState extends material.State<_ExtensionManagerContent> { int _tabIndex = 0; + final List _installedMocks = [ + const ExtensionManifest( + id: 'queryahub.clickhouse-driver', + name: 'ClickHouse Driver', + version: '1.0.0', + publisher: 'QueryaHub', + type: ExtensionType.databaseDriver, + engines: {'querya_desktop': '^0.4.7'}, + description: 'Full support for ClickHouse databases including Dictionaries and Materialized Views.', + ), + ]; + + final List _marketMocks = [ + const ExtensionManifest( + id: 'community.redis-driver', + name: 'Redis Driver', + version: '0.9.5', + publisher: 'Community', + type: ExtensionType.databaseDriver, + engines: {'querya_desktop': '^0.4.7'}, + description: 'Connect to Redis instances and visualize key-value storage.', + ), + ]; + @override material.Widget build(material.BuildContext context) { final theme = Theme.of(context).colorScheme; @@ -120,14 +147,32 @@ class _ExtensionManagerContentState extends material.State<_ExtensionManagerCont } material.Widget _buildInstalledTab() { - return const material.Center(child: Text('Installed extensions will appear here.')); + return material.ListView.separated( + padding: const material.EdgeInsets.all(24), + itemCount: _installedMocks.length, + separatorBuilder: (_, __) => const material.SizedBox(height: 16), + itemBuilder: (ctx, i) => ExtensionCard( + manifest: _installedMocks[i], + isInstalled: true, + onUninstall: () {}, + ), + ); } material.Widget _buildMarketplaceTab() { - return const material.Center(child: Text('Marketplace extensions will appear here.')); + return material.ListView.separated( + padding: const material.EdgeInsets.all(24), + itemCount: _marketMocks.length, + separatorBuilder: (_, __) => const material.SizedBox(height: 16), + itemBuilder: (ctx, i) => ExtensionCard( + manifest: _marketMocks[i], + isInstalled: false, + onInstall: () {}, + ), + ); } material.Widget _buildUpdatesTab() { - return const material.Center(child: Text('Extension updates will appear here.')); + return const material.Center(child: material.Text('No updates available.')); } } diff --git a/lib/features/extensions/presentation/widgets/extension_card.dart b/lib/features/extensions/presentation/widgets/extension_card.dart new file mode 100644 index 0000000..58e2f29 --- /dev/null +++ b/lib/features/extensions/presentation/widgets/extension_card.dart @@ -0,0 +1,122 @@ +import 'package:flutter/material.dart' as material; +import 'package:querya_desktop/core/extensions/models/extension_manifest.dart'; +import 'package:querya_desktop/shared/widgets/widgets.dart'; + +class ExtensionCard extends material.StatelessWidget { + const ExtensionCard({ + super.key, + required this.manifest, + required this.isInstalled, + this.hasUpdate = false, + this.onInstall, + this.onUninstall, + this.onUpdate, + }); + + final ExtensionManifest manifest; + final bool isInstalled; + final bool hasUpdate; + final material.VoidCallback? onInstall; + final material.VoidCallback? onUninstall; + final material.VoidCallback? onUpdate; + + @override + material.Widget build(material.BuildContext context) { + final theme = Theme.of(context).colorScheme; + final radius = Theme.of(context).radiusMd; + + return material.Container( + padding: const material.EdgeInsets.all(16), + decoration: material.BoxDecoration( + color: theme.surface, + border: material.Border.all(color: theme.border), + borderRadius: material.BorderRadius.circular(radius), + ), + child: material.Row( + crossAxisAlignment: material.CrossAxisAlignment.start, + children: [ + _buildIcon(theme, radius), + const material.SizedBox(width: 16), + material.Expanded( + child: material.Column( + crossAxisAlignment: material.CrossAxisAlignment.start, + children: [ + material.Row( + children: [ + material.Expanded( + child: Text(manifest.name).large().semiBold(), + ), + ], + ), + const material.SizedBox(height: 4), + material.Row( + children: [ + Text(manifest.publisher).muted().small(), + const material.SizedBox(width: 8), + material.Container( + width: 4, + height: 4, + decoration: material.BoxDecoration( + color: theme.mutedForeground, + shape: material.BoxShape.circle, + ), + ), + const material.SizedBox(width: 8), + Text('v${manifest.version}').muted().small(), + ], + ), + const material.SizedBox(height: 8), + Text( + manifest.description ?? 'No description provided.', + maxLines: 2, + overflow: material.TextOverflow.ellipsis, + ).muted(), + ], + ), + ), + const material.SizedBox(width: 16), + material.Column( + mainAxisAlignment: material.MainAxisAlignment.center, + crossAxisAlignment: material.CrossAxisAlignment.end, + children: [ + if (isInstalled && hasUpdate) + material.Padding( + padding: const material.EdgeInsets.only(bottom: 8.0), + child: PrimaryButton( + onPressed: onUpdate, + child: const Text('Update'), + ), + ), + if (isInstalled) + SecondaryButton( + onPressed: onUninstall, + child: const Text('Uninstall'), + ), + if (!isInstalled) + PrimaryButton( + onPressed: onInstall, + child: const Text('Install'), + ), + ], + ), + ], + ), + ); + } + + material.Widget _buildIcon(ColorScheme theme, double radius) { + return material.Container( + width: 48, + height: 48, + decoration: material.BoxDecoration( + color: theme.muted, + borderRadius: material.BorderRadius.circular(radius), + ), + child: material.Icon( + material.Icons.extension_rounded, + size: 24, + color: theme.mutedForeground, + ), + ); + } +} From fa8866cd4bcc616f7cc714e931eb328773379672 Mon Sep 17 00:00:00 2001 From: ZhuchkaTriplesix Date: Wed, 24 Jun 2026 15:36:58 +0300 Subject: [PATCH 2/2] fix(ui): use theme.card instead of theme.surface in extension card --- .../extensions/presentation/widgets/extension_card.dart | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/lib/features/extensions/presentation/widgets/extension_card.dart b/lib/features/extensions/presentation/widgets/extension_card.dart index 58e2f29..5f5fc45 100644 --- a/lib/features/extensions/presentation/widgets/extension_card.dart +++ b/lib/features/extensions/presentation/widgets/extension_card.dart @@ -28,7 +28,7 @@ class ExtensionCard extends material.StatelessWidget { return material.Container( padding: const material.EdgeInsets.all(16), decoration: material.BoxDecoration( - color: theme.surface, + color: theme.card, border: material.Border.all(color: theme.border), borderRadius: material.BorderRadius.circular(radius), ),