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
122 changes: 122 additions & 0 deletions lib/features/connections/forms/connection_auth_section.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/features/connections/remove_saved_password_option.dart';
import 'package:querya_desktop/shared/widgets/widgets.dart';

/// Reusable Username & Password input section for database connection forms (#1364).
class ConnectionAuthSection extends material.StatefulWidget {
const ConnectionAuthSection({
super.key,
required this.usernameController,
required this.passwordController,
this.usernamePlaceholder = 'postgres',
this.usernameLabel = 'Username',
this.passwordLabel = 'Password',
this.isEditing = false,
this.removeSavedPassword = false,
this.onRemoveSavedPasswordChanged,
this.useRowLayout = true,
});

final material.TextEditingController usernameController;
final material.TextEditingController passwordController;
final String usernamePlaceholder;
final String usernameLabel;
final String passwordLabel;
final bool isEditing;
final bool removeSavedPassword;
final material.ValueChanged<bool>? onRemoveSavedPasswordChanged;
final bool useRowLayout;

@override
material.State<ConnectionAuthSection> createState() =>
_ConnectionAuthSectionState();
}

class _ConnectionAuthSectionState extends material.State<ConnectionAuthSection> {
bool _showPassword = false;

material.Widget _buildUsernameField() {
return material.Column(
crossAxisAlignment: material.CrossAxisAlignment.stretch,
mainAxisSize: material.MainAxisSize.min,
children: [
Text(widget.usernameLabel).small().semiBold(),
const Gap(8),
TextField(
controller: widget.usernameController,
placeholder: Text(widget.usernamePlaceholder),
),
],
);
}

material.Widget _buildPasswordField() {
return material.Column(
crossAxisAlignment: material.CrossAxisAlignment.stretch,
mainAxisSize: material.MainAxisSize.min,
children: [
Text(widget.passwordLabel).small().semiBold(),
if (widget.isEditing && widget.onRemoveSavedPasswordChanged != null)
RemoveSavedPasswordOption(
value: widget.removeSavedPassword,
onChanged: widget.onRemoveSavedPasswordChanged!,
),
const Gap(8),
material.Stack(
children: [
TextField(
controller: widget.passwordController,
enabled: !widget.removeSavedPassword,
placeholder: Text(
widget.isEditing ? 'Leave blank to keep existing' : 'Password',
),
obscureText: !_showPassword,
),
material.Positioned(
right: 8,
top: 0,
bottom: 0,
child: material.Center(
child: material.IconButton(
icon: material.Icon(
_showPassword
? material.Icons.visibility_off
: material.Icons.visibility,
size: 20,
),
onPressed: () => setState(() => _showPassword = !_showPassword),
padding: material.EdgeInsets.zero,
constraints: const material.BoxConstraints(),
),
),
),
],
),
],
);
}

@override
material.Widget build(material.BuildContext context) {
if (widget.useRowLayout) {
return material.Row(
crossAxisAlignment: material.CrossAxisAlignment.start,
children: [
material.Expanded(child: _buildUsernameField()),
const Gap(16),
material.Expanded(child: _buildPasswordField()),
],
);
}

return material.Column(
crossAxisAlignment: material.CrossAxisAlignment.stretch,
mainAxisSize: material.MainAxisSize.min,
children: [
_buildUsernameField(),
const Gap(16),
_buildPasswordField(),
],
);
}
}
38 changes: 38 additions & 0 deletions lib/features/connections/forms/connection_database_section.dart
Original file line number Diff line number Diff line change
@@ -0,0 +1,38 @@
import 'package:flutter/material.dart' as material;
import 'package:querya_desktop/shared/widgets/widgets.dart';

/// Reusable Database name input field for connection forms (#1364).
class ConnectionDatabaseSection extends StatelessWidget {
const ConnectionDatabaseSection({
super.key,
required this.databaseController,
this.label = 'Database',
this.placeholder = 'postgres',
this.hint,
});

final material.TextEditingController databaseController;
final String label;
final String placeholder;
final String? hint;

@override
Widget build(BuildContext context) {
return material.Column(
crossAxisAlignment: material.CrossAxisAlignment.stretch,
mainAxisSize: material.MainAxisSize.min,
children: [
Text(label).small().semiBold(),
if (hint != null) ...[
const Gap(4),
Text(hint!).muted().small(),
],
const Gap(8),
TextField(
controller: databaseController,
placeholder: Text(placeholder),
),
],
);
}
}
68 changes: 68 additions & 0 deletions lib/features/connections/forms/connection_host_port_section.dart
Original file line number Diff line number Diff line change
@@ -0,0 +1,68 @@
import 'package:flutter/material.dart' as material;
import 'package:querya_desktop/shared/widgets/widgets.dart';

