mirror of
https://github.com/zarzet/SpotiFLAC-Mobile.git
synced 2026-08-18 08:57:13 +02:00
feat: remove default Spotify credentials, require user's own API key
- Remove hardcoded Spotify client ID/secret from Go backend - Spotify now requires user to provide their own credentials - Deezer remains free (no credentials required) - Update UI to show 'Free' badge for Deezer, 'API Key' for Spotify - Show warning card when Spotify selected without credentials - Add hasSpotifyCredentials check to platform bridge
This commit is contained in:
@@ -60,18 +60,16 @@ class SettingsNotifier extends Notifier<AppSettings> {
|
||||
|
||||
/// Apply current Spotify credentials to Go backend
|
||||
Future<void> _applySpotifyCredentials() async {
|
||||
// Only apply custom credentials if enabled and both fields are set
|
||||
if (state.useCustomSpotifyCredentials &&
|
||||
state.spotifyClientId.isNotEmpty &&
|
||||
// Only apply if both fields are set
|
||||
if (state.spotifyClientId.isNotEmpty &&
|
||||
state.spotifyClientSecret.isNotEmpty) {
|
||||
await PlatformBridge.setSpotifyCredentials(
|
||||
state.spotifyClientId,
|
||||
state.spotifyClientSecret,
|
||||
);
|
||||
} else {
|
||||
// Clear to use default
|
||||
await PlatformBridge.setSpotifyCredentials('', '');
|
||||
}
|
||||
// Note: If credentials are empty, Spotify API will return error
|
||||
// User should use Deezer as metadata source instead
|
||||
}
|
||||
|
||||
void setDefaultService(String service) {
|
||||
|
||||
@@ -76,38 +76,50 @@ class OptionsSettingsPage extends ConsumerWidget {
|
||||
.setMetadataSource(v),
|
||||
),
|
||||
if (settings.metadataSource == 'spotify') ...[
|
||||
SettingsSwitchItem(
|
||||
icon: Icons.toggle_on,
|
||||
title: 'Use Custom Credentials',
|
||||
subtitle: settings.useCustomSpotifyCredentials
|
||||
? 'Using your credentials'
|
||||
: 'Using default credentials',
|
||||
value: settings.useCustomSpotifyCredentials,
|
||||
onChanged: (v) {
|
||||
ref
|
||||
.read(settingsProvider.notifier)
|
||||
.setUseCustomSpotifyCredentials(v);
|
||||
if (v && settings.spotifyClientId.isEmpty) {
|
||||
_showSpotifyCredentialsDialog(context, ref, settings);
|
||||
}
|
||||
},
|
||||
showDivider: true,
|
||||
),
|
||||
// Info card about Spotify credentials requirement
|
||||
if (settings.spotifyClientId.isEmpty)
|
||||
Padding(
|
||||
padding: const EdgeInsets.fromLTRB(16, 8, 16, 8),
|
||||
child: Card(
|
||||
color: Theme.of(context).colorScheme.errorContainer,
|
||||
child: Padding(
|
||||
padding: const EdgeInsets.all(12),
|
||||
child: Row(
|
||||
children: [
|
||||
Icon(
|
||||
Icons.warning_amber_rounded,
|
||||
color: Theme.of(context).colorScheme.onErrorContainer,
|
||||
),
|
||||
const SizedBox(width: 12),
|
||||
Expanded(
|
||||
child: Text(
|
||||
'Spotify requires your own API credentials. Get them free from developer.spotify.com',
|
||||
style: TextStyle(
|
||||
color: Theme.of(context).colorScheme.onErrorContainer,
|
||||
fontSize: 12,
|
||||
),
|
||||
),
|
||||
),
|
||||
],
|
||||
),
|
||||
),
|
||||
),
|
||||
),
|
||||
SettingsItem(
|
||||
icon: Icons.key,
|
||||
title: 'Set Credentials',
|
||||
title: 'Spotify Credentials',
|
||||
subtitle: settings.spotifyClientId.isNotEmpty
|
||||
? 'Client ID: ${settings.spotifyClientId.length > 8 ? '${settings.spotifyClientId.substring(0, 8)}...' : settings.spotifyClientId}'
|
||||
: 'Not configured',
|
||||
: 'Required - tap to configure',
|
||||
onTap: () =>
|
||||
_showSpotifyCredentialsDialog(context, ref, settings),
|
||||
trailing: Icon(
|
||||
settings.spotifyClientId.isNotEmpty
|
||||
? Icons.edit
|
||||
: Icons.add,
|
||||
? Icons.check_circle
|
||||
: Icons.error_outline,
|
||||
color: settings.spotifyClientId.isNotEmpty
|
||||
? Theme.of(context).colorScheme.onSurfaceVariant
|
||||
: Theme.of(context).colorScheme.primary,
|
||||
? Theme.of(context).colorScheme.primary
|
||||
: Theme.of(context).colorScheme.error,
|
||||
size: 20,
|
||||
),
|
||||
showDivider: false,
|
||||
@@ -820,6 +832,8 @@ class _MetadataSourceSelector extends ConsumerWidget {
|
||||
_SourceChip(
|
||||
icon: Icons.graphic_eq,
|
||||
label: 'Deezer',
|
||||
badge: 'Free',
|
||||
badgeColor: colorScheme.tertiary,
|
||||
// Not selected if extension is active
|
||||
isSelected: currentSource == 'deezer' && !hasExtensionSearch,
|
||||
onTap: () {
|
||||
@@ -834,6 +848,8 @@ class _MetadataSourceSelector extends ConsumerWidget {
|
||||
_SourceChip(
|
||||
icon: Icons.music_note,
|
||||
label: 'Spotify',
|
||||
badge: 'API Key',
|
||||
badgeColor: colorScheme.secondary,
|
||||
// Not selected if extension is active
|
||||
isSelected: currentSource == 'spotify' && !hasExtensionSearch,
|
||||
onTap: () {
|
||||
@@ -878,12 +894,16 @@ class _SourceChip extends StatelessWidget {
|
||||
final String label;
|
||||
final bool isSelected;
|
||||
final VoidCallback? onTap;
|
||||
final String? badge;
|
||||
final Color? badgeColor;
|
||||
|
||||
const _SourceChip({
|
||||
required this.icon,
|
||||
required this.label,
|
||||
required this.isSelected,
|
||||
this.onTap,
|
||||
this.badge,
|
||||
this.badgeColor,
|
||||
});
|
||||
|
||||
@override
|
||||
@@ -929,6 +949,24 @@ class _SourceChip extends StatelessWidget {
|
||||
: colorScheme.onSurfaceVariant,
|
||||
),
|
||||
),
|
||||
if (badge != null) ...[
|
||||
const SizedBox(height: 4),
|
||||
Container(
|
||||
padding: const EdgeInsets.symmetric(horizontal: 6, vertical: 2),
|
||||
decoration: BoxDecoration(
|
||||
color: (badgeColor ?? colorScheme.tertiary).withValues(alpha: 0.2),
|
||||
borderRadius: BorderRadius.circular(4),
|
||||
),
|
||||
child: Text(
|
||||
badge!,
|
||||
style: TextStyle(
|
||||
fontSize: 9,
|
||||
fontWeight: FontWeight.w500,
|
||||
color: badgeColor ?? colorScheme.tertiary,
|
||||
),
|
||||
),
|
||||
),
|
||||
],
|
||||
],
|
||||
),
|
||||
),
|
||||
|
||||
@@ -380,11 +380,10 @@ class _SetupScreenState extends ConsumerState<SetupScreen> {
|
||||
_clientIdController.text.trim(),
|
||||
_clientSecretController.text.trim(),
|
||||
);
|
||||
ref.read(settingsProvider.notifier).setUseCustomSpotifyCredentials(true);
|
||||
// Set search source to Spotify when using custom credentials
|
||||
// Set search source to Spotify when credentials are provided
|
||||
ref.read(settingsProvider.notifier).setMetadataSource('spotify');
|
||||
} else {
|
||||
// Use Deezer as default search source
|
||||
// Use Deezer as default search source (free, no credentials required)
|
||||
ref.read(settingsProvider.notifier).setMetadataSource('deezer');
|
||||
}
|
||||
|
||||
|
||||
@@ -331,7 +331,6 @@ class PlatformBridge {
|
||||
}
|
||||
|
||||
/// Set custom Spotify API credentials
|
||||
/// Pass empty strings to use default credentials
|
||||
static Future<void> setSpotifyCredentials(String clientId, String clientSecret) async {
|
||||
await _channel.invokeMethod('setSpotifyCredentials', {
|
||||
'client_id': clientId,
|
||||
@@ -339,6 +338,13 @@ class PlatformBridge {
|
||||
});
|
||||
}
|
||||
|
||||
/// Check if Spotify credentials are configured
|
||||
/// Returns true if credentials are available (custom or env vars)
|
||||
static Future<bool> hasSpotifyCredentials() async {
|
||||
final result = await _channel.invokeMethod('hasSpotifyCredentials');
|
||||
return result as bool;
|
||||
}
|
||||
|
||||
/// Pre-warm track ID cache for album/playlist tracks
|
||||
/// This runs in background and returns immediately
|
||||
/// Speeds up subsequent downloads by caching ISRC → Track ID mappings
|
||||
|
||||
Reference in New Issue
Block a user