From 7bf07d38bb7ca0fe4b12df5551a5480fb4c9391d Mon Sep 17 00:00:00 2001 From: 0chencc <19362246+0Chencc@users.noreply.github.com> Date: Wed, 11 Feb 2026 21:08:22 +0900 Subject: [PATCH] fix: remove default Client ID, require user-provided ID Spotify's new Development Mode rules (2025-02-11) limit each Client ID to 5 authorized users, making shared public IDs unviable. - Remove hardcoded spotifyClientId from AppConfig - effectiveSpotifyClientIdProvider no longer falls back to a default - Login screen: Client ID input always visible and required before login - Settings: Client ID section promoted from hidden advanced option - Updated l10n descriptions (zh/en/ja) to reflect requirement --- .../providers/credentials_provider.dart | 10 +- lib/core/config/app_config.dart | 3 - lib/l10n/app_en.arb | 2 +- lib/l10n/app_ja.arb | 2 +- lib/l10n/app_localizations.dart | 2 +- lib/l10n/app_localizations_en.dart | 2 +- lib/l10n/app_localizations_ja.dart | 2 +- lib/l10n/app_localizations_zh.dart | 2 +- lib/l10n/app_zh.arb | 2 +- lib/presentation/screens/login_screen.dart | 292 +++++++----------- .../settings/api_credentials_section.dart | 73 +---- 11 files changed, 145 insertions(+), 247 deletions(-) diff --git a/lib/application/providers/credentials_provider.dart b/lib/application/providers/credentials_provider.dart index 52357d2..02c54a8 100644 --- a/lib/application/providers/credentials_provider.dart +++ b/lib/application/providers/credentials_provider.dart @@ -1,5 +1,4 @@ import 'package:flutter_riverpod/flutter_riverpod.dart'; -import '../../core/config/app_config.dart'; import '../../data/datasources/credentials_local_datasource.dart'; import '../di/injection_container.dart'; @@ -165,12 +164,9 @@ final credentialsProvider = return CredentialsNotifier(ref.watch(credentialsLocalDataSourceProvider)); }); -/// Derived provider that returns the effective Spotify Client ID -/// (custom > default) +/// Derived provider that returns the user-configured Spotify Client ID. +/// Returns empty string if not configured — login screen guards this. final effectiveSpotifyClientIdProvider = Provider((ref) { final creds = ref.watch(credentialsProvider); - final custom = creds.customSpotifyClientId; - return (custom != null && custom.isNotEmpty) - ? custom - : AppConfig.spotifyClientId; + return creds.customSpotifyClientId ?? ''; }); diff --git a/lib/core/config/app_config.dart b/lib/core/config/app_config.dart index 87e84bb..90a840b 100644 --- a/lib/core/config/app_config.dart +++ b/lib/core/config/app_config.dart @@ -1,7 +1,4 @@ class AppConfig { - // Spotify Client ID (public client — safe to embed, PKCE replaces client_secret) - static const String spotifyClientId = '94fcde08534f4025a402cd2bba93e1f0'; - // Custom URL scheme for OAuth callback static const String urlScheme = 'fullstop'; diff --git a/lib/l10n/app_en.arb b/lib/l10n/app_en.arb index 046ba6b..0334137 100644 --- a/lib/l10n/app_en.arb +++ b/lib/l10n/app_en.arb @@ -1439,7 +1439,7 @@ "description": "Custom Spotify Client ID field label" }, - "customClientIdDescription": "The shared Client ID may be rate-limited by Spotify under heavy usage. You can create your own app on Spotify Developer Dashboard and use your own Client ID.", + "customClientIdDescription": "Spotify requires each developer to use their own Client ID. Create an app on Spotify Developer Dashboard and enter the Client ID below.", "@customClientIdDescription": { "description": "Description explaining why custom Client ID may be needed" }, diff --git a/lib/l10n/app_ja.arb b/lib/l10n/app_ja.arb index c89e3f0..fd3aaf1 100644 --- a/lib/l10n/app_ja.arb +++ b/lib/l10n/app_ja.arb @@ -472,7 +472,7 @@ "customClientId": "カスタム Client ID", - "customClientIdDescription": "共有 Client ID は Spotify のレート制限を受ける可能性があります。Spotify Developer Dashboard で独自のアプリを作成し、自分の Client ID を使用できます。", + "customClientIdDescription": "Spotifyでは各開発者が独自のClient IDを使用する必要があります。Spotify Developer Dashboardでアプリを作成し、以下にClient IDを入力してください。", "customClientIdHint": "Spotify Client ID を入力", diff --git a/lib/l10n/app_localizations.dart b/lib/l10n/app_localizations.dart index 32b6874..e8cd2f4 100644 --- a/lib/l10n/app_localizations.dart +++ b/lib/l10n/app_localizations.dart @@ -1705,7 +1705,7 @@ abstract class AppLocalizations { /// Description explaining why custom Client ID may be needed /// /// In en, this message translates to: - /// **'The shared Client ID may be rate-limited by Spotify under heavy usage. You can create your own app on Spotify Developer Dashboard and use your own Client ID.'** + /// **'Spotify requires each developer to use their own Client ID. Create an app on Spotify Developer Dashboard and enter the Client ID below.'** String get customClientIdDescription; /// Hint for custom Client ID input diff --git a/lib/l10n/app_localizations_en.dart b/lib/l10n/app_localizations_en.dart index 82609c9..e046eaa 100644 --- a/lib/l10n/app_localizations_en.dart +++ b/lib/l10n/app_localizations_en.dart @@ -915,7 +915,7 @@ class AppLocalizationsEn extends AppLocalizations { @override String get customClientIdDescription => - 'The shared Client ID may be rate-limited by Spotify under heavy usage. You can create your own app on Spotify Developer Dashboard and use your own Client ID.'; + 'Spotify requires each developer to use their own Client ID. Create an app on Spotify Developer Dashboard and enter the Client ID below.'; @override String get customClientIdHint => 'Enter your Spotify Client ID'; diff --git a/lib/l10n/app_localizations_ja.dart b/lib/l10n/app_localizations_ja.dart index e842730..f987a37 100644 --- a/lib/l10n/app_localizations_ja.dart +++ b/lib/l10n/app_localizations_ja.dart @@ -897,7 +897,7 @@ class AppLocalizationsJa extends AppLocalizations { @override String get customClientIdDescription => - '共有 Client ID は Spotify のレート制限を受ける可能性があります。Spotify Developer Dashboard で独自のアプリを作成し、自分の Client ID を使用できます。'; + 'Spotifyでは各開発者が独自のClient IDを使用する必要があります。Spotify Developer Dashboardでアプリを作成し、以下にClient IDを入力してください。'; @override String get customClientIdHint => 'Spotify Client ID を入力'; diff --git a/lib/l10n/app_localizations_zh.dart b/lib/l10n/app_localizations_zh.dart index 2910347..82f16bd 100644 --- a/lib/l10n/app_localizations_zh.dart +++ b/lib/l10n/app_localizations_zh.dart @@ -893,7 +893,7 @@ class AppLocalizationsZh extends AppLocalizations { @override String get customClientIdDescription => - '公用 Client ID 在高频请求时可能被 Spotify 限流。你可以在 Spotify Developer Dashboard 创建自己的应用,使用自己的 Client ID。'; + 'Spotify 要求每位开发者使用自己的 Client ID。请前往 Spotify Developer Dashboard 创建应用,将 Client ID 填入下方。'; @override String get customClientIdHint => '输入你的 Spotify Client ID'; diff --git a/lib/l10n/app_zh.arb b/lib/l10n/app_zh.arb index 84ebfcb..a0f33c4 100644 --- a/lib/l10n/app_zh.arb +++ b/lib/l10n/app_zh.arb @@ -530,7 +530,7 @@ "customClientId": "自定义 Client ID", - "customClientIdDescription": "公用 Client ID 在高频请求时可能被 Spotify 限流。你可以在 Spotify Developer Dashboard 创建自己的应用,使用自己的 Client ID。", + "customClientIdDescription": "Spotify 要求每位开发者使用自己的 Client ID。请前往 Spotify Developer Dashboard 创建应用,将 Client ID 填入下方。", "customClientIdHint": "输入你的 Spotify Client ID", diff --git a/lib/presentation/screens/login_screen.dart b/lib/presentation/screens/login_screen.dart index a53416c..b549d04 100644 --- a/lib/presentation/screens/login_screen.dart +++ b/lib/presentation/screens/login_screen.dart @@ -4,6 +4,7 @@ import 'package:flutter_riverpod/flutter_riverpod.dart'; import '../../l10n/app_localizations.dart'; import '../../application/providers/auth_provider.dart'; import '../../application/providers/credentials_provider.dart'; +import '../../core/config/app_config.dart'; import '../themes/app_theme.dart'; import '../widgets/app_logo.dart'; @@ -15,11 +16,34 @@ class LoginScreen extends ConsumerStatefulWidget { } class _LoginScreenState extends ConsumerState { - bool _advancedExpanded = false; final _clientIdController = TextEditingController(); + bool _clientIdSaved = false; + + @override + void initState() { + super.initState(); + // Load existing client ID if any + final creds = ref.read(credentialsProvider); + if (creds.customSpotifyClientId != null && + creds.customSpotifyClientId!.isNotEmpty) { + _clientIdController.text = creds.customSpotifyClientId!; + _clientIdSaved = true; + } + _clientIdController.addListener(_onClientIdChanged); + } + + void _onClientIdChanged() { + // Mark unsaved when text changes after a save + if (_clientIdSaved && + _clientIdController.text.trim() != + ref.read(credentialsProvider).customSpotifyClientId) { + setState(() => _clientIdSaved = false); + } + } @override void dispose() { + _clientIdController.removeListener(_onClientIdChanged); _clientIdController.dispose(); super.dispose(); } @@ -28,6 +52,9 @@ class _LoginScreenState extends ConsumerState { Widget build(BuildContext context) { final authState = ref.watch(authProvider); final isLoading = authState.status == AuthStatus.loading; + final l10n = AppLocalizations.of(context)!; + final effectiveClientId = ref.watch(effectiveSpotifyClientIdProvider); + final hasClientId = effectiveClientId.isNotEmpty && _clientIdSaved; return Scaffold( body: SafeArea( @@ -44,12 +71,10 @@ class _LoginScreenState extends ConsumerState { mainAxisAlignment: MainAxisAlignment.center, children: [ const SizedBox(height: 32), - // Logo const AppLogo(size: 120), const SizedBox(height: 32), - // Title Text( - AppLocalizations.of(context)!.appTitle, + l10n.appTitle, style: const TextStyle( fontSize: 28, fontWeight: FontWeight.bold, @@ -57,16 +82,15 @@ class _LoginScreenState extends ConsumerState { textAlign: TextAlign.center, ), const SizedBox(height: 16), - // Subtitle Text( - AppLocalizations.of(context)!.focusOnFavoriteArtists, + l10n.focusOnFavoriteArtists, style: TextStyle( fontSize: 16, color: AppTheme.spotifyLightGray, ), textAlign: TextAlign.center, ), - const SizedBox(height: 48), + const SizedBox(height: 36), // Loading state with cancel option if (isLoading) _buildLoadingState(context, ref), @@ -75,23 +99,23 @@ class _LoginScreenState extends ConsumerState { if (authState.status == AuthStatus.error) _buildErrorMessage(context, ref, authState.errorMessage), - // Login button (hidden during loading) + // Client ID input (always visible, required) + if (!isLoading) _buildClientIdSection(context, l10n), + + const SizedBox(height: 20), + + // Login button (disabled without client ID) if (!isLoading) SizedBox( width: double.infinity, child: ElevatedButton.icon( - onPressed: () => ref.read(authProvider.notifier).login(), + onPressed: hasClientId + ? () => ref.read(authProvider.notifier).login() + : null, icon: const Icon(Icons.login), - label: Text( - AppLocalizations.of(context)!.connectWithSpotify, - ), + label: Text(l10n.connectWithSpotify), ), ), - const SizedBox(height: 16), - - // Advanced options (custom Client ID) - if (!isLoading) _buildAdvancedSection(context), - const SizedBox(height: 24), // Info section @@ -105,17 +129,17 @@ class _LoginScreenState extends ConsumerState { children: [ _buildInfoRow( Icons.lock_outline, - AppLocalizations.of(context)!.credentialsStayOnDevice, + l10n.credentialsStayOnDevice, ), const SizedBox(height: 8), _buildInfoRow( Icons.speaker_group, - AppLocalizations.of(context)!.controlsExistingSession, + l10n.controlsExistingSession, ), const SizedBox(height: 8), _buildInfoRow( Icons.workspace_premium, - AppLocalizations.of(context)!.requiresPremium, + l10n.requiresPremium, ), ], ), @@ -129,136 +153,94 @@ class _LoginScreenState extends ConsumerState { ); } - Widget _buildAdvancedSection(BuildContext context) { - final l10n = AppLocalizations.of(context)!; - final creds = ref.watch(credentialsProvider); - final hasCustom = - creds.customSpotifyClientId != null && - creds.customSpotifyClientId!.isNotEmpty; - final effectiveClientId = ref.watch(effectiveSpotifyClientIdProvider); - - return Column( - children: [ - // Clickable row to expand/collapse - InkWell( - borderRadius: BorderRadius.circular(8), - onTap: () { - setState(() { - _advancedExpanded = !_advancedExpanded; - if (_advancedExpanded && hasCustom) { - _clientIdController.text = creds.customSpotifyClientId!; - } - }); - }, - child: Padding( - padding: const EdgeInsets.symmetric(vertical: 8, horizontal: 4), - child: Row( - mainAxisAlignment: MainAxisAlignment.center, - mainAxisSize: MainAxisSize.min, - children: [ - Icon( - Icons.tune, - size: 16, - color: AppTheme.spotifyLightGray, - ), - const SizedBox(width: 6), - Text( - l10n.advancedOptions, - style: TextStyle( - fontSize: 13, - color: AppTheme.spotifyLightGray, - ), - ), - Icon( - _advancedExpanded ? Icons.expand_less : Icons.expand_more, - size: 18, - color: AppTheme.spotifyLightGray, - ), - ], + Widget _buildClientIdSection(BuildContext context, AppLocalizations l10n) { + return Container( + width: double.infinity, + padding: const EdgeInsets.all(16), + decoration: BoxDecoration( + color: AppTheme.spotifyDarkGray, + borderRadius: BorderRadius.circular(12), + ), + child: Column( + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + Text( + l10n.customClientIdDescription, + style: TextStyle( + fontSize: 12, + color: AppTheme.spotifyLightGray.withValues(alpha: 0.8), ), ), - ), - if (_advancedExpanded) ...[ + const SizedBox(height: 12), + TextField( + controller: _clientIdController, + decoration: InputDecoration( + labelText: l10n.customClientId, + hintText: l10n.customClientIdHint, + border: const OutlineInputBorder(), + isDense: true, + suffixIcon: _clientIdSaved + ? Icon(Icons.check_circle, color: AppTheme.spotifyGreen, size: 20) + : null, + ), + style: const TextStyle(fontFamily: 'monospace', fontSize: 13), + ), const SizedBox(height: 8), + Row( + mainAxisAlignment: MainAxisAlignment.end, + children: [ + FilledButton( + onPressed: () => _saveClientId(l10n), + child: Text(l10n.save), + ), + ], + ), + const SizedBox(height: 8), + // Redirect URI info Container( - width: double.infinity, - padding: const EdgeInsets.all(16), + padding: const EdgeInsets.all(10), decoration: BoxDecoration( - color: AppTheme.spotifyDarkGray, - borderRadius: BorderRadius.circular(12), + color: Colors.blue.withValues(alpha: 0.1), + borderRadius: BorderRadius.circular(8), + border: Border.all(color: Colors.blue.withValues(alpha: 0.3)), ), - child: Column( - crossAxisAlignment: CrossAxisAlignment.start, + child: Row( children: [ - // Current status - Row( - children: [ - Icon(Icons.key, size: 16, color: AppTheme.spotifyLightGray), - const SizedBox(width: 8), - Expanded( - child: Text( - l10n.configured(_maskClientId(effectiveClientId)), - style: TextStyle( - fontSize: 12, - color: hasCustom - ? AppTheme.spotifyGreen - : AppTheme.spotifyLightGray, + const Icon(Icons.info_outline, color: Colors.blue, size: 18), + const SizedBox(width: 10), + Expanded( + child: Column( + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + Text( + l10n.redirectUriForSpotifyApp, + style: const TextStyle( + fontWeight: FontWeight.bold, + color: Colors.blue, + fontSize: 11, ), ), - ), - ], - ), - const SizedBox(height: 12), - Text( - l10n.customClientIdDescription, - style: TextStyle( - fontSize: 12, - color: AppTheme.spotifyLightGray.withValues(alpha: 0.8), - ), - ), - const SizedBox(height: 12), - TextField( - controller: _clientIdController, - decoration: InputDecoration( - labelText: l10n.customClientId, - hintText: l10n.customClientIdHint, - border: const OutlineInputBorder(), - isDense: true, - suffixIcon: hasCustom - ? IconButton( - icon: const Icon(Icons.clear, size: 18), - tooltip: l10n.useDefaultClient, - onPressed: () => _clearCustomClientId(l10n), - ) - : null, - ), - style: const TextStyle(fontFamily: 'monospace', fontSize: 13), - ), - const SizedBox(height: 8), - Row( - mainAxisAlignment: MainAxisAlignment.end, - children: [ - if (hasCustom) - TextButton( - onPressed: () => _clearCustomClientId(l10n), - child: Text(l10n.useDefaultClient), + const SizedBox(height: 2), + SelectableText( + AppConfig.spotifyRedirectUri, + style: const TextStyle( + fontFamily: 'monospace', + fontSize: 11, + color: Colors.blue, + ), ), - const SizedBox(width: 8), - FilledButton( - onPressed: () => _saveCustomClientId(l10n), - child: Text(l10n.save), - ), - ], + ], + ), ), ], ), ), ], - ], + ), ); } - Future _saveCustomClientId(AppLocalizations l10n) async { + Future _saveClientId(AppLocalizations l10n) async { final value = _clientIdController.text.trim(); if (value.isEmpty) return; @@ -268,29 +250,13 @@ class _LoginScreenState extends ConsumerState { if (!mounted) return; if (success) { + setState(() => _clientIdSaved = true); ScaffoldMessenger.of(context).showSnackBar( SnackBar(content: Text(l10n.customClientIdSaved)), ); } } - Future _clearCustomClientId(AppLocalizations l10n) async { - final notifier = ref.read(credentialsProvider.notifier); - await notifier.clearCustomSpotifyClientId(); - _clientIdController.clear(); - - if (!mounted) return; - - ScaffoldMessenger.of(context).showSnackBar( - SnackBar(content: Text(l10n.customClientIdCleared)), - ); - } - - String _maskClientId(String clientId) { - if (clientId.length < 8) return '****'; - return '${clientId.substring(0, 4)}...${clientId.substring(clientId.length - 4)}'; - } - Widget _buildLoadingState(BuildContext context, WidgetRef ref) { return Container( padding: const EdgeInsets.all(24), @@ -302,7 +268,6 @@ class _LoginScreenState extends ConsumerState { ), child: Column( children: [ - // Animated loading indicator SizedBox( width: 60, height: 60, @@ -323,7 +288,6 @@ class _LoginScreenState extends ConsumerState { textAlign: TextAlign.center, ), const SizedBox(height: 16), - // Tip about browser Container( padding: const EdgeInsets.symmetric(horizontal: 12, vertical: 8), decoration: BoxDecoration( @@ -333,32 +297,22 @@ class _LoginScreenState extends ConsumerState { child: Row( mainAxisSize: MainAxisSize.min, children: [ - Icon( - Icons.info_outline, - size: 16, - color: AppTheme.spotifyGreen, - ), + Icon(Icons.info_outline, size: 16, color: AppTheme.spotifyGreen), const SizedBox(width: 8), Flexible( child: Text( AppLocalizations.of(context)!.afterAgreeCloseBrowser, - style: TextStyle( - fontSize: 12, - color: AppTheme.spotifyGreen, - ), + style: TextStyle(fontSize: 12, color: AppTheme.spotifyGreen), ), ), ], ), ), const SizedBox(height: 20), - // Cancel button - more prominent SizedBox( width: double.infinity, child: ElevatedButton.icon( - onPressed: () { - ref.read(authProvider.notifier).cancelLogin(); - }, + onPressed: () => ref.read(authProvider.notifier).cancelLogin(), icon: const Icon(Icons.cancel, size: 20), label: Text(AppLocalizations.of(context)!.cancelLogin), style: ElevatedButton.styleFrom( @@ -403,15 +357,10 @@ class _LoginScreenState extends ConsumerState { String _getErrorMessage(BuildContext context, String? error) { final l10n = AppLocalizations.of(context)!; if (error == null) return 'An unknown error occurred'; - final lowerError = error.toLowerCase(); - - // Parse common errors and provide user-friendly messages if (lowerError.contains('invalid_client') || lowerError.contains('invalid client')) { - if (lowerError.contains('redirect')) { - return l10n.errorRedirectUri; - } + if (lowerError.contains('redirect')) return l10n.errorRedirectUri; return l10n.errorInvalidClient; } if (lowerError.contains('redirect_uri') || @@ -436,8 +385,6 @@ class _LoginScreenState extends ConsumerState { if (lowerError.contains('certificate') || lowerError.contains('ssl')) { return 'SSL certificate error. Please ensure your browser accepts the local certificate.'; } - - // Return the original error if not matched return error; } @@ -473,7 +420,6 @@ class _LoginScreenState extends ConsumerState { ), ), ), - // Copy button IconButton( onPressed: () { Clipboard.setData(ClipboardData(text: rawError)); diff --git a/lib/presentation/widgets/settings/api_credentials_section.dart b/lib/presentation/widgets/settings/api_credentials_section.dart index 8f3748c..354f9cf 100644 --- a/lib/presentation/widgets/settings/api_credentials_section.dart +++ b/lib/presentation/widgets/settings/api_credentials_section.dart @@ -15,7 +15,7 @@ class ApiCredentialsSection extends ConsumerStatefulWidget { } class _ApiCredentialsSectionState extends ConsumerState { - bool _advancedExpanded = false; + bool _editing = false; final _clientIdController = TextEditingController(); @override @@ -28,10 +28,7 @@ class _ApiCredentialsSectionState extends ConsumerState { Widget build(BuildContext context) { final l10n = AppLocalizations.of(context)!; final effectiveClientId = ref.watch(effectiveSpotifyClientIdProvider); - final creds = ref.watch(credentialsProvider); - final hasCustom = - creds.customSpotifyClientId != null && - creds.customSpotifyClientId!.isNotEmpty; + final hasClientId = effectiveClientId.isNotEmpty; return Column( children: [ @@ -39,38 +36,29 @@ class _ApiCredentialsSectionState extends ConsumerState { leading: const Icon(Icons.key), title: Text(l10n.spotifyApi), subtitle: Text( - l10n.configured(_maskClientId(effectiveClientId)), - ), - trailing: Icon(Icons.check_circle, color: AppTheme.spotifyGreen), - ), - _buildRedirectUriInfo(context), - // Advanced options - ListTile( - leading: const Icon(Icons.tune), - title: Text(l10n.advancedOptions), - trailing: Icon( - _advancedExpanded ? Icons.expand_less : Icons.expand_more, + hasClientId + ? l10n.configured(_maskClientId(effectiveClientId)) + : l10n.customClientIdHint, ), + trailing: hasClientId + ? Icon(Icons.check_circle, color: AppTheme.spotifyGreen) + : const Icon(Icons.warning_amber, color: Colors.orange), onTap: () { setState(() { - _advancedExpanded = !_advancedExpanded; - if (_advancedExpanded && hasCustom) { - _clientIdController.text = creds.customSpotifyClientId!; + _editing = !_editing; + if (_editing && hasClientId) { + _clientIdController.text = effectiveClientId; } }); }, ), - if (_advancedExpanded) - _buildAdvancedOptions(context, l10n, hasCustom), + _buildRedirectUriInfo(context), + if (_editing) _buildEditSection(context, l10n), ], ); } - Widget _buildAdvancedOptions( - BuildContext context, - AppLocalizations l10n, - bool hasCustom, - ) { + Widget _buildEditSection(BuildContext context, AppLocalizations l10n) { return Container( margin: const EdgeInsets.symmetric(horizontal: 16, vertical: 8), padding: const EdgeInsets.all(12), @@ -97,13 +85,6 @@ class _ApiCredentialsSectionState extends ConsumerState { hintText: l10n.customClientIdHint, border: const OutlineInputBorder(), isDense: true, - suffixIcon: hasCustom - ? IconButton( - icon: const Icon(Icons.clear, size: 18), - tooltip: l10n.useDefaultClient, - onPressed: () => _clearCustomClientId(context, l10n), - ) - : null, ), style: const TextStyle(fontFamily: 'monospace', fontSize: 13), ), @@ -111,14 +92,8 @@ class _ApiCredentialsSectionState extends ConsumerState { Row( mainAxisAlignment: MainAxisAlignment.end, children: [ - if (hasCustom) - TextButton( - onPressed: () => _clearCustomClientId(context, l10n), - child: Text(l10n.useDefaultClient), - ), - const SizedBox(width: 8), FilledButton( - onPressed: () => _saveCustomClientId(context, l10n), + onPressed: () => _saveClientId(context, l10n), child: Text(l10n.save), ), ], @@ -128,7 +103,7 @@ class _ApiCredentialsSectionState extends ConsumerState { ); } - Future _saveCustomClientId( + Future _saveClientId( BuildContext context, AppLocalizations l10n, ) async { @@ -148,22 +123,6 @@ class _ApiCredentialsSectionState extends ConsumerState { } } - Future _clearCustomClientId( - BuildContext context, - AppLocalizations l10n, - ) async { - final notifier = ref.read(credentialsProvider.notifier); - await notifier.clearCustomSpotifyClientId(); - _clientIdController.clear(); - - if (!context.mounted) return; - - ScaffoldMessenger.of(context).showSnackBar( - SnackBar(content: Text(l10n.customClientIdCleared)), - ); - _showReauthDialog(context, l10n); - } - void _showReauthDialog(BuildContext context, AppLocalizations l10n) { showDialog( context: context,