Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
8 changes: 4 additions & 4 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -130,12 +130,12 @@ FullStop 的**迷你模式**让播放器化身为屏幕角落的一抹优雅—
## 开始使用

### 准备工作
1. **Spotify Premium** 订阅。
2. 一个 Spotify 开发者 App 的 `Client ID` 和 `Secret`
1. **Spotify Premium** 订阅。
2. 下载并打开 FullStop,点击「连接 Spotify」即可完成授权。无需任何额外配置

### 安装 (iOS)
1. 在 [Releases](https://github.com/0Chencc/FullStop/releases) 下载最新的 `.ipa` 文件。
2. 使用 **AltStore**、**Sideloadly** 或 **TrollStore** 进行自签安装。
1. 在 [Releases](https://github.com/0Chencc/FullStop/releases) 下载最新的 `.ipa` 文件。
2. 使用 **AltStore**、**Sideloadly** 或 **TrollStore** 进行自签安装。

---

Expand Down
2 changes: 1 addition & 1 deletion README_en.md
Original file line number Diff line number Diff line change
Expand Up @@ -133,7 +133,7 @@ Click the picture-in-picture button in the title bar, and the window shrinks ins

### Prerequisites
1. **Spotify Premium** subscription.
2. A Spotify Developer App with `Client ID` and `Secret`.
2. Download and open FullStop, click "Connect with Spotify" to authorize. No additional configuration needed.

### Installation (iOS)
1. Download the latest `.ipa` file from [Releases](https://github.com/0Chencc/FullStop/releases).
Expand Down
2 changes: 1 addition & 1 deletion README_ja.md
Original file line number Diff line number Diff line change
Expand Up @@ -133,7 +133,7 @@ FullStopの**ミニモード**は、プレーヤーを画面の隅にそっと

### 必要なもの
1. **Spotify Premium** サブスクリプション。
2. Spotify開発者アプリの `Client ID``Secret`
2. FullStop をダウンロードして開き、「Spotify に接続」をクリックするだけで認証完了。追加の設定は不要です

### インストール (iOS)
1. [Releases](https://github.com/0Chencc/FullStop/releases) から最新の `.ipa` ファイルをダウンロード。
Expand Down
Binary file added img/ios.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
52 changes: 7 additions & 45 deletions lib/app.dart
Original file line number Diff line number Diff line change
Expand Up @@ -6,15 +6,13 @@ import 'package:window_manager/window_manager.dart';
import 'l10n/app_localizations.dart';
import 'application/di/core_providers.dart' show sharedPrefsProvider;
import 'application/providers/auth_provider.dart';
import 'application/providers/credentials_provider.dart';
import 'application/providers/locale_provider.dart';
import 'application/providers/navigation_provider.dart';
import 'application/providers/playback_provider.dart';
import 'domain/entities/playback_state.dart';
import 'core/services/system_tray_service.dart';
import 'presentation/screens/home_screen.dart';
import 'presentation/screens/login_screen.dart';
import 'presentation/screens/setup_guide_screen.dart';
import 'presentation/themes/app_theme.dart';
import 'presentation/widgets/custom_title_bar.dart';
import 'presentation/widgets/mini_player_content.dart';
Expand Down Expand Up @@ -191,7 +189,7 @@ class _AppShellState extends ConsumerState<_AppShell> with WindowListener {
key: _navigatorKey,
onGenerateRoute: (settings) {
return MaterialPageRoute(
builder: (context) => const _AppRouter(),
builder: (context) => const _AuthRouter(),
settings: settings,
);
},
Expand All @@ -216,55 +214,19 @@ class _AppShellState extends ConsumerState<_AppShell> with WindowListener {
],
);
}
return const _AppRouter();
return const _AuthRouter();
}
}

class _AppRouter extends ConsumerStatefulWidget {
const _AppRouter();
/// Routes directly to auth flow — no setup guard needed with PKCE.
class _AuthRouter extends ConsumerStatefulWidget {
const _AuthRouter();

@override
ConsumerState<_AppRouter> createState() => _AppRouterState();
ConsumerState<_AuthRouter> createState() => _AuthRouterState();
}

class _AppRouterState extends ConsumerState<_AppRouter> {
bool _setupComplete = false;

void _onSetupComplete() {
setState(() {
_setupComplete = true;
});
// After setup, check auth status
ref.read(authProvider.notifier).checkAuthStatus();
}

@override
Widget build(BuildContext context) {
final credentialsState = ref.watch(credentialsProvider);

// Show loading while checking credentials
if (credentialsState.isLoading) {
return const Scaffold(body: Center(child: CircularProgressIndicator()));
}

// If credentials not configured and setup not complete, show setup guide
if (!credentialsState.hasSpotifyCredentials && !_setupComplete) {
return SetupGuideScreen(onSetupComplete: _onSetupComplete);
}

// Credentials are configured, show auth flow
return const _AuthWrapper();
}
}

class _AuthWrapper extends ConsumerStatefulWidget {
const _AuthWrapper();

@override
ConsumerState<_AuthWrapper> createState() => _AuthWrapperState();
}

class _AuthWrapperState extends ConsumerState<_AuthWrapper> {
class _AuthRouterState extends ConsumerState<_AuthRouter> {
bool _hasCheckedAuth = false;

@override
Expand Down
21 changes: 9 additions & 12 deletions lib/application/di/auth_providers.dart
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,7 @@ import '../../data/datasources/credentials_shared_prefs_datasource.dart';
import '../../data/repositories/auth_repository_impl.dart';
import '../../domain/entities/proxy_settings.dart';
import '../../domain/repositories/auth_repository.dart';
import '../providers/credentials_provider.dart';
import 'core_providers.dart';
import 'spotify_providers.dart';

Expand Down Expand Up @@ -50,17 +51,17 @@ final oauthServiceProvider = Provider<OAuthService>((ref) {
// Auth Repository
final authRepositoryProvider = Provider<AuthRepository>((ref) {
final localDataSource = ref.watch(authLocalDataSourceProvider);
final credentialsDataSource = ref.watch(credentialsLocalDataSourceProvider);
final apiClient = ref.watch(spotifyApiClientProvider);
final dio = ref.watch(authDioProvider);
final oauthService = ref.watch(oauthServiceProvider);
final clientId = ref.watch(effectiveSpotifyClientIdProvider);

return AuthRepositoryImpl(
localDataSource: localDataSource,
credentialsDataSource: credentialsDataSource,
apiClient: apiClient,
dio: dio,
oauthService: oauthService,
clientId: clientId,
);
});

Expand All @@ -73,8 +74,6 @@ class _PlaceholderCredentialsDataSource implements CredentialsLocalDataSource {
@override
Future<void> clearAppProxySettings() async {}
@override
Future<void> clearSpotifyCredentials() async {}
@override
Future<bool> getAudioFeaturesEnabled() async => false;
@override
Future<String?> getGetSongBpmApiKey() async => null;
Expand All @@ -89,25 +88,23 @@ class _PlaceholderCredentialsDataSource implements CredentialsLocalDataSource {
@override
Future<AppProxySettings> getAppProxySettings() async => const AppProxySettings();
@override
Future<String?> getSpotifyClientId() async => null;
@override
Future<String?> getSpotifyClientSecret() async => null;
@override
Future<bool> hasGetSongBpmApiKey() async => false;
@override
Future<bool> hasLlmConfig() async => false;
@override
Future<bool> hasSpotifyCredentials() async => false;
@override
Future<void> saveAppProxySettings(AppProxySettings config) async {}
@override
Future<void> saveLlmCredentials({String apiKey = '', required String model, required String baseUrl}) async {}
@override
Future<void> saveSpotifyCredentials({required String clientId, required String clientSecret}) async {}
@override
Future<void> setAudioFeaturesEnabled(bool enabled) async {}
@override
Future<void> setGetSongBpmApiKey(String apiKey) async {}
@override
Future<void> setGpuAccelerationEnabled(bool enabled) async {}
@override
Future<String?> getCustomSpotifyClientId() async => null;
@override
Future<void> saveCustomSpotifyClientId(String clientId) async {}
@override
Future<void> clearCustomSpotifyClientId() async {}
}
23 changes: 13 additions & 10 deletions lib/application/di/core_providers.dart
Original file line number Diff line number Diff line change
Expand Up @@ -13,14 +13,13 @@ import '../../core/services/token_refresh_service.dart';
import '../../core/services/url_launcher_service.dart';
import '../../core/services/window_service.dart';
import '../../core/utils/logger.dart';
import '../providers/credentials_provider.dart';
import '../../data/datasources/auth_local_datasource.dart';
import '../../data/datasources/auth_shared_prefs_datasource.dart';
import '../../data/datasources/credentials_local_datasource.dart';
import '../../domain/entities/proxy_settings.dart';
import '../../data/services/app_links_deep_link_service.dart';
import '../../data/services/default_url_launcher_service.dart';
import '../../data/services/window_manager_service.dart';
import 'auth_providers.dart' show credentialsLocalDataSourceProvider;

// SharedPreferences provider for macOS/iOS
final sharedPrefsProvider = FutureProvider<SharedPreferences>((ref) async {
Expand Down Expand Up @@ -99,17 +98,21 @@ final authDioProvider = Provider<Dio>((ref) {

// Token Refresh Service (lazily initialized to avoid circular dependencies)
TokenRefreshService? _tokenRefreshService;
String? _lastTokenRefreshClientId;

TokenRefreshService _getOrCreateTokenRefreshService(
AuthLocalDataSource authDataSource,
CredentialsLocalDataSource credentialsDataSource,
Dio authDio,
String clientId,
) {
_tokenRefreshService ??= TokenRefreshService(
authDataSource: authDataSource,
credentialsDataSource: credentialsDataSource,
dio: authDio,
);
if (_tokenRefreshService == null || _lastTokenRefreshClientId != clientId) {
_tokenRefreshService = TokenRefreshService(
clientId: clientId,
authDataSource: authDataSource,
dio: authDio,
);
_lastTokenRefreshClientId = clientId;
}
return _tokenRefreshService!;
}

Expand Down Expand Up @@ -150,12 +153,12 @@ final apiDioProvider = Provider<Dio>((ref) {

// Get fresh data sources for token refresh
final authLocalDataSource = ref.read(authLocalDataSourceProvider);
final credentialsDataSource = ref.read(credentialsLocalDataSourceProvider);

final clientId = ref.read(effectiveSpotifyClientIdProvider);
final tokenRefreshService = _getOrCreateTokenRefreshService(
authLocalDataSource,
credentialsDataSource,
authDio,
clientId,
);

final newToken = await tokenRefreshService.refreshToken();
Expand Down
Loading
Loading