bharat_erp/lib/modules/settings/presentation/providers/settings_provider.dart
2026-07-22 18:17:43 +05:30

261 lines
9.4 KiB
Dart

import 'package:flutter_riverpod/flutter_riverpod.dart';
import '../../../../core/constants/app_constants.dart';
import '../../../../core/errors/failure.dart';
import '../../../../core/network/api_handler.dart';
import '../../../../core/network/dio_client.dart';
import '../../../../core/theme/branding_config.dart';
import '../../../../core/theme/theme_provider.dart';
import '../../../../core/utils/favicon_store.dart';
import '../../../../core/utils/favicon_updater.dart';
import '../../../../core/utils/media_url.dart';
import '../../data/datasources/settings_local_data_source.dart';
import '../../data/datasources/settings_remote_data_source.dart';
import '../../data/repositories/settings_repository_impl.dart';
import '../../domain/entities/app_settings.dart';
import '../../domain/repositories/settings_repository.dart';
import '../../domain/usecases/get_settings_use_case.dart';
import '../../domain/usecases/save_settings_use_case.dart';
final settingsLocalDataSourceProvider = Provider<SettingsLocalDataSource>((ref) {
return SettingsLocalDataSource(ref.watch(sharedPreferencesProvider));
});
final settingsRemoteDataSourceProvider =
Provider<SettingsRemoteDataSource>((ref) {
return SettingsRemoteDataSource(ref.watch(dioProvider));
});
final settingsRepositoryProvider = Provider<SettingsRepository>((ref) {
return SettingsRepositoryImpl(
local: ref.watch(settingsLocalDataSourceProvider),
remote: ref.watch(settingsRemoteDataSourceProvider),
);
});
final getSettingsUseCaseProvider = Provider<GetSettingsUseCase>((ref) {
return GetSettingsUseCase(ref.watch(settingsRepositoryProvider));
});
final saveSettingsUseCaseProvider = Provider<SaveSettingsUseCase>((ref) {
return SaveSettingsUseCase(ref.watch(settingsRepositoryProvider));
});
final appSettingsProvider =
StateNotifierProvider<AppSettingsNotifier, AppSettings>((ref) {
return AppSettingsNotifier(
getSettings: ref.watch(getSettingsUseCaseProvider),
saveSettings: ref.watch(saveSettingsUseCaseProvider),
repository: ref.watch(settingsRepositoryProvider),
faviconStore: FaviconStore(ref.watch(sharedPreferencesProvider)),
syncAppLogo: (logoUrl, companyName) async {
final branding = ref.read(brandingProvider);
await ref.read(brandingProvider.notifier).updateBranding(
BrandingConfig(
logoUrl: logoUrl,
primaryColorValue: branding.primaryColorValue,
secondaryColorValue: branding.secondaryColorValue,
companyName: companyName ?? branding.companyName,
),
);
},
);
});
class AppSettingsNotifier extends StateNotifier<AppSettings> {
AppSettingsNotifier({
required GetSettingsUseCase getSettings,
required SaveSettingsUseCase saveSettings,
required SettingsRepository repository,
required FaviconStore faviconStore,
required Future<void> Function(String? logoUrl, String? companyName)
syncAppLogo,
}) : _getSettings = getSettings,
_saveSettings = saveSettings,
_repository = repository,
_faviconStore = faviconStore,
_syncAppLogo = syncAppLogo,
super(const AppSettings()) {
_load();
}
final GetSettingsUseCase _getSettings;
final SaveSettingsUseCase _saveSettings;
final SettingsRepository _repository;
final FaviconStore _faviconStore;
final Future<void> Function(String? logoUrl, String? companyName) _syncAppLogo;
Future<Failure?>? _companyProfileInFlight;
Future<void> _syncMainLogo(CompanyProfileSettings profile) async {
final logo = resolveMediaUrl(profile.logoUrl);
await _syncAppLogo(
(logo == null || logo.isEmpty) ? null : logo,
profile.companyName.isEmpty ? null : profile.companyName,
);
}
Future<void> _syncFavicon(String? faviconUrl) async {
final resolved = resolveMediaUrl(faviconUrl) ?? faviconUrl?.trim();
final value = (resolved == null || resolved.isEmpty) ? null : resolved;
await _faviconStore.write(value);
updateFavicon(
resolveFaviconHref(value ?? AppConstants.defaultFaviconAsset),
);
}
Future<void> _load() async {
final result = await _getSettings();
state = result.data ?? const AppSettings();
await _syncFavicon(state.companyProfile.faviconUrl);
await _syncMainLogo(state.companyProfile);
}
Future<Failure?> refreshCompanyProfile() {
final existing = _companyProfileInFlight;
if (existing != null) return existing;
final future = () async {
try {
final result = await _repository.fetchCompanyProfile();
if (result.failure == null && result.data != null) {
state = state.copyWith(companyProfile: result.data!);
await _syncMainLogo(result.data!);
await _syncFavicon(result.data!.faviconUrl);
}
return result.failure;
} finally {
_companyProfileInFlight = null;
}
}();
_companyProfileInFlight = future;
return future;
}
Future<Failure?> refreshEmailSettings() async {
final result = await _repository.fetchEmailSettings();
if (result.failure == null && result.data != null) {
state = state.copyWith(email: result.data!);
}
return result.failure;
}
/// Fetches Company Profile + Email Settings after login / session restore
/// and applies them app-wide (logo, favicon, company name, email config).
Future<void> syncCompanyAndEmailFromServer() async {
await Future.wait([
refreshCompanyProfile(),
refreshEmailSettings(),
]);
}
Future<void> _persist(AppSettings settings) async {
state = settings;
final result = await _saveSettings(settings);
state = result.data ?? settings;
}
Future<void> updateGeneral(GeneralSettings general) async {
await _persist(state.copyWith(general: general));
}
/// PUT `/settings/company` — returns failure when the API call fails.
Future<Failure?> updateCompanyProfile(CompanyProfileSettings profile) async {
final result = await _repository.saveCompanyProfile(profile);
if (result.failure != null) return result.failure;
if (result.data != null) {
state = state.copyWith(companyProfile: result.data!);
await _saveSettings(state);
await _syncMainLogo(result.data!);
await _syncFavicon(result.data!.faviconUrl);
}
return null;
}
/// POST `/settings/company/logo` — also updates the app main logo.
Future<Result<String?>> uploadCompanyLogo(
List<int> bytes,
String filename,
) async {
final result = await _repository.uploadCompanyLogo(bytes, filename);
if (result.failure == null && result.data != null && result.data!.isNotEmpty) {
final logoUrl = resolveMediaUrl(result.data!) ?? result.data!;
final profile = state.companyProfile.copyWith(logoUrl: logoUrl);
state = state.copyWith(companyProfile: profile);
await _saveSettings(state);
await _syncMainLogo(profile);
return (data: logoUrl, failure: null);
}
return result;
}
/// POST `/settings/company/favicon` — also updates the browser tab icon.
Future<Result<String?>> uploadCompanyFavicon(
List<int> bytes,
String filename,
) async {
final result = await _repository.uploadCompanyFavicon(bytes, filename);
if (result.failure == null &&
result.data != null &&
result.data!.isNotEmpty) {
final faviconUrl = resolveMediaUrl(result.data!) ?? result.data!;
final profile = state.companyProfile.copyWith(faviconUrl: faviconUrl);
state = state.copyWith(companyProfile: profile);
await _saveSettings(state);
await _syncFavicon(faviconUrl);
return (data: faviconUrl, failure: null);
}
return result;
}
/// Applies a local/preview logo to company profile + main app branding.
Future<void> applyLocalCompanyLogo(String logoUrl) async {
final resolved = resolveMediaUrl(logoUrl) ?? logoUrl;
final profile = state.companyProfile.copyWith(logoUrl: resolved);
state = state.copyWith(companyProfile: profile);
await _saveSettings(state);
await _syncMainLogo(profile);
}
/// Applies a local/preview favicon when upload is unavailable.
Future<void> applyLocalCompanyFavicon(String faviconUrl) async {
final resolved = resolveMediaUrl(faviconUrl) ?? faviconUrl;
final profile = state.companyProfile.copyWith(faviconUrl: resolved);
state = state.copyWith(companyProfile: profile);
await _saveSettings(state);
await _syncFavicon(resolved);
}
Future<void> updateUiPreferences(UiPreferencesSettings prefs) async {
await _persist(state.copyWith(uiPreferences: prefs));
}
Future<void> updateAsset(AssetSettingsConfig asset) async {
await _persist(state.copyWith(asset: asset));
}
Future<void> updateNotifications(NotificationSettingsConfig notifications) async {
await _persist(state.copyWith(notifications: notifications));
}
/// PUT `/settings/email` — returns failure when the API call fails.
Future<Failure?> updateEmail(EmailConfigurationSettings email) async {
final result = await _repository.saveEmailSettings(email);
if (result.failure != null) return result.failure;
if (result.data != null) {
state = state.copyWith(email: result.data!);
await _saveSettings(state);
}
return null;
}
Future<void> updateSecurity(SecuritySettingsConfig security) async {
await _persist(state.copyWith(security: security));
}
Future<void> resetToDefaults() async {
await _persist(const AppSettings());
await _syncAppLogo(null, null);
}
}