feat(ui): hub-endpoint settings dialog with persistence

Studio is no longer hardcoded to localhost:50051. A gear icon
in the sidebar footer opens a small settings dialog where the
operator picks host / port / TLS-on-or-off. Saved values
persist via shared_preferences and Studio reconnects to the
new endpoint immediately.

- data/hub.dart: HubService.loadPersistedEndpoint() called once
  before the first frame; reconnect() persists on every change.
- widgets/fai_settings_dialog.dart: typed form with port range
  validation, live URL preview pill, error surfacing if the
  reconnect fails.
- main.dart: gear icon at the sidebar bottom, replaces the
  static "platform v0.10.42" footer.

shared_preferences added as dependency. flutter analyze clean,
flutter test 2/2, macOS debug build succeeds.

Bumps fai_studio 0.4.0 -> 0.5.0.

Signed-off-by: flemming-it <stefan.a.flemming@googlemail.com>
This commit is contained in:
flemming-it 2026-05-05 22:12:47 +02:00
parent 4352ecc28c
commit 521a8baab8
9 changed files with 365 additions and 11 deletions

View file

@ -16,7 +16,12 @@ import 'theme/theme.dart';
import 'theme/tokens.dart';
import 'widgets/widgets.dart';
void main() {
Future<void> main() async {
WidgetsFlutterBinding.ensureInitialized();
// Restore the persisted endpoint before the first frame so the
// sidebar pill doesn't briefly say "127.0.0.1:50051" before
// switching to the operator's actual hub.
await HubService.instance.loadPersistedEndpoint();
runApp(const StudioApp());
}
@ -199,15 +204,34 @@ class _Sidebar extends StatelessWidget {
],
),
),
// Footer hint.
// Footer: settings + version.
Padding(
padding: const EdgeInsets.symmetric(horizontal: FaiSpace.lg),
child: Text(
'platform v0.10.42',
style: theme.textTheme.bodySmall?.copyWith(
color: theme.colorScheme.onSurfaceVariant,
fontSize: 10,
),
padding: const EdgeInsets.symmetric(horizontal: FaiSpace.md),
child: Row(
children: [
Expanded(
child: Text(
'platform target',
style: theme.textTheme.bodySmall?.copyWith(
color: theme.colorScheme.onSurfaceVariant,
fontSize: 10,
),
),
),
IconButton(
icon: const Icon(Icons.settings_outlined, size: 16),
tooltip: 'Hub endpoint…',
visualDensity: VisualDensity.compact,
onPressed: () async {
final saved = await FaiSettingsDialog.show(context);
if (saved && context.mounted) {
// Force a sidebar rebuild so the new endpoint
// shows in the connection pill.
(context as Element).markNeedsBuild();
}
},
),
],
),
),
],