/// Reusable Host & Port input row for database connection forms (#1364).
class ConnectionHostPortSection extends StatelessWidget {
const ConnectionHostPortSection({
super.key,
required this.hostController,
required this.portController,
this.hostPlaceholder = 'localhost',
this.portPlaceholder = '5432',
this.hostLabel = 'Host',
this.portLabel = 'Port',
this.hostFlex = 3,
this.portFlex = 2,
this.gap = 16,
});

final material.TextEditingController hostController;
final material.TextEditingController portController;
final String hostPlaceholder;
final String portPlaceholder;
final String hostLabel;
final String portLabel;
final int hostFlex;
final int portFlex;
final double gap;

@override
Widget build(BuildContext context) {
return material.Row(
crossAxisAlignment: material.CrossAxisAlignment.start,
children: [
material.Expanded(
flex: hostFlex,
child: material.Column(
crossAxisAlignment: material.CrossAxisAlignment.stretch,
mainAxisSize: material.MainAxisSize.min,
children: [
Text(hostLabel).small().semiBold(),
const Gap(8),
TextField(
controller: hostController,
placeholder: Text(hostPlaceholder),
),
],
),
),
Gap(gap),
material.Expanded(
flex: portFlex,
child: material.Column(
crossAxisAlignment: material.CrossAxisAlignment.stretch,
mainAxisSize: material.MainAxisSize.min,
children: [
Text(portLabel).small().semiBold(),
const Gap(8),
TextField(
controller: portController,
placeholder: Text(portPlaceholder),
),
],
),
),
],
);
}
}
103 changes: 18 additions & 85 deletions lib/features/mongodb/mongodb_connection_form.dart
Original file line number Diff line number Diff line change
Expand Up @@ -9,9 +9,10 @@ import 'package:querya_desktop/core/database/mongodb_connection.dart';
import 'package:querya_desktop/core/layout/window_layout.dart';
import 'package:querya_desktop/core/security/ssh_tunnel_config.dart';
import 'package:querya_desktop/core/storage/local_db.dart';
import 'package:querya_desktop/features/connections/remove_saved_password_option.dart';
import 'package:querya_desktop/features/connections/connection_creation_flow.dart';
import 'package:querya_desktop/features/connections/ssh_tunnel_section.dart';
import 'package:querya_desktop/features/connections/forms/connection_auth_section.dart';
import 'package:querya_desktop/features/connections/forms/connection_host_port_section.dart';
import 'package:querya_desktop/features/connections/ssl_certificate_support.dart';
import 'package:querya_desktop/shared/widgets/form_validity_notifier.dart';
import 'package:querya_desktop/shared/widgets/ssl_certificate_fields.dart';
Expand Down Expand Up @@ -101,7 +102,6 @@ class _MongoConnectionFormContentState
bool _useConnectionString = false;
bool _useSSL = false;
bool _removeSavedPassword = false;
bool _showPassword = false;
bool _isTesting = false;
String? _testResult;
Timer? _dismissTimer;
Expand Down Expand Up @@ -472,92 +472,25 @@ class _MongoConnectionFormContentState
),
const Gap(16),
// Host and Port
material.Row(
children: [
material.Expanded(
flex: 3,
child: material.Column(
crossAxisAlignment:
material.CrossAxisAlignment.stretch,
mainAxisSize: material.MainAxisSize.min,
children: [
const Text('Host').small().semiBold(),
const Gap(8),
TextField(
controller: _hostController,
placeholder: const Text('localhost'),
),
],
),
),
const Gap(12),
material.Expanded(
flex: 1,
child: material.Column(
crossAxisAlignment:
material.CrossAxisAlignment.stretch,
mainAxisSize: material.MainAxisSize.min,
children: [
const Text('Port').small().semiBold(),
const Gap(8),
TextField(
controller: _portController,
placeholder: const Text('27017'),
),
],
),
),
],
ConnectionHostPortSection(
hostController: _hostController,
portController: _portController,
portPlaceholder: '27017',
portFlex: 1,
gap: 12,
),
const Gap(16),
// Authentication
const Text('Authentication (Optional)')
.small()
.semiBold(),
const Gap(8),
TextField(
controller: _usernameController,
placeholder: const Text('Username'),
),
const Gap(12),
if (_isEditing)
RemoveSavedPasswordOption(
value: _removeSavedPassword,
onChanged: (v) =>
setState(() => _removeSavedPassword = v),
),
material.Stack(
children: [
TextField(
controller: _passwordController,
enabled: !_removeSavedPassword,
placeholder: Text(
_isEditing
? 'Leave blank to keep existing'
: 'Password',
),
obscureText: !_showPassword,
),
material.Positioned(
right: 8,
top: 0,
bottom: 0,
child: material.Center(
child: material.IconButton(
icon: material.Icon(
_showPassword
? material.Icons.visibility_off
: material.Icons.visibility,
size: 20,
),
onPressed: () => setState(
() => _showPassword = !_showPassword),
padding: material.EdgeInsets.zero,
constraints: const material.BoxConstraints(),
),
),
),
],
ConnectionAuthSection(
usernameController: _usernameController,
passwordController: _passwordController,
usernameLabel: 'Authentication (Optional)',
usernamePlaceholder: 'Username',
isEditing: _isEditing,
removeSavedPassword: _removeSavedPassword,
onRemoveSavedPasswordChanged: (v) =>
setState(() => _removeSavedPassword = v),
useRowLayout: false,
),
const Gap(16),
// Database and Auth Source
Expand Down
Loading
Loading