Files
PinePods-nix/PinePods-0.8.2/mobile/lib/bloc/settings/settings_bloc.dart
2026-03-03 10:57:43 -05:00

363 lines
14 KiB
Dart

// Copyright 2020 Ben Hills and the project contributors. All rights reserved.
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
import 'package:pinepods_mobile/bloc/bloc.dart';
import 'package:pinepods_mobile/core/environment.dart';
import 'package:pinepods_mobile/entities/app_settings.dart';
import 'package:pinepods_mobile/entities/search_providers.dart';
import 'package:pinepods_mobile/services/settings/settings_service.dart';
import 'package:pinepods_mobile/services/pinepods/pinepods_service.dart';
import 'package:logging/logging.dart';
import 'package:rxdart/rxdart.dart';
class SettingsBloc extends Bloc {
final log = Logger('SettingsBloc');
final SettingsService _settingsService;
final BehaviorSubject<AppSettings> _settings = BehaviorSubject<AppSettings>.seeded(AppSettings.sensibleDefaults());
final BehaviorSubject<bool> _darkMode = BehaviorSubject<bool>();
final BehaviorSubject<String> _theme = BehaviorSubject<String>();
final BehaviorSubject<bool> _markDeletedAsPlayed = BehaviorSubject<bool>();
final BehaviorSubject<bool> _deleteDownloadedPlayedEpisodes = BehaviorSubject<bool>();
final BehaviorSubject<bool> _storeDownloadOnSDCard = BehaviorSubject<bool>();
final BehaviorSubject<double> _playbackSpeed = BehaviorSubject<double>();
final BehaviorSubject<String> _searchProvider = BehaviorSubject<String>();
final BehaviorSubject<bool> _externalLinkConsent = BehaviorSubject<bool>();
final BehaviorSubject<bool> _autoOpenNowPlaying = BehaviorSubject<bool>();
final BehaviorSubject<bool> _showFunding = BehaviorSubject<bool>();
final BehaviorSubject<bool> _trimSilence = BehaviorSubject<bool>();
final BehaviorSubject<bool> _volumeBoost = BehaviorSubject<bool>();
final BehaviorSubject<int> _autoUpdatePeriod = BehaviorSubject<int>();
final BehaviorSubject<int> _layoutMode = BehaviorSubject<int>();
final BehaviorSubject<String?> _pinepodsServer = BehaviorSubject<String?>();
final BehaviorSubject<String?> _pinepodsApiKey = BehaviorSubject<String?>();
final BehaviorSubject<int?> _pinepodsUserId = BehaviorSubject<int?>();
final BehaviorSubject<String?> _pinepodsUsername = BehaviorSubject<String?>();
final BehaviorSubject<String?> _pinepodsEmail = BehaviorSubject<String?>();
final BehaviorSubject<List<String>> _bottomBarOrder = BehaviorSubject<List<String>>();
var _currentSettings = AppSettings.sensibleDefaults();
SettingsBloc(this._settingsService) {
_init();
// Check if we need to fetch user details for existing login
_fetchUserDetailsIfNeeded();
}
Future<void> _fetchUserDetailsIfNeeded() async {
// Only fetch if we have server/api key but no username
if (_currentSettings.pinepodsServer != null &&
_currentSettings.pinepodsApiKey != null &&
(_currentSettings.pinepodsUsername == null || _currentSettings.pinepodsUsername!.isEmpty)) {
try {
final pinepodsService = PinepodsService();
pinepodsService.setCredentials(_currentSettings.pinepodsServer!, _currentSettings.pinepodsApiKey!);
// Use stored user ID if available, otherwise we need to get it somehow
final userId = _currentSettings.pinepodsUserId;
print('DEBUG: User ID from settings: $userId');
if (userId != null) {
final userDetails = await pinepodsService.getUserDetails(userId);
print('DEBUG: User details response: $userDetails');
if (userDetails != null) {
// Update settings with user details
final username = userDetails['Username'] ?? userDetails['username'] ?? '';
final email = userDetails['Email'] ?? userDetails['email'] ?? '';
print('DEBUG: Parsed username: "$username", email: "$email"');
setPinepodsUsername(username);
setPinepodsEmail(email);
}
}
} catch (e) {
// Silently fail - don't break the app if this fails
print('Failed to fetch user details on startup: $e');
}
}
}
void _init() {
/// Load all settings
// Add our available search providers.
var providers = <SearchProvider>[SearchProvider(key: 'itunes', name: 'iTunes')];
if (podcastIndexKey.isNotEmpty) {
providers.add(SearchProvider(key: 'podcastindex', name: 'PodcastIndex'));
}
_currentSettings = AppSettings(
theme: _settingsService.theme,
markDeletedEpisodesAsPlayed: _settingsService.markDeletedEpisodesAsPlayed,
deleteDownloadedPlayedEpisodes: _settingsService.deleteDownloadedPlayedEpisodes,
storeDownloadsSDCard: _settingsService.storeDownloadsSDCard,
playbackSpeed: _settingsService.playbackSpeed,
searchProvider: _settingsService.searchProvider,
searchProviders: providers,
externalLinkConsent: _settingsService.externalLinkConsent,
autoOpenNowPlaying: _settingsService.autoOpenNowPlaying,
showFunding: _settingsService.showFunding,
autoUpdateEpisodePeriod: _settingsService.autoUpdateEpisodePeriod,
trimSilence: _settingsService.trimSilence,
volumeBoost: _settingsService.volumeBoost,
layout: _settingsService.layoutMode,
pinepodsServer: _settingsService.pinepodsServer,
pinepodsApiKey: _settingsService.pinepodsApiKey,
pinepodsUserId: _settingsService.pinepodsUserId,
pinepodsUsername: _settingsService.pinepodsUsername,
pinepodsEmail: _settingsService.pinepodsEmail,
bottomBarOrder: _settingsService.bottomBarOrder,
);
_settings.add(_currentSettings);
_darkMode.listen((bool darkMode) {
_currentSettings = _currentSettings.copyWith(theme: darkMode ? 'Dark' : 'Light');
_settings.add(_currentSettings);
_settingsService.themeDarkMode = darkMode;
});
_theme.listen((String theme) {
_currentSettings = _currentSettings.copyWith(theme: theme);
_settings.add(_currentSettings);
_settingsService.theme = theme;
// Sync with server if authenticated
_syncThemeToServer(theme);
});
_markDeletedAsPlayed.listen((bool mark) {
_currentSettings = _currentSettings.copyWith(markDeletedEpisodesAsPlayed: mark);
_settings.add(_currentSettings);
_settingsService.markDeletedEpisodesAsPlayed = mark;
});
_deleteDownloadedPlayedEpisodes.listen((bool delete) {
_currentSettings = _currentSettings.copyWith(deleteDownloadedPlayedEpisodes: delete);
_settings.add(_currentSettings);
_settingsService.deleteDownloadedPlayedEpisodes = delete;
});
_storeDownloadOnSDCard.listen((bool sdcard) {
_currentSettings = _currentSettings.copyWith(storeDownloadsSDCard: sdcard);
_settings.add(_currentSettings);
_settingsService.storeDownloadsSDCard = sdcard;
});
_playbackSpeed.listen((double speed) {
_currentSettings = _currentSettings.copyWith(playbackSpeed: speed);
_settings.add(_currentSettings);
_settingsService.playbackSpeed = speed;
});
_autoOpenNowPlaying.listen((bool autoOpen) {
_currentSettings = _currentSettings.copyWith(autoOpenNowPlaying: autoOpen);
_settings.add(_currentSettings);
_settingsService.autoOpenNowPlaying = autoOpen;
});
_showFunding.listen((show) {
// If the setting has not changed, don't bother updating it
if (show != _currentSettings.showFunding) {
_currentSettings = _currentSettings.copyWith(showFunding: show);
_settingsService.showFunding = show;
}
_settings.add(_currentSettings);
});
_searchProvider.listen((search) {
_currentSettings = _currentSettings.copyWith(searchProvider: search);
_settings.add(_currentSettings);
_settingsService.searchProvider = search;
});
_externalLinkConsent.listen((consent) {
// If the setting has not changed, don't bother updating it
if (consent != _settingsService.externalLinkConsent) {
_currentSettings = _currentSettings.copyWith(externalLinkConsent: consent);
_settingsService.externalLinkConsent = consent;
}
_settings.add(_currentSettings);
});
_autoUpdatePeriod.listen((period) {
_currentSettings = _currentSettings.copyWith(autoUpdateEpisodePeriod: period);
_settings.add(_currentSettings);
_settingsService.autoUpdateEpisodePeriod = period;
});
_trimSilence.listen((trim) {
_currentSettings = _currentSettings.copyWith(trimSilence: trim);
_settings.add(_currentSettings);
_settingsService.trimSilence = trim;
});
_volumeBoost.listen((boost) {
_currentSettings = _currentSettings.copyWith(volumeBoost: boost);
_settings.add(_currentSettings);
_settingsService.volumeBoost = boost;
});
_pinepodsServer.listen((server) {
_currentSettings = _currentSettings.copyWith(pinepodsServer: server);
_settings.add(_currentSettings);
_settingsService.pinepodsServer = server;
});
_pinepodsApiKey.listen((apiKey) {
_currentSettings = _currentSettings.copyWith(pinepodsApiKey: apiKey);
_settings.add(_currentSettings);
_settingsService.pinepodsApiKey = apiKey;
});
_pinepodsUserId.listen((userId) {
_currentSettings = _currentSettings.copyWith(pinepodsUserId: userId);
_settings.add(_currentSettings);
_settingsService.pinepodsUserId = userId;
});
_pinepodsUsername.listen((username) {
_currentSettings = _currentSettings.copyWith(pinepodsUsername: username);
_settings.add(_currentSettings);
_settingsService.pinepodsUsername = username;
});
_pinepodsEmail.listen((email) {
_currentSettings = _currentSettings.copyWith(pinepodsEmail: email);
_settings.add(_currentSettings);
_settingsService.pinepodsEmail = email;
});
_layoutMode.listen((mode) {
_currentSettings = _currentSettings.copyWith(layout: mode);
_settings.add(_currentSettings);
_settingsService.layoutMode = mode;
});
_bottomBarOrder.listen((order) {
_currentSettings = _currentSettings.copyWith(bottomBarOrder: order);
_settings.add(_currentSettings);
_settingsService.bottomBarOrder = order;
});
}
Stream<AppSettings> get settings => _settings.stream;
void Function(bool) get darkMode => _darkMode.add;
void Function(bool) get storeDownloadonSDCard => _storeDownloadOnSDCard.add;
void Function(bool) get markDeletedAsPlayed => _markDeletedAsPlayed.add;
void Function(bool) get deleteDownloadedPlayedEpisodes => _deleteDownloadedPlayedEpisodes.add;
void Function(double) get setPlaybackSpeed => _playbackSpeed.add;
void Function(bool) get setAutoOpenNowPlaying => _autoOpenNowPlaying.add;
void Function(String) get setSearchProvider => _searchProvider.add;
void Function(bool) get setExternalLinkConsent => _externalLinkConsent.add;
void Function(bool) get setShowFunding => _showFunding.add;
void Function(int) get autoUpdatePeriod => _autoUpdatePeriod.add;
void Function(bool) get trimSilence => _trimSilence.add;
void Function(bool) get volumeBoost => _volumeBoost.add;
void Function(int) get layoutMode => _layoutMode.add;
void Function(String?) get setPinepodsServer => _pinepodsServer.add;
void Function(String?) get setPinepodsApiKey => _pinepodsApiKey.add;
void Function(int?) get setPinepodsUserId => _pinepodsUserId.add;
void Function(String?) get setPinepodsUsername => _pinepodsUsername.add;
void Function(String?) get setPinepodsEmail => _pinepodsEmail.add;
void Function(List<String>) get setBottomBarOrder => _bottomBarOrder.add;
void Function(String) get setTheme => _theme.add;
AppSettings get currentSettings => _settings.value;
Future<void> _syncThemeToServer(String theme) async {
try {
// Only sync if we have PinePods credentials
if (_currentSettings.pinepodsServer != null &&
_currentSettings.pinepodsApiKey != null &&
_currentSettings.pinepodsUserId != null) {
final pinepodsService = PinepodsService();
pinepodsService.setCredentials(
_currentSettings.pinepodsServer!,
_currentSettings.pinepodsApiKey!,
);
await pinepodsService.setUserTheme(_currentSettings.pinepodsUserId!, theme);
log.info('Theme synced to server: $theme');
}
} catch (e) {
log.warning('Failed to sync theme to server: $e');
// Don't throw - theme should still work locally
}
}
Future<void> fetchThemeFromServer() async {
try {
// Only fetch if we have PinePods credentials
if (_currentSettings.pinepodsServer != null &&
_currentSettings.pinepodsApiKey != null &&
_currentSettings.pinepodsUserId != null) {
final pinepodsService = PinepodsService();
pinepodsService.setCredentials(
_currentSettings.pinepodsServer!,
_currentSettings.pinepodsApiKey!,
);
final serverTheme = await pinepodsService.getUserTheme(_currentSettings.pinepodsUserId!);
if (serverTheme != null && serverTheme.isNotEmpty) {
// Update local theme without syncing back to server
_settingsService.theme = serverTheme;
_currentSettings = _currentSettings.copyWith(theme: serverTheme);
_settings.add(_currentSettings);
log.info('Theme fetched from server: $serverTheme');
}
}
} catch (e) {
log.warning('Failed to fetch theme from server: $e');
// Don't throw - continue with local theme
}
}
@override
void dispose() {
_darkMode.close();
_theme.close();
_markDeletedAsPlayed.close();
_deleteDownloadedPlayedEpisodes.close();
_storeDownloadOnSDCard.close();
_playbackSpeed.close();
_searchProvider.close();
_externalLinkConsent.close();
_autoOpenNowPlaying.close();
_showFunding.close();
_trimSilence.close();
_volumeBoost.close();
_autoUpdatePeriod.close();
_layoutMode.close();
_pinepodsServer.close();
_pinepodsApiKey.close();
_pinepodsUserId.close();
_pinepodsUsername.close();
_pinepodsEmail.close();
_bottomBarOrder.close();
_settings.close();
}
}