From c27091d947d6458a8b99d708c2d4a02818e8a94b Mon Sep 17 00:00:00 2001 From: abdellah-darni Date: Sat, 13 Jun 2026 11:17:48 +0100 Subject: [PATCH 1/3] Implement backend URL configuration and setup screen - Remove deprecated API_URL constant and related imports. - Introduce BackendUrlController for managing backend URL state. - Add BackendSetupScreen for user to input backend server URL. - Update StompService and dioClient to utilize dynamic backend URL. - Modify SettingsScreen to include option for changing backend server. - Ensure splash screen redirects to backend setup if no URL is configured. --- .../deepdame/lib/core/config/app_config.dart | 4 - .../core/network/backend_url_controller.dart | 21 ++ .../network/backend_url_controller.g.dart | 63 +++++ .../deepdame/lib/core/network/dio_client.dart | 22 +- .../lib/core/network/dio_client.g.dart | 2 +- .../lib/core/network/stomp_service.dart | 12 +- .../lib/core/network/stomp_service.g.dart | 2 +- .../deepdame/lib/core/routing/app_router.dart | 6 + .../backend_setup/backend_setup_screen.dart | 250 ++++++++++++++++++ .../features/settings/settings_screen.dart | 64 +++++ .../lib/features/splash/splash_screen.dart | 7 + client/deepdame/lib/main.dart | 19 -- 12 files changed, 441 insertions(+), 31 deletions(-) delete mode 100644 client/deepdame/lib/core/config/app_config.dart create mode 100644 client/deepdame/lib/core/network/backend_url_controller.dart create mode 100644 client/deepdame/lib/core/network/backend_url_controller.g.dart create mode 100644 client/deepdame/lib/features/backend_setup/backend_setup_screen.dart diff --git a/client/deepdame/lib/core/config/app_config.dart b/client/deepdame/lib/core/config/app_config.dart deleted file mode 100644 index 4a46910f..00000000 --- a/client/deepdame/lib/core/config/app_config.dart +++ /dev/null @@ -1,4 +0,0 @@ -// to mark it as a application wide constant instead of just a random variable -// ignore: constant_identifier_names -const String API_URL = 'https://deepdame.darni.org'; -// const String API_URL = 'http://ilyass-server:8082'; diff --git a/client/deepdame/lib/core/network/backend_url_controller.dart b/client/deepdame/lib/core/network/backend_url_controller.dart new file mode 100644 index 00000000..1650767f --- /dev/null +++ b/client/deepdame/lib/core/network/backend_url_controller.dart @@ -0,0 +1,21 @@ +import 'package:deepdame/core/storage/storage_providers.dart'; +import 'package:riverpod_annotation/riverpod_annotation.dart'; + +part 'backend_url_controller.g.dart'; + +@Riverpod(keepAlive: true) +class BackendUrlController extends _$BackendUrlController { + @override + String? build() => + ref.watch(sharedPreferencesProvider).getString('backend_url'); + + Future setUrl(String url) async { + await ref.read(sharedPreferencesProvider).setString('backend_url', url); + state = url; + } + + Future clear() async { + await ref.read(sharedPreferencesProvider).remove('backend_url'); + state = null; + } +} diff --git a/client/deepdame/lib/core/network/backend_url_controller.g.dart b/client/deepdame/lib/core/network/backend_url_controller.g.dart new file mode 100644 index 00000000..19dbd9e8 --- /dev/null +++ b/client/deepdame/lib/core/network/backend_url_controller.g.dart @@ -0,0 +1,63 @@ +// GENERATED CODE - DO NOT MODIFY BY HAND + +part of 'backend_url_controller.dart'; + +// ************************************************************************** +// RiverpodGenerator +// ************************************************************************** + +// GENERATED CODE - DO NOT MODIFY BY HAND +// ignore_for_file: type=lint, type=warning + +@ProviderFor(BackendUrlController) +final backendUrlControllerProvider = BackendUrlControllerProvider._(); + +final class BackendUrlControllerProvider + extends $NotifierProvider { + BackendUrlControllerProvider._() + : super( + from: null, + argument: null, + retry: null, + name: r'backendUrlControllerProvider', + isAutoDispose: false, + dependencies: null, + $allTransitiveDependencies: null, + ); + + @override + String debugGetCreateSourceHash() => _$backendUrlControllerHash(); + + @$internal + @override + BackendUrlController create() => BackendUrlController(); + + /// {@macro riverpod.override_with_value} + Override overrideWithValue(String? value) { + return $ProviderOverride( + origin: this, + providerOverride: $SyncValueProvider(value), + ); + } +} + +String _$backendUrlControllerHash() => + r'd67e192ea9782cabe8fe17d6690bf4069269423d'; + +abstract class _$BackendUrlController extends $Notifier { + String? build(); + @$mustCallSuper + @override + void runBuild() { + final ref = this.ref as $Ref; + final element = + ref.element + as $ClassProviderElement< + AnyNotifier, + String?, + Object?, + Object? + >; + element.handleCreate(ref, build); + } +} diff --git a/client/deepdame/lib/core/network/dio_client.dart b/client/deepdame/lib/core/network/dio_client.dart index aee5b112..9650974c 100644 --- a/client/deepdame/lib/core/network/dio_client.dart +++ b/client/deepdame/lib/core/network/dio_client.dart @@ -1,5 +1,9 @@ import 'package:cookie_jar/cookie_jar.dart'; +import 'package:deepdame/core/network/auth_interceptor.dart'; +import 'package:deepdame/core/network/backend_url_controller.dart'; import 'package:dio/dio.dart'; +import 'package:dio_cookie_manager/dio_cookie_manager.dart'; +import 'package:flutter/foundation.dart'; import 'package:riverpod_annotation/riverpod_annotation.dart'; part 'dio_client.g.dart'; @@ -8,4 +12,20 @@ part 'dio_client.g.dart'; PersistCookieJar cookieJar(Ref ref) => throw UnimplementedError(); @Riverpod(keepAlive: true) -Dio dio(Ref ref) => throw UnimplementedError(); +Dio dio(Ref ref) { + final baseUrl = ref.watch(backendUrlControllerProvider) ?? ''; + final dio = Dio(BaseOptions(baseUrl: baseUrl)); + dio.interceptors.add(CookieManager(ref.watch(cookieJarProvider))); + dio.interceptors.add(AuthInterceptor(dio)); + + if (kDebugMode) { + dio.interceptors.add( + LogInterceptor( + requestBody: true, + responseBody: true, + ), + ); + } + + return dio; +} diff --git a/client/deepdame/lib/core/network/dio_client.g.dart b/client/deepdame/lib/core/network/dio_client.g.dart index fe93d02d..fde9ae77 100644 --- a/client/deepdame/lib/core/network/dio_client.g.dart +++ b/client/deepdame/lib/core/network/dio_client.g.dart @@ -93,4 +93,4 @@ final class DioProvider extends $FunctionalProvider } } -String _$dioHash() => r'8ba822f17784699968d6df58656e43f08a9bcb4b'; +String _$dioHash() => r'0cf3c32042b6116cd29e8e7daec1f0fca1f92507'; diff --git a/client/deepdame/lib/core/network/stomp_service.dart b/client/deepdame/lib/core/network/stomp_service.dart index 1bb63308..e0970fef 100644 --- a/client/deepdame/lib/core/network/stomp_service.dart +++ b/client/deepdame/lib/core/network/stomp_service.dart @@ -2,7 +2,7 @@ import 'dart:async'; import 'dart:convert'; import 'package:cookie_jar/cookie_jar.dart'; -import 'package:deepdame/core/config/app_config.dart'; +import 'package:deepdame/core/network/backend_url_controller.dart'; import 'package:deepdame/core/network/dio_client.dart'; import 'package:flutter/foundation.dart'; import 'package:riverpod_annotation/riverpod_annotation.dart'; @@ -13,11 +13,12 @@ part 'stomp_service.g.dart'; enum StompState { disconnected, connecting, connected, error } class StompService { - StompService(this._cookieJar) { + StompService(this._cookieJar, this._baseUrl) { debugPrint('StompService constructed'); } final CookieJar _cookieJar; + final String _baseUrl; StompClient? _client; final _state = StreamController.broadcast(); StompState _current = StompState.disconnected; @@ -36,12 +37,12 @@ class StompService { if (_client?.connected ?? false) return; _emit(StompState.connecting); - final cookies = await _cookieJar.loadForRequest(Uri.parse(API_URL)); + final cookies = await _cookieJar.loadForRequest(Uri.parse(_baseUrl)); final cookieHeader = cookies.map((c) => '${c.name}=${c.value}').join('; '); _client = StompClient( config: StompConfig.sockJS( - url: '$API_URL/ws', + url: '$_baseUrl/ws', onConnect: (_) { debugPrint('----- STOMP CONNECTED'); _emit(StompState.connected); @@ -129,7 +130,8 @@ class StompService { @Riverpod(keepAlive: true) StompService stompService(Ref ref) { - final service = StompService(ref.watch(cookieJarProvider)); + final baseUrl = ref.watch(backendUrlControllerProvider) ?? ''; + final service = StompService(ref.watch(cookieJarProvider), baseUrl); ref.onDispose(service.dispose); return service; } diff --git a/client/deepdame/lib/core/network/stomp_service.g.dart b/client/deepdame/lib/core/network/stomp_service.g.dart index 70d7e33c..ec2fc93b 100644 --- a/client/deepdame/lib/core/network/stomp_service.g.dart +++ b/client/deepdame/lib/core/network/stomp_service.g.dart @@ -48,7 +48,7 @@ final class StompServiceProvider } } -String _$stompServiceHash() => r'3b51fa582b71eaad9bedbe6d39db2c7e98172b81'; +String _$stompServiceHash() => r'a7c83c1e17d6595d0fa4cf296e297ff6c9520c7a'; @ProviderFor(stompState) final stompStateProvider = StompStateProvider._(); diff --git a/client/deepdame/lib/core/routing/app_router.dart b/client/deepdame/lib/core/routing/app_router.dart index 46d1a386..4a61607f 100644 --- a/client/deepdame/lib/core/routing/app_router.dart +++ b/client/deepdame/lib/core/routing/app_router.dart @@ -3,6 +3,7 @@ import 'package:deepdame/features/auth/login_screen.dart'; import 'package:deepdame/features/auth/recover_account.dart'; import 'package:deepdame/features/auth/register_screen.dart'; import 'package:deepdame/features/auth/validate_email.dart'; +import 'package:deepdame/features/backend_setup/backend_setup_screen.dart'; import 'package:deepdame/features/banned/banned_screen.dart'; import 'package:deepdame/features/chat/chat_screen.dart'; import 'package:deepdame/features/dev/test/dev_chat_probe_screen.dart'; @@ -34,6 +35,11 @@ final appRouter = GoRouter( path: '/splash', builder: (context, state) => const SplashScreen(), ), + GoRoute( + name: 'backend-setup', + path: '/backend-setup', + builder: (context, state) => const BackendSetupScreen(), + ), GoRoute( name: 'onboarding', path: '/onboarding', diff --git a/client/deepdame/lib/features/backend_setup/backend_setup_screen.dart b/client/deepdame/lib/features/backend_setup/backend_setup_screen.dart new file mode 100644 index 00000000..a1690a57 --- /dev/null +++ b/client/deepdame/lib/features/backend_setup/backend_setup_screen.dart @@ -0,0 +1,250 @@ +import 'package:deepdame/core/network/backend_url_controller.dart'; +import 'package:deepdame/features/auth/application/auth_controller.dart'; +import 'package:dio/dio.dart'; +import 'package:flutter/material.dart'; +import 'package:flutter_riverpod/flutter_riverpod.dart'; +import 'package:go_router/go_router.dart'; + +enum _TestStatus { idle, success, failure } + +/// Normalizes a raw URL string: trims whitespace and strips a trailing +/// slash. Returns null if the result isn't a valid http(s) URL. +String? normalizeBackendUrl(String raw) { + var normalized = raw.trim(); + while (normalized.endsWith('/')) { + normalized = normalized.substring(0, normalized.length - 1); + } + + final uri = Uri.tryParse(normalized); + final isValid = + uri != null && + (uri.scheme == 'http' || uri.scheme == 'https') && + uri.host.isNotEmpty; + + return isValid ? normalized : null; +} + +class BackendSetupScreen extends ConsumerStatefulWidget { + const BackendSetupScreen({super.key}); + + @override + ConsumerState createState() => + _BackendSetupScreenState(); +} + +class _BackendSetupScreenState extends ConsumerState { + final _urlController = TextEditingController(); + bool _isTesting = false; + String? _fieldError; + _TestStatus _testStatus = _TestStatus.idle; + + @override + void dispose() { + _urlController.dispose(); + super.dispose(); + } + + Future _testConnection() async { + final url = normalizeBackendUrl(_urlController.text); + if (url == null) { + setState(() { + _fieldError = 'Enter a valid http:// or https:// URL'; + _testStatus = _TestStatus.idle; + }); + return; + } + + setState(() { + _fieldError = null; + _isTesting = true; + _testStatus = _TestStatus.idle; + }); + + final dio = Dio( + BaseOptions( + baseUrl: url, + connectTimeout: const Duration(seconds: 5), + receiveTimeout: const Duration(seconds: 5), + ), + ); + + var success = false; + try { + final response = await dio.get('/actuator/health'); + success = response.statusCode == 200; + } on DioException catch (_) { + success = false; + } + + if (!mounted) return; + setState(() { + _isTesting = false; + _testStatus = success ? _TestStatus.success : _TestStatus.failure; + }); + } + + Future _continue() async { + final url = normalizeBackendUrl(_urlController.text); + if (url == null) { + setState(() => _fieldError = 'Enter a valid http:// or https:// URL'); + return; + } + + // Idempotent: clears any active session. On first-launch, this is a + // no-op since there's nothing to clear. + await ref.read(authControllerProvider.notifier).logout(); + + // Save the new URL — this triggers a dioProvider/STOMP rebuild. + await ref.read(backendUrlControllerProvider.notifier).setUrl(url); + + if (mounted) context.goNamed('splash'); + } + + @override + Widget build(BuildContext context) { + final scheme = Theme.of(context).colorScheme; + final canContinue = _urlController.text.trim().isNotEmpty; + + return Scaffold( + appBar: AppBar( + title: const Text('Server Setup'), + automaticallyImplyLeading: false, + ), + body: SafeArea( + child: SingleChildScrollView( + child: Padding( + padding: const EdgeInsets.all(25), + child: Column( + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + Text( + 'Enter the URL of a DeepDame backend server.', + style: TextStyle( + fontWeight: FontWeight.w700, + fontSize: 20, + color: scheme.onSurface, + ), + ), + const SizedBox(height: 8), + Text( + "Don't have a server? See the README for setup " + 'instructions.', + style: TextStyle( + fontSize: 13, + color: scheme.onSurface.withAlpha(160), + ), + ), + const SizedBox(height: 24), + Text( + 'Server URL', + style: TextStyle( + fontWeight: FontWeight.w500, + fontSize: 13, + color: scheme.onSurface.withAlpha(160), + ), + ), + const SizedBox(height: 6), + TextField( + controller: _urlController, + autofocus: true, + autocorrect: false, + keyboardType: TextInputType.url, + onChanged: (_) => setState(() { + _fieldError = null; + _testStatus = _TestStatus.idle; + }), + style: const TextStyle( + fontSize: 18, + fontWeight: FontWeight.w400, + ), + decoration: InputDecoration( + border: const OutlineInputBorder( + borderRadius: BorderRadius.all(Radius.circular(20)), + ), + hintStyle: TextStyle(color: scheme.onSurface.withAlpha(160)), + hintText: 'https://your-server.com', + errorText: _fieldError, + ), + ), + const SizedBox(height: 16), + OutlinedButton( + onPressed: _isTesting ? null : _testConnection, + style: OutlinedButton.styleFrom( + minimumSize: const Size.fromHeight(48), + shape: const RoundedRectangleBorder( + borderRadius: BorderRadius.all(Radius.circular(20)), + ), + ), + child: _isTesting + ? SizedBox( + width: 20, + height: 20, + child: CircularProgressIndicator( + strokeWidth: 2.5, + color: scheme.primary, + ), + ) + : const Text('Test connection'), + ), + if (_testStatus != _TestStatus.idle) ...[ + const SizedBox(height: 8), + Row( + children: [ + Icon( + _testStatus == _TestStatus.success + ? Icons.check_circle_outline_rounded + : Icons.warning_amber_rounded, + size: 18, + color: _testStatus == _TestStatus.success + ? Colors.green + : scheme.error, + ), + const SizedBox(width: 8), + Text( + _testStatus == _TestStatus.success + ? 'Connected' + : "Couldn't reach server", + style: TextStyle( + fontSize: 13, + fontWeight: FontWeight.w500, + color: _testStatus == _TestStatus.success + ? Colors.green + : scheme.error, + ), + ), + ], + ), + ], + const SizedBox(height: 24), + SizedBox( + width: double.infinity, + child: FilledButton( + onPressed: canContinue ? _continue : null, + style: FilledButton.styleFrom( + backgroundColor: scheme.primary, + foregroundColor: scheme.onPrimary, + disabledBackgroundColor: scheme.onSurface.withValues( + alpha: 0.12, + ), + disabledForegroundColor: scheme.onSurface.withValues( + alpha: 0.38, + ), + padding: const EdgeInsets.all(16), + shape: const RoundedRectangleBorder( + borderRadius: BorderRadius.all(Radius.circular(20)), + ), + ), + child: const Text( + 'Continue', + style: TextStyle(fontWeight: FontWeight.w600, fontSize: 16), + ), + ), + ), + ], + ), + ), + ), + ), + ); + } +} diff --git a/client/deepdame/lib/features/settings/settings_screen.dart b/client/deepdame/lib/features/settings/settings_screen.dart index e588b1a1..0c7dcbf3 100644 --- a/client/deepdame/lib/features/settings/settings_screen.dart +++ b/client/deepdame/lib/features/settings/settings_screen.dart @@ -1,3 +1,5 @@ +import 'dart:async'; + import 'package:deepdame/core/theme/theme_controller.dart'; import 'package:deepdame/core/theme/theme_state.dart'; import 'package:deepdame/core/theme/theme_swatch.dart'; @@ -133,6 +135,12 @@ class SettingsScreen extends ConsumerWidget { children: [_LogoutTile()], ), + const SizedBox(height: 24), + + const _SettingsCard( + children: [_ChangeBackendTile()], + ), + const SizedBox(height: 32), ], ), @@ -491,6 +499,62 @@ class _LogoutTile extends ConsumerWidget { } } +class _ChangeBackendTile extends ConsumerWidget { + const _ChangeBackendTile(); + + @override + Widget build(BuildContext context, WidgetRef ref) { + final scheme = Theme.of(context).colorScheme; + return InkWell( + onTap: () => _onChangeBackendTap(context, ref), + borderRadius: BorderRadius.circular(14), + child: Padding( + padding: const EdgeInsets.symmetric(horizontal: 16, vertical: 14), + child: Row( + children: [ + Text( + 'Change backend server', + style: TextStyle( + fontSize: 15, + fontWeight: FontWeight.w500, + color: scheme.error, + ), + ), + ], + ), + ), + ); + } + + Future _onChangeBackendTap(BuildContext context, WidgetRef ref) async { + final confirmed = await showDialog( + context: context, + builder: (ctx) => AlertDialog( + title: const Text('Change backend server?'), + content: const Text( + "You'll be taken to the server setup screen. Your " + 'session will end only after you enter a new server.', + ), + actions: [ + TextButton( + onPressed: () => Navigator.of(ctx).pop(false), + child: const Text('Cancel'), + ), + FilledButton( + onPressed: () => Navigator.of(ctx).pop(true), + child: const Text('Continue'), + ), + ], + ), + ); + + if (confirmed != true) return; + if (context.mounted) { + unawaited(context.pushNamed('backend-setup')); + } + } +} + // ─── Structural ─────────────────────────────────────────────────────────────── class _TileShimmer extends StatelessWidget { diff --git a/client/deepdame/lib/features/splash/splash_screen.dart b/client/deepdame/lib/features/splash/splash_screen.dart index 9f7cefba..3fef208e 100644 --- a/client/deepdame/lib/features/splash/splash_screen.dart +++ b/client/deepdame/lib/features/splash/splash_screen.dart @@ -42,6 +42,13 @@ class _SplashScreenState extends ConsumerState Future _checkAuth() async { final prefs = ref.read(sharedPreferencesProvider); + + final backendUrl = prefs.getString('backend_url'); + if (backendUrl == null || backendUrl.isEmpty) { + if (mounted) context.goNamed('backend-setup'); + return; + } + final hasSeenOnboarding = prefs.getBool('has_seen_onboarding') ?? false; if (!hasSeenOnboarding) { diff --git a/client/deepdame/lib/main.dart b/client/deepdame/lib/main.dart index 03f13130..662068e5 100644 --- a/client/deepdame/lib/main.dart +++ b/client/deepdame/lib/main.dart @@ -1,7 +1,5 @@ import 'package:cookie_jar/cookie_jar.dart'; import 'package:deepdame/core/audio/sound_service.dart'; -import 'package:deepdame/core/config/app_config.dart'; -import 'package:deepdame/core/network/auth_interceptor.dart'; import 'package:deepdame/core/network/dio_client.dart'; import 'package:deepdame/core/routing/app_router.dart'; import 'package:deepdame/core/storage/storage_providers.dart'; @@ -10,11 +8,8 @@ import 'package:deepdame/firebase_options.dart'; import 'package:deepdame/notifications/fcm_background_handler.dart'; import 'package:deepdame/notifications/local_notifications.dart'; import 'package:deepdame/notifications/messaging_provider.dart'; -import 'package:dio/dio.dart'; -import 'package:dio_cookie_manager/dio_cookie_manager.dart'; import 'package:firebase_core/firebase_core.dart'; import 'package:firebase_messaging/firebase_messaging.dart'; -import 'package:flutter/foundation.dart'; import 'package:flutter/material.dart'; import 'package:flutter_riverpod/flutter_riverpod.dart'; import 'package:path_provider/path_provider.dart'; @@ -38,26 +33,12 @@ void main() async { storage: FileStorage('${appDocDir.path}/.cookies'), ); - final dio = Dio(BaseOptions(baseUrl: API_URL)); - dio.interceptors.add(CookieManager(cookieJar)); - dio.interceptors.add(AuthInterceptor(dio)); - - if (kDebugMode) { - dio.interceptors.add( - LogInterceptor( - requestBody: true, - responseBody: true, - ), - ); - } - final prefs = await SharedPreferences.getInstance(); runApp( ProviderScope( overrides: [ cookieJarProvider.overrideWithValue(cookieJar), - dioProvider.overrideWithValue(dio), sharedPreferencesProvider.overrideWithValue(prefs), ], child: const DeepDame(), From ace6b21054f075305cdf9ded56e58683cb660d24 Mon Sep 17 00:00:00 2001 From: abdellah-darni Date: Sat, 13 Jun 2026 11:19:47 +0100 Subject: [PATCH 2/3] bump version to 2.0.28+29 in pubspec.yaml --- client/deepdame/pubspec.yaml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/client/deepdame/pubspec.yaml b/client/deepdame/pubspec.yaml index e3289a87..ed25a95b 100644 --- a/client/deepdame/pubspec.yaml +++ b/client/deepdame/pubspec.yaml @@ -16,7 +16,7 @@ publish_to: 'none' # Remove this line if you wish to publish to pub.dev # https://developer.apple.com/library/archive/documentation/General/Reference/InfoPlistKeyReference/Articles/CoreFoundationKeys.html # In Windows, build-name is used as the major, minor, and patch parts # of the product and file versions while build-number is used as the build suffix. -version: 2.0.27+28 +version: 2.0.28+29 environment: sdk: ^3.10.4 From 46af4d079f199f1e65248b8f689d15db0f58105e Mon Sep 17 00:00:00 2001 From: abdellah-darni Date: Sat, 13 Jun 2026 11:23:27 +0100 Subject: [PATCH 3/3] format --- .../features/backend_setup/backend_setup_screen.dart | 12 ++++++++---- 1 file changed, 8 insertions(+), 4 deletions(-) diff --git a/client/deepdame/lib/features/backend_setup/backend_setup_screen.dart b/client/deepdame/lib/features/backend_setup/backend_setup_screen.dart index a1690a57..297dbf97 100644 --- a/client/deepdame/lib/features/backend_setup/backend_setup_screen.dart +++ b/client/deepdame/lib/features/backend_setup/backend_setup_screen.dart @@ -28,8 +28,7 @@ class BackendSetupScreen extends ConsumerStatefulWidget { const BackendSetupScreen({super.key}); @override - ConsumerState createState() => - _BackendSetupScreenState(); + ConsumerState createState() => _BackendSetupScreenState(); } class _BackendSetupScreenState extends ConsumerState { @@ -161,7 +160,9 @@ class _BackendSetupScreenState extends ConsumerState { border: const OutlineInputBorder( borderRadius: BorderRadius.all(Radius.circular(20)), ), - hintStyle: TextStyle(color: scheme.onSurface.withAlpha(160)), + hintStyle: TextStyle( + color: scheme.onSurface.withAlpha(160), + ), hintText: 'https://your-server.com', errorText: _fieldError, ), @@ -236,7 +237,10 @@ class _BackendSetupScreenState extends ConsumerState { ), child: const Text( 'Continue', - style: TextStyle(fontWeight: FontWeight.w600, fontSize: 16), + style: TextStyle( + fontWeight: FontWeight.w600, + fontSize: 16, + ), ), ), ),