Skip to content
Open
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
Original file line number Diff line number Diff line change
@@ -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';

Expand All @@ -24,6 +27,30 @@ class _ExtensionManagerContent extends material.StatefulWidget {
class _ExtensionManagerContentState extends material.State<_ExtensionManagerContent> {
int _tabIndex = 0;

final List<ExtensionManifest> _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<ExtensionManifest> _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;
Expand Down Expand Up @@ -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.'));
}
}
122 changes: 122 additions & 0 deletions lib/features/extensions/presentation/widgets/extension_card.dart
Original file line number Diff line number Diff line change
@@ -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.card,
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,
),
);
}
}
Loading