new settings design

master
gitea 2 months ago
parent 2db3339071
commit ffd1ac5ddc

@ -28,6 +28,7 @@ class RelayManagementScreen extends StatefulWidget {
class _RelayManagementScreenState extends State<RelayManagementScreen> { class _RelayManagementScreenState extends State<RelayManagementScreen> {
final TextEditingController _urlController = TextEditingController(); final TextEditingController _urlController = TextEditingController();
final FocusNode _urlFocusNode = FocusNode();
bool _useNip05RelaysAutomatically = false; bool _useNip05RelaysAutomatically = false;
bool _isDarkMode = false; bool _isDarkMode = false;
bool _isLoadingSetting = true; bool _isLoadingSetting = true;
@ -37,8 +38,6 @@ class _RelayManagementScreenState extends State<RelayManagementScreen> {
MultiMediaService? _multiMediaService; MultiMediaService? _multiMediaService;
List<MediaServerConfig> _mediaServers = []; List<MediaServerConfig> _mediaServers = [];
MediaServerConfig? _defaultMediaServer; MediaServerConfig? _defaultMediaServer;
bool _mediaServersExpanded = false;
bool _settingsExpanded = true; // Settings expanded by default
// Store original values to detect changes // Store original values to detect changes
List<MediaServerConfig> _originalMediaServers = []; List<MediaServerConfig> _originalMediaServers = [];
@ -51,6 +50,16 @@ class _RelayManagementScreenState extends State<RelayManagementScreen> {
super.initState(); super.initState();
_initializeMultiMediaService(); _initializeMultiMediaService();
_loadSetting(); _loadSetting();
_urlFocusNode.addListener(_onUrlFocusChanged);
}
void _onUrlFocusChanged() {
if (_urlFocusNode.hasFocus && _urlController.text.isEmpty) {
_urlController.text = 'wss://';
_urlController.selection = TextSelection.fromPosition(
TextPosition(offset: _urlController.text.length),
);
}
} }
Future<void> _initializeMultiMediaService() async { Future<void> _initializeMultiMediaService() async {
@ -71,7 +80,6 @@ class _RelayManagementScreenState extends State<RelayManagementScreen> {
_defaultMediaServer = _multiMediaService!.getDefaultServer(); _defaultMediaServer = _multiMediaService!.getDefaultServer();
_originalMediaServers = List.from(_mediaServers); _originalMediaServers = List.from(_mediaServers);
_originalDefaultServerId = _defaultMediaServer?.id; _originalDefaultServerId = _defaultMediaServer?.id;
_mediaServersExpanded = _mediaServers.isNotEmpty; // Auto-expand if servers exist
}); });
} }
} }
@ -133,6 +141,8 @@ class _RelayManagementScreenState extends State<RelayManagementScreen> {
@override @override
void dispose() { void dispose() {
_urlController.dispose(); _urlController.dispose();
_urlFocusNode.removeListener(_onUrlFocusChanged);
_urlFocusNode.dispose();
super.dispose(); super.dispose();
} }
@ -399,7 +409,7 @@ class _RelayManagementScreenState extends State<RelayManagementScreen> {
(s) => s.isDefault, (s) => s.isDefault,
orElse: () => updatedServers.isNotEmpty ? updatedServers.first : result, orElse: () => updatedServers.isNotEmpty ? updatedServers.first : result,
); );
_mediaServersExpanded = true; // Expand to show the newly added server // Server added successfully
}); });
Logger.info('Media server added. Total servers: ${_mediaServers.length}, Default: ${_defaultMediaServer?.baseUrl}'); Logger.info('Media server added. Total servers: ${_mediaServers.length}, Default: ${_defaultMediaServer?.baseUrl}');
@ -495,274 +505,142 @@ class _RelayManagementScreenState extends State<RelayManagementScreen> {
@override @override
Widget build(BuildContext context) { Widget build(BuildContext context) {
final theme = Theme.of(context);
return Scaffold( return Scaffold(
appBar: AppBar( appBar: AppBar(
title: const Text('Advanced Settings'), title: const Text('Advanced Settings'),
actions: _hasUnsavedChanges
? [
TextButton.icon(
onPressed: _saveAllSettings,
icon: const Icon(Icons.save, size: 18),
label: const Text('Save'),
style: TextButton.styleFrom(
foregroundColor: Colors.green,
),
),
]
: null,
), ),
body: ListenableBuilder( body: ListenableBuilder(
listenable: widget.controller, listenable: widget.controller,
builder: (context, child) { builder: (context, child) {
return SingleChildScrollView( return SingleChildScrollView(
child: Column(
children: [
// Settings section - Now using ExpansionTile for uniformity
ExpansionTile(
title: const Text('Settings'),
subtitle: _hasUnsavedChanges
? const Text('You have unsaved changes', style: TextStyle(color: Colors.orange))
: Text('${_useNip05RelaysAutomatically ? "NIP-05" : "Manual"} relays • ${_isDarkMode ? "Dark" : "Light"} mode'),
initiallyExpanded: _settingsExpanded,
trailing: _hasUnsavedChanges
? ElevatedButton.icon(
onPressed: _saveAllSettings,
icon: const Icon(Icons.save, size: 18),
label: const Text('Save'),
style: ElevatedButton.styleFrom(
backgroundColor: Colors.green,
foregroundColor: Colors.white,
padding: const EdgeInsets.symmetric(horizontal: 12, vertical: 8), padding: const EdgeInsets.symmetric(horizontal: 12, vertical: 8),
),
)
: null,
onExpansionChanged: (expanded) {
setState(() {
_settingsExpanded = expanded;
});
},
children: [
Padding(
padding: const EdgeInsets.all(16),
child: Column( child: Column(
crossAxisAlignment: CrossAxisAlignment.start, crossAxisAlignment: CrossAxisAlignment.stretch,
children: [
if (_isLoadingSetting)
const Row(
children: [ children: [
SizedBox( // Preferences Section
width: 16, _buildSectionCard(
height: 16, context: context,
child: CircularProgressIndicator(strokeWidth: 2), title: 'Preferences',
), icon: Icons.settings_outlined,
SizedBox(width: 12), child: _isLoadingSetting
Text('Loading settings...'), ? const Padding(
], padding: EdgeInsets.symmetric(vertical: 8),
child: Center(child: CircularProgressIndicator(strokeWidth: 2)),
) )
else ...[ : Column(
SwitchListTile( children: [
title: const Text('Use NIP-05 relays automatically'), _buildSettingTile(
subtitle: const Text( context: context,
'Automatically replace relays with NIP-05 preferred relays upon login', title: 'Dark Mode',
style: TextStyle(fontSize: 12), subtitle: 'Enable dark theme for the app',
),
value: _useNip05RelaysAutomatically,
onChanged: (value) {
_saveSetting('use_nip05_relays_automatically', value);
},
contentPadding: EdgeInsets.zero,
),
const SizedBox(height: 8),
SwitchListTile(
title: const Text('Dark Mode'),
subtitle: const Text(
'Enable dark theme for the app',
style: TextStyle(fontSize: 12),
),
value: _isDarkMode, value: _isDarkMode,
onChanged: (value) { onChanged: (value) {
_saveSetting('dark_mode', value); _saveSetting('dark_mode', value);
}, },
contentPadding: EdgeInsets.zero, icon: Icons.dark_mode_outlined,
), ),
const SizedBox(height: 16), ],
// Media Server Settings - Multiple Servers ),
),
const SizedBox(height: 8),
// Media Server Section
Builder( Builder(
builder: (context) { builder: (context) {
final immichEnabled = dotenv.env['IMMICH_ENABLE']?.toLowerCase() != 'false'; final immichEnabled = dotenv.env['IMMICH_ENABLE']?.toLowerCase() != 'false';
final defaultBlossomServer = dotenv.env['BLOSSOM_SERVER'] ?? 'https://media.based21.com'; final defaultBlossomServer = dotenv.env['BLOSSOM_SERVER'] ?? 'https://media.based21.com';
return ExpansionTile( return _buildSectionCard(
key: ValueKey('media-servers-${_mediaServers.length}-${_mediaServersExpanded}'), context: context,
title: const Text('Media Servers'), title: 'Media Server',
subtitle: Text(_mediaServers.isEmpty icon: Icons.storage_outlined,
? 'No servers configured' child: Column(
: '${_mediaServers.length} server(s)${_defaultMediaServer != null ? " • Default: ${_defaultMediaServer!.name ?? _defaultMediaServer!.type}" : ""}'), crossAxisAlignment: CrossAxisAlignment.stretch,
initiallyExpanded: _mediaServersExpanded,
onExpansionChanged: (expanded) {
setState(() {
_mediaServersExpanded = expanded;
});
},
children: [ children: [
// Server list if (_defaultMediaServer != null)
if (_mediaServers.isEmpty) _buildMediaServerTile(
Padding( context: context,
padding: const EdgeInsets.all(16.0), server: _defaultMediaServer!,
child: Text( isDefault: true,
'No media servers configured. Add one to get started.', onEdit: () => _editMediaServer(_defaultMediaServer!),
style: Theme.of(context).textTheme.bodyMedium?.copyWith( onRemove: () => _removeMediaServer(_defaultMediaServer!),
color: Colors.grey,
),
),
) )
else else
...List.generate(_mediaServers.length, (index) {
final server = _mediaServers[index];
return ListTile(
key: ValueKey(server.id),
leading: Icon(
server.isDefault ? Icons.star : Icons.star_border,
color: server.isDefault ? Colors.amber : Colors.grey,
),
title: Text(server.name ?? '${server.type.toUpperCase()} Server'),
subtitle: Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
Text(server.baseUrl),
if (server.isDefault)
Padding( Padding(
padding: const EdgeInsets.only(top: 4.0), padding: const EdgeInsets.symmetric(vertical: 4),
child: Chip( child: Text(
label: const Text('Default'), 'No media server configured',
labelStyle: const TextStyle(fontSize: 10), style: theme.textTheme.bodySmall?.copyWith(
padding: EdgeInsets.zero, color: Colors.grey,
), fontSize: 12,
),
],
),
trailing: Row(
mainAxisSize: MainAxisSize.min,
children: [
IconButton(
icon: const Icon(Icons.edit),
onPressed: () => _editMediaServer(server),
), ),
IconButton(
icon: const Icon(Icons.delete),
onPressed: () => _removeMediaServer(server),
), ),
],
), ),
onTap: () => _setDefaultServer(server), const SizedBox(height: 8),
); OutlinedButton.icon(
}),
// Add server button
Padding(
padding: const EdgeInsets.all(16.0),
child: ElevatedButton.icon(
onPressed: () => _addMediaServer(immichEnabled, defaultBlossomServer), onPressed: () => _addMediaServer(immichEnabled, defaultBlossomServer),
icon: const Icon(Icons.add), icon: const Icon(Icons.add, size: 16),
label: const Text('Add Media Server'), label: const Text('Add More', style: TextStyle(fontSize: 13)),
style: OutlinedButton.styleFrom(
padding: const EdgeInsets.symmetric(vertical: 8, horizontal: 12),
minimumSize: const Size(0, 36),
), ),
), ),
], ],
),
); );
}, },
), ),
], const SizedBox(height: 8),
],
),
),
],
),
// Error message
if (widget.controller.error != null)
Container(
width: double.infinity,
padding: const EdgeInsets.all(16),
color: Colors.red.shade100,
child: Row(
children: [
Icon(Icons.error, color: Colors.red.shade700),
const SizedBox(width: 8),
Expanded(
child: Text(
widget.controller.error!,
style: TextStyle(color: Colors.red.shade700),
),
),
IconButton(
icon: const Icon(Icons.close),
onPressed: widget.controller.clearError,
color: Colors.red.shade700,
),
],
),
),
// Relay Management Section (Expandable) // Relays Section
ExpansionTile( _buildSectionCard(
title: const Text('Nostr Relays'), context: context,
subtitle: Text('${widget.controller.relays.length} relay(s) configured'), title: 'Relays',
initiallyExpanded: false, icon: Icons.cloud_outlined,
children: [
Padding(
padding: const EdgeInsets.all(16),
child: Column( child: Column(
crossAxisAlignment: CrossAxisAlignment.stretch, crossAxisAlignment: CrossAxisAlignment.stretch,
children: [ children: [
// Test All and Toggle All buttons
Row(
children: [
Expanded(
child: ElevatedButton.icon(
onPressed: widget.controller.isCheckingHealth
? null
: widget.controller.checkRelayHealth,
icon: widget.controller.isCheckingHealth
? const SizedBox(
width: 16,
height: 16,
child: CircularProgressIndicator(strokeWidth: 2),
)
: const Icon(Icons.network_check),
label: const Text('Test All'),
),
),
const SizedBox(width: 8),
Expanded(
child: ElevatedButton.icon(
onPressed: widget.controller.relays.isEmpty
? null
: () async {
final hasEnabled = widget.controller.relays.any((r) => r.isEnabled);
if (hasEnabled) {
await widget.controller.turnAllOff();
} else {
await widget.controller.turnAllOn();
}
},
icon: const Icon(Icons.power_settings_new),
label: Text(
widget.controller.relays.isNotEmpty &&
widget.controller.relays.any((r) => r.isEnabled)
? 'Turn All Off'
: 'Turn All On',
),
),
),
],
),
const SizedBox(height: 16),
// Add relay input // Add relay input
Row( TextField(
children: [
Expanded(
child: TextField(
controller: _urlController, controller: _urlController,
focusNode: _urlFocusNode,
style: const TextStyle(fontSize: 14),
decoration: InputDecoration( decoration: InputDecoration(
labelText: 'Relay URL', labelText: 'Relay URL',
hintText: 'wss://relay.example.com', hintText: 'wss://relay.example.com',
border: const OutlineInputBorder(), border: const OutlineInputBorder(),
prefixIcon: const Icon(Icons.link, size: 18),
contentPadding: const EdgeInsets.symmetric(horizontal: 12, vertical: 12),
isDense: true,
), ),
keyboardType: TextInputType.url, keyboardType: TextInputType.url,
), ),
), const SizedBox(height: 8),
const SizedBox(width: 8),
ElevatedButton.icon( ElevatedButton.icon(
onPressed: () async { onPressed: () async {
final url = _urlController.text.trim(); String url = _urlController.text.trim();
// Remove wss:// prefix if user added it manually, we'll add it properly
if (url.startsWith('wss://')) {
url = url.substring(6);
}
if (url.isNotEmpty) { if (url.isNotEmpty) {
final success = await widget.controller.addRelay(url); final fullUrl = url.startsWith('wss://') ? url : 'wss://$url';
final success = await widget.controller.addRelay(fullUrl);
if (mounted) { if (mounted) {
if (success) { if (success) {
_urlController.clear(); _urlController.clear();
@ -787,48 +665,49 @@ class _RelayManagementScreenState extends State<RelayManagementScreen> {
} }
} }
}, },
icon: const Icon(Icons.add), icon: const Icon(Icons.add, size: 16),
label: const Text('Add'), label: const Text('Add Relay', style: TextStyle(fontSize: 13)),
style: ElevatedButton.styleFrom(
padding: const EdgeInsets.symmetric(vertical: 8, horizontal: 12),
minimumSize: const Size(0, 36),
), ),
],
), ),
const SizedBox(height: 16), const SizedBox(height: 12),
// Relay list // Relay list
SizedBox( if (widget.controller.relays.isEmpty)
height: 300, Padding(
child: widget.controller.relays.isEmpty padding: const EdgeInsets.symmetric(vertical: 16),
? Center(
child: Column( child: Column(
mainAxisAlignment: MainAxisAlignment.center,
children: [ children: [
Icon( Icon(
Icons.cloud_off, Icons.cloud_off,
size: 48, size: 36,
color: Colors.grey.shade400, color: Colors.grey.shade400,
), ),
const SizedBox(height: 16), const SizedBox(height: 8),
Text( Text(
'No relays configured', 'No relays configured',
style: Theme.of(context).textTheme.titleMedium?.copyWith( style: theme.textTheme.bodyMedium?.copyWith(
color: Colors.grey, color: Colors.grey,
fontSize: 13,
), ),
), ),
const SizedBox(height: 8), const SizedBox(height: 4),
Text( Text(
'Add a relay to get started', 'Add a relay to get started',
style: Theme.of(context).textTheme.bodySmall?.copyWith( style: theme.textTheme.bodySmall?.copyWith(
color: Colors.grey, color: Colors.grey,
fontSize: 11,
), ),
), ),
], ],
), ),
) )
: ListView.builder( else
shrinkWrap: true, ...widget.controller.relays.map((relay) {
itemCount: widget.controller.relays.length, return Padding(
itemBuilder: (context, index) { padding: const EdgeInsets.only(bottom: 6),
final relay = widget.controller.relays[index]; child: _RelayListItem(
return _RelayListItem(
relay: relay, relay: relay,
onToggle: () async { onToggle: () async {
await widget.controller.toggleRelay(relay.url); await widget.controller.toggleRelay(relay.url);
@ -842,15 +721,47 @@ class _RelayManagementScreenState extends State<RelayManagementScreen> {
), ),
); );
}, },
),
); );
}, }).toList(),
],
), ),
), ),
],
// Error message
if (widget.controller.error != null) ...[
const SizedBox(height: 8),
Container(
padding: const EdgeInsets.all(12),
decoration: BoxDecoration(
color: Colors.red.shade50,
borderRadius: BorderRadius.circular(10),
border: Border.all(color: Colors.red.shade200),
), ),
child: Row(
children: [
Icon(Icons.error_outline, size: 16, color: Colors.red.shade700),
const SizedBox(width: 8),
Expanded(
child: Text(
widget.controller.error!,
style: TextStyle(
color: Colors.red.shade700,
fontSize: 12,
),
),
),
IconButton(
icon: const Icon(Icons.close, size: 16),
onPressed: widget.controller.clearError,
color: Colors.red.shade700,
padding: EdgeInsets.zero,
constraints: const BoxConstraints(minWidth: 24, minHeight: 24),
), ),
], ],
), ),
),
],
], ],
), ),
); );
@ -858,6 +769,207 @@ class _RelayManagementScreenState extends State<RelayManagementScreen> {
), ),
); );
} }
Widget _buildSectionCard({
required BuildContext context,
required String title,
required IconData icon,
required Widget child,
}) {
final theme = Theme.of(context);
final isDark = theme.brightness == Brightness.dark;
return Card(
elevation: 0,
margin: EdgeInsets.zero,
shape: RoundedRectangleBorder(
borderRadius: BorderRadius.circular(12),
side: BorderSide(
color: isDark ? Colors.grey.shade800 : Colors.grey.shade200,
width: 1,
),
),
child: Padding(
padding: const EdgeInsets.all(12),
child: Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
Row(
children: [
Container(
padding: const EdgeInsets.all(6),
decoration: BoxDecoration(
color: theme.primaryColor.withValues(alpha: 0.1),
borderRadius: BorderRadius.circular(6),
),
child: Icon(icon, color: theme.primaryColor, size: 16),
),
const SizedBox(width: 8),
Text(
title,
style: theme.textTheme.titleMedium?.copyWith(
fontWeight: FontWeight.w600,
fontSize: 15,
),
),
],
),
const SizedBox(height: 12),
child,
],
),
),
);
}
Widget _buildSettingTile({
required BuildContext context,
required String title,
required String subtitle,
required bool value,
required ValueChanged<bool> onChanged,
required IconData icon,
}) {
final theme = Theme.of(context);
return Row(
children: [
Icon(icon, size: 16, color: theme.primaryColor),
const SizedBox(width: 10),
Expanded(
child: Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
Text(
title,
style: theme.textTheme.bodyMedium?.copyWith(
fontWeight: FontWeight.w500,
fontSize: 14,
),
),
const SizedBox(height: 2),
Text(
subtitle,
style: theme.textTheme.bodySmall?.copyWith(
color: Colors.grey,
fontSize: 11,
),
),
],
),
),
Transform.scale(
scale: 0.85,
child: Switch(
value: value,
onChanged: onChanged,
),
),
],
);
}
Widget _buildMediaServerTile({
required BuildContext context,
required MediaServerConfig server,
required bool isDefault,
required VoidCallback onEdit,
required VoidCallback onRemove,
}) {
final theme = Theme.of(context);
return Container(
padding: const EdgeInsets.all(10),
decoration: BoxDecoration(
color: theme.cardColor,
borderRadius: BorderRadius.circular(8),
border: Border.all(
color: isDefault ? theme.primaryColor.withValues(alpha: 0.3) : Colors.grey.shade300,
width: isDefault ? 1.5 : 1,
),
),
child: Row(
children: [
Container(
padding: const EdgeInsets.all(5),
decoration: BoxDecoration(
color: theme.primaryColor.withValues(alpha: 0.1),
borderRadius: BorderRadius.circular(6),
),
child: Icon(
Icons.storage,
color: theme.primaryColor,
size: 16,
),
),
const SizedBox(width: 8),
Expanded(
child: Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
Row(
children: [
Flexible(
child: Text(
server.name ?? '${server.type.toUpperCase()} Server',
style: theme.textTheme.bodyMedium?.copyWith(
fontWeight: FontWeight.w600,
fontSize: 13,
),
overflow: TextOverflow.ellipsis,
),
),
if (isDefault) ...[
const SizedBox(width: 6),
Container(
padding: const EdgeInsets.symmetric(horizontal: 6, vertical: 1),
decoration: BoxDecoration(
color: theme.primaryColor.withValues(alpha: 0.2),
borderRadius: BorderRadius.circular(3),
),
child: Text(
'Default',
style: TextStyle(
fontSize: 9,
fontWeight: FontWeight.w600,
color: theme.primaryColor,
),
),
),
],
],
),
const SizedBox(height: 2),
Text(
server.baseUrl,
style: theme.textTheme.bodySmall?.copyWith(
color: Colors.grey,
fontSize: 11,
),
overflow: TextOverflow.ellipsis,
),
],
),
),
IconButton(
icon: const Icon(Icons.edit, size: 16),
onPressed: onEdit,
tooltip: 'Edit',
padding: EdgeInsets.zero,
constraints: const BoxConstraints(minWidth: 28, minHeight: 28),
),
IconButton(
icon: const Icon(Icons.delete_outline, size: 16),
onPressed: onRemove,
tooltip: 'Remove',
color: Colors.red,
padding: EdgeInsets.zero,
constraints: const BoxConstraints(minWidth: 28, minHeight: 28),
),
],
),
);
}
} }
/// Widget for displaying a single relay in the list. /// Widget for displaying a single relay in the list.
@ -879,10 +991,21 @@ class _RelayListItem extends StatelessWidget {
@override @override
Widget build(BuildContext context) { Widget build(BuildContext context) {
return Card( final theme = Theme.of(context);
margin: const EdgeInsets.symmetric(horizontal: 16, vertical: 4), final isConnected = relay.isConnected && relay.isEnabled;
child: Padding(
padding: const EdgeInsets.symmetric(horizontal: 12, vertical: 8), return Container(
padding: const EdgeInsets.all(10),
decoration: BoxDecoration(
color: theme.cardColor,
borderRadius: BorderRadius.circular(8),
border: Border.all(
color: isConnected
? Colors.green.withValues(alpha: 0.3)
: Colors.grey.shade300,
width: isConnected ? 1.5 : 1,
),
),
child: Row( child: Row(
children: [ children: [
// Status indicator // Status indicator
@ -891,12 +1014,19 @@ class _RelayListItem extends StatelessWidget {
height: 10, height: 10,
decoration: BoxDecoration( decoration: BoxDecoration(
shape: BoxShape.circle, shape: BoxShape.circle,
color: relay.isConnected && relay.isEnabled color: isConnected ? Colors.green : Colors.grey,
? Colors.green boxShadow: isConnected
: Colors.grey, ? [
BoxShadow(
color: Colors.green.withValues(alpha: 0.5),
blurRadius: 3,
spreadRadius: 1,
),
]
: null,
), ),
), ),
const SizedBox(width: 8), const SizedBox(width: 10),
// URL and status text // URL and status text
Expanded( Expanded(
child: Column( child: Column(
@ -905,69 +1035,59 @@ class _RelayListItem extends StatelessWidget {
children: [ children: [
Text( Text(
relay.url, relay.url,
style: const TextStyle( style: theme.textTheme.bodyMedium?.copyWith(
fontSize: 13,
fontWeight: FontWeight.w500, fontWeight: FontWeight.w500,
fontSize: 13,
), ),
overflow: TextOverflow.ellipsis, overflow: TextOverflow.ellipsis,
maxLines: 1, maxLines: 1,
), ),
const SizedBox(height: 2), const SizedBox(height: 2),
Row(
children: [
Icon(
isConnected ? Icons.check_circle : Icons.cancel,
size: 12,
color: isConnected ? Colors.green : Colors.grey,
),
const SizedBox(width: 3),
Text( Text(
relay.isConnected && relay.isEnabled isConnected ? 'Connected' : 'Disabled',
? 'Connected' style: theme.textTheme.bodySmall?.copyWith(
: 'Disabled', color: isConnected ? Colors.green : Colors.grey,
style: TextStyle( fontWeight: FontWeight.w500,
fontSize: 11, fontSize: 11,
color: relay.isConnected && relay.isEnabled
? Colors.green
: Colors.grey,
), ),
), ),
], ],
), ),
],
),
), ),
const SizedBox(width: 8), const SizedBox(width: 8),
// Toggle switch // Toggle switch
Row(
mainAxisSize: MainAxisSize.min,
children: [
Text(
relay.isEnabled ? 'ON' : 'OFF',
style: TextStyle(
fontSize: 11,
fontWeight: FontWeight.w500,
color: relay.isEnabled
? Colors.green
: Colors.grey[600],
),
),
const SizedBox(width: 4),
Transform.scale( Transform.scale(
scale: 0.8, scale: 0.85,
child: Switch( child: Switch(
value: relay.isEnabled, value: relay.isEnabled,
onChanged: (_) => onToggle(), onChanged: (_) => onToggle(),
), ),
), ),
],
),
const SizedBox(width: 4), const SizedBox(width: 4),
// Remove button // Remove button
IconButton( IconButton(
icon: const Icon(Icons.delete, size: 18), icon: const Icon(Icons.delete_outline, size: 16),
color: Colors.red, color: Colors.red,
tooltip: 'Remove', tooltip: 'Remove',
onPressed: onRemove, onPressed: onRemove,
padding: EdgeInsets.zero, padding: EdgeInsets.zero,
constraints: const BoxConstraints( constraints: const BoxConstraints(
minWidth: 32, minWidth: 28,
minHeight: 32, minHeight: 28,
), ),
), ),
], ],
), ),
),
); );
} }
} }

Loading…
Cancel
Save

Powered by TurnKey Linux.