mirror of
https://github.com/zarzet/SpotiFLAC-Mobile.git
synced 2026-10-21 15:02:21 +02:00
feat(home): expose extension country settings and isolate regional caches
This commit is contained in:
1 parent
2455cabced
commit
1a6dc7366e
11 files changed
+606
-21
No files matched your search
@@ -5330,6 +5330,14 @@
|
||||
"@extensionsHomeFeedProvider": {
|
||||
"description": "Extensions page - label for home feed provider selector"
|
||||
},
|
||||
"extensionsHomeFeedCountry": "Home feed country",
|
||||
"@extensionsHomeFeedCountry": {
|
||||
"description": "Country picker for the active Home feed extension"
|
||||
},
|
||||
"extensionsHomeFeedCountryDescription": "Choose a country's Home recommendations without a VPN.",
|
||||
"@extensionsHomeFeedCountryDescription": {
|
||||
"description": "Help for choosing regional Home recommendations"
|
||||
},
|
||||
"extensionsHomeFeedDescription": "Choose which extension provides the home feed on the main screen",
|
||||
"@extensionsHomeFeedDescription": {
|
||||
"description": "Extensions page - description for home feed provider picker"
|
||||
@@ -6985,6 +6993,14 @@
|
||||
"@regionCountryUS": {
|
||||
"description": "Country name for SongLink region picker"
|
||||
},
|
||||
"regionCountryIT": "Italy",
|
||||
"@regionCountryIT": {
|
||||
"description": "Country name for Italy"
|
||||
},
|
||||
"regionCountryES": "Spain",
|
||||
"@regionCountryES": {
|
||||
"description": "Country name for Spain"
|
||||
},
|
||||
"regionCountryGB": "United Kingdom",
|
||||
"@regionCountryGB": {
|
||||
"description": "Country name for SongLink region picker"
|
||||
|
||||
@@ -5358,6 +5358,8 @@
|
||||
"@extensionsHomeFeedProvider": {
|
||||
"description": "Extensions page - label for home feed provider selector"
|
||||
},
|
||||
"extensionsHomeFeedCountry": "Negara beranda",
|
||||
"extensionsHomeFeedCountryDescription": "Pilih rekomendasi beranda suatu negara tanpa VPN.",
|
||||
"extensionsHomeFeedDescription": "Pilih ekstensi provider feed beranda di layar utama",
|
||||
"@extensionsHomeFeedDescription": {
|
||||
"description": "Extensions page - description for home feed provider picker"
|
||||
@@ -7025,6 +7027,8 @@
|
||||
"@regionCountryUS": {
|
||||
"description": "Country name for SongLink region picker"
|
||||
},
|
||||
"regionCountryIT": "Italia",
|
||||
"regionCountryES": "Spanyol",
|
||||
"regionCountryGB": "Inggris Raya",
|
||||
"@regionCountryGB": {
|
||||
"description": "Country name for SongLink region picker"
|
||||
|
||||
@@ -244,6 +244,22 @@ class Extension {
|
||||
bool get hasPostProcessing => postProcessing?.enabled ?? false;
|
||||
bool get hasServiceHealth => serviceHealth.isNotEmpty;
|
||||
bool get hasHomeFeed => capabilities['homeFeed'] == true;
|
||||
|
||||
/// The extension owns its country choices and the setting sent to its API.
|
||||
ExtensionSetting? get homeFeedRegionSetting {
|
||||
if (!hasHomeFeed) return null;
|
||||
final key = capabilities['homeFeedRegionSetting'];
|
||||
if (key is! String || key.isEmpty) return null;
|
||||
return settings.where((setting) {
|
||||
final options = setting.options;
|
||||
return setting.key == key &&
|
||||
(setting.type == 'select' || setting.type == 'string') &&
|
||||
options != null &&
|
||||
options.isNotEmpty &&
|
||||
options.every((code) => RegExp(r'^[a-zA-Z]{2}$').hasMatch(code));
|
||||
}).firstOrNull;
|
||||
}
|
||||
|
||||
ExtensionDownloadTransferPolicy get downloadTransferPolicy =>
|
||||
ExtensionDownloadTransferPolicy.fromCapabilities(capabilities);
|
||||
bool get requiresNativeContainerConversion =>
|
||||
@@ -760,6 +776,7 @@ class ExtensionState {
|
||||
final bool isLoading;
|
||||
final String? error;
|
||||
final bool isInitialized;
|
||||
final Map<String, int> settingsRevisions;
|
||||
|
||||
const ExtensionState({
|
||||
this.extensions = const [],
|
||||
@@ -769,6 +786,7 @@ class ExtensionState {
|
||||
this.isLoading = false,
|
||||
this.error,
|
||||
this.isInitialized = false,
|
||||
this.settingsRevisions = const {},
|
||||
});
|
||||
|
||||
ExtensionState copyWith({
|
||||
@@ -779,6 +797,7 @@ class ExtensionState {
|
||||
bool? isLoading,
|
||||
String? error,
|
||||
bool? isInitialized,
|
||||
Map<String, int>? settingsRevisions,
|
||||
}) {
|
||||
return ExtensionState(
|
||||
extensions: extensions ?? this.extensions,
|
||||
@@ -789,6 +808,7 @@ class ExtensionState {
|
||||
isLoading: isLoading ?? this.isLoading,
|
||||
error: error,
|
||||
isInitialized: isInitialized ?? this.isInitialized,
|
||||
settingsRevisions: settingsRevisions ?? this.settingsRevisions,
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -12,6 +12,21 @@ import 'package:spotiflac_android/providers/settings_provider.dart';
|
||||
|
||||
final _log = AppLogger('ExploreProvider');
|
||||
|
||||
final homeFeedExtensionProvider = Provider<Extension?>((ref) {
|
||||
final preferred = ref.watch(
|
||||
settingsProvider.select((settings) => settings.homeFeedProvider),
|
||||
);
|
||||
if (preferred == AppSettings.homeFeedProviderOff) return null;
|
||||
final extensions = ref.watch(
|
||||
extensionProvider.select((state) => state.extensions),
|
||||
);
|
||||
final available = extensions.where((ext) => ext.enabled && ext.hasHomeFeed);
|
||||
if (preferred != null && preferred.isNotEmpty) {
|
||||
return available.where((ext) => ext.id == preferred).firstOrNull;
|
||||
}
|
||||
return available.firstOrNull;
|
||||
});
|
||||
|
||||
class ExploreItem {
|
||||
final String id;
|
||||
final String uri;
|
||||
@@ -256,7 +271,11 @@ Map<String, Object?> _decodeExploreCache(String rawCache) {
|
||||
sections = _withDefaultExploreProviderId(sections, providerId);
|
||||
}
|
||||
|
||||
return {'provider_id': providerId, 'sections': sections};
|
||||
return {
|
||||
'provider_id': providerId,
|
||||
'region': decoded['region'],
|
||||
'sections': sections,
|
||||
};
|
||||
}
|
||||
|
||||
String _encodeExploreCache(Map<String, Object?> cachePayload) {
|
||||
@@ -295,6 +314,15 @@ class ExploreNotifier extends Notifier<ExploreState> {
|
||||
|
||||
@override
|
||||
ExploreState build() {
|
||||
ref.listen(extensionProvider.select((state) => state.settingsRevisions), (
|
||||
previous,
|
||||
next,
|
||||
) {
|
||||
final extension = _resolveHomeFeedExtension();
|
||||
if (extension != null && previous?[extension.id] != next[extension.id]) {
|
||||
_reloadConfiguration(extension.id);
|
||||
}
|
||||
});
|
||||
_restoreFromCache();
|
||||
return const ExploreState();
|
||||
}
|
||||
@@ -328,6 +356,21 @@ class ExploreNotifier extends Notifier<ExploreState> {
|
||||
final resolvedProviderId = providerId?.isNotEmpty == true
|
||||
? providerId
|
||||
: _resolveHomeFeedExtension()?.id;
|
||||
var extension = _resolveHomeFeedExtension();
|
||||
if (extension == null && cachePayload['region'] != null) {
|
||||
// Regional caches cannot be validated until installed manifests and
|
||||
// their saved settings are available on a cold launch.
|
||||
await ref.read(extensionProvider.notifier).ensureInitialized();
|
||||
if (!_isCacheRestoreValid(generation)) return;
|
||||
extension = _resolveHomeFeedExtension();
|
||||
if (extension == null) return;
|
||||
}
|
||||
if (extension != null) {
|
||||
if (resolvedProviderId != extension.id) return;
|
||||
final region = await _homeFeedRegion(extension);
|
||||
if (!_isCacheRestoreValid(generation)) return;
|
||||
if (cachePayload['region'] != region) return;
|
||||
}
|
||||
if (resolvedProviderId != null && resolvedProviderId.isNotEmpty) {
|
||||
normalizedSections = _withDefaultExploreProviderId(
|
||||
normalizedSections,
|
||||
@@ -369,34 +412,53 @@ class ExploreNotifier extends Notifier<ExploreState> {
|
||||
}
|
||||
|
||||
Extension? _resolveHomeFeedExtension() {
|
||||
final settings = ref.read(settingsProvider);
|
||||
final preferredId = settings.homeFeedProvider;
|
||||
final enabledHomeFeedExtensions = ref
|
||||
.read(extensionProvider)
|
||||
.extensions
|
||||
.where((extension) => extension.enabled && extension.hasHomeFeed)
|
||||
.toList(growable: false);
|
||||
return ref.read(homeFeedExtensionProvider);
|
||||
}
|
||||
|
||||
if (preferredId != null && preferredId.isNotEmpty) {
|
||||
return enabledHomeFeedExtensions
|
||||
.where((extension) => extension.id == preferredId)
|
||||
.firstOrNull;
|
||||
Future<String?> _homeFeedRegion(Extension extension) async {
|
||||
final setting = extension.homeFeedRegionSetting;
|
||||
if (setting == null) return null;
|
||||
final settings = await ref.read(
|
||||
extensionSettingsProvider(extension.id).future,
|
||||
);
|
||||
return (settings[setting.key] ?? setting.defaultValue)
|
||||
?.toString()
|
||||
.trim()
|
||||
.toUpperCase();
|
||||
}
|
||||
|
||||
Future<void> _reloadConfiguration(String extensionId) async {
|
||||
clear();
|
||||
final generation = _homeFeedRequestId;
|
||||
try {
|
||||
final prefs = await SharedPreferences.getInstance();
|
||||
if (!_isRequestValid(generation)) return;
|
||||
await ExploreCacheStore(prefs).clear();
|
||||
} catch (error) {
|
||||
_log.w('Failed to clear changed home feed: $error');
|
||||
}
|
||||
if (_isRequestValid(generation) &&
|
||||
_resolveHomeFeedExtension()?.id == extensionId) {
|
||||
await refresh();
|
||||
}
|
||||
|
||||
return enabledHomeFeedExtensions.firstOrNull;
|
||||
}
|
||||
|
||||
Future<void> _saveToCache(
|
||||
List<Map<String, Object?>> normalizedSections,
|
||||
String providerId,
|
||||
String? region,
|
||||
int requestId,
|
||||
) async {
|
||||
try {
|
||||
final prefs = await SharedPreferences.getInstance();
|
||||
final encoded = await compute(_encodeExploreCache, {
|
||||
'provider_id': providerId,
|
||||
'region': region,
|
||||
'sections': normalizedSections,
|
||||
});
|
||||
await ExploreCacheStore(prefs).write(encoded);
|
||||
if (_isRequestValid(requestId)) {
|
||||
await ExploreCacheStore(prefs).write(encoded);
|
||||
}
|
||||
} catch (e) {
|
||||
_log.w('Failed to save explore cache: $e');
|
||||
}
|
||||
@@ -440,6 +502,8 @@ class ExploreNotifier extends Notifier<ExploreState> {
|
||||
return;
|
||||
}
|
||||
|
||||
final region = await _homeFeedRegion(targetExt);
|
||||
if (!_isRequestValid(requestId)) return;
|
||||
final result = await PlatformBridge.getExtensionHomeFeed(
|
||||
targetExt.id,
|
||||
cancelPrevious: forceRefresh,
|
||||
@@ -490,7 +554,7 @@ class ExploreNotifier extends Notifier<ExploreState> {
|
||||
lastFetched: DateTime.now(),
|
||||
);
|
||||
|
||||
_saveToCache(normalizedSections, targetExt.id);
|
||||
_saveToCache(normalizedSections, targetExt.id, region, requestId);
|
||||
} catch (e, stack) {
|
||||
_log.e('Error fetching home feed: $e', e, stack);
|
||||
if (!_isRequestValid(requestId)) return;
|
||||
|
||||
@@ -22,6 +22,11 @@ const _metadataProviderPriorityKey = 'metadata_provider_priority';
|
||||
const _providerPriorityKey = 'provider_priority';
|
||||
const _storeRegistryUrlPrefKey = 'store_registry_url';
|
||||
|
||||
final extensionSettingsProvider = FutureProvider.autoDispose
|
||||
.family<Map<String, dynamic>, String>((ref, extensionId) {
|
||||
return PlatformBridge.getExtensionSettings(extensionId);
|
||||
});
|
||||
|
||||
/// Result of restoring extensions from a backup.
|
||||
class ExtensionNotifier extends Notifier<ExtensionState> {
|
||||
static const _extensionHealthDefaultCacheTtl = Duration(minutes: 10);
|
||||
@@ -784,7 +789,7 @@ class ExtensionNotifier extends Notifier<ExtensionState> {
|
||||
...current,
|
||||
...Map<String, dynamic>.from(settings),
|
||||
};
|
||||
await PlatformBridge.setExtensionSettings(id, merged);
|
||||
await setExtensionSettings(id, merged);
|
||||
} catch (e) {
|
||||
_log.w('Restore: failed to apply settings for $id: $e');
|
||||
}
|
||||
|
||||
@@ -236,16 +236,47 @@ extension ExtensionNotifierProviderPriority on ExtensionNotifier {
|
||||
}
|
||||
}
|
||||
|
||||
Future<void> setExtensionSettings(
|
||||
Future<bool> setExtensionSettings(
|
||||
String extensionId,
|
||||
Map<String, dynamic> settings,
|
||||
) async {
|
||||
try {
|
||||
await PlatformBridge.setExtensionSettings(extensionId, settings);
|
||||
ref.invalidate(extensionSettingsProvider(extensionId));
|
||||
state = state.copyWith(
|
||||
settingsRevisions: {
|
||||
...state.settingsRevisions,
|
||||
extensionId: (state.settingsRevisions[extensionId] ?? 0) + 1,
|
||||
},
|
||||
);
|
||||
_log.d('Updated settings for extension: $extensionId');
|
||||
return true;
|
||||
} catch (e) {
|
||||
_log.e('Failed to set extension settings: $e');
|
||||
state = state.copyWith(error: e.toString());
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
Future<bool> setHomeFeedRegion(String extensionId, String region) async {
|
||||
final extension = state.extensions
|
||||
.where((extension) => extension.id == extensionId)
|
||||
.firstOrNull;
|
||||
final setting = extension?.homeFeedRegionSetting;
|
||||
if (setting == null || !setting.options!.contains(region)) return false;
|
||||
try {
|
||||
final settings = await PlatformBridge.getExtensionSettings(extensionId);
|
||||
if ((settings[setting.key] ?? setting.defaultValue) == region) {
|
||||
return true;
|
||||
}
|
||||
return await setExtensionSettings(extensionId, {
|
||||
...settings,
|
||||
setting.key: region,
|
||||
});
|
||||
} catch (error) {
|
||||
_log.e('Failed to update home feed country: $error');
|
||||
state = state.copyWith(error: error.toString());
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -18,6 +18,7 @@ import 'package:spotiflac_android/providers/repo_provider.dart';
|
||||
import 'package:spotiflac_android/utils/extension_auth_launcher.dart';
|
||||
import 'package:spotiflac_android/utils/extension_action_forms.dart';
|
||||
import 'package:spotiflac_android/widgets/settings_group.dart';
|
||||
import 'package:spotiflac_android/utils/extension_country_name.dart';
|
||||
import 'package:spotiflac_android/widgets/app_icon.dart';
|
||||
import 'package:spotiflac_android/widgets/app_icon_button.dart';
|
||||
import 'package:spotiflac_android/widgets/app_loading_indicator.dart';
|
||||
@@ -268,6 +269,8 @@ class _ExtensionDetailPageState extends ConsumerState<ExtensionDetailPage> {
|
||||
final setting = entry.value;
|
||||
return _SettingItem(
|
||||
key: ValueKey(setting.key),
|
||||
countryOptions:
|
||||
setting.key == extension.homeFeedRegionSetting?.key,
|
||||
setting: setting,
|
||||
value: _settings[setting.key] ?? setting.defaultValue,
|
||||
showDivider: index < extension.settings.length - 1,
|
||||
@@ -1026,6 +1029,7 @@ class _SettingItem extends StatefulWidget {
|
||||
final bool showDivider;
|
||||
final ValueChanged<dynamic> onChanged;
|
||||
final String extensionId;
|
||||
final bool countryOptions;
|
||||
final Future<void> Function(Map<String, dynamic> payload)? onActionPayload;
|
||||
|
||||
const _SettingItem({
|
||||
@@ -1034,6 +1038,7 @@ class _SettingItem extends StatefulWidget {
|
||||
required this.value,
|
||||
required this.onChanged,
|
||||
required this.extensionId,
|
||||
this.countryOptions = false,
|
||||
this.onActionPayload,
|
||||
this.showDivider = true,
|
||||
});
|
||||
@@ -1065,7 +1070,9 @@ class _SettingItemState extends State<_SettingItem> {
|
||||
? _showOptions
|
||||
: null;
|
||||
footer = Text(
|
||||
widget.value?.toString() ?? context.l10n.extensionSettingNotSet,
|
||||
widget.countryOptions
|
||||
? extensionCountryName(context, widget.value?.toString() ?? '')
|
||||
: widget.value?.toString() ?? context.l10n.extensionSettingNotSet,
|
||||
style: Theme.of(
|
||||
context,
|
||||
).textTheme.bodyMedium?.copyWith(color: colorScheme.primary),
|
||||
@@ -1090,7 +1097,13 @@ class _SettingItemState extends State<_SettingItem> {
|
||||
colorScheme: colorScheme,
|
||||
)
|
||||
: Text(
|
||||
widget.value?.toString() ?? context.l10n.extensionSettingNotSet,
|
||||
widget.countryOptions
|
||||
? extensionCountryName(
|
||||
context,
|
||||
widget.value?.toString() ?? '',
|
||||
)
|
||||
: widget.value?.toString() ??
|
||||
context.l10n.extensionSettingNotSet,
|
||||
style: Theme.of(
|
||||
context,
|
||||
).textTheme.bodyMedium?.copyWith(color: colorScheme.primary),
|
||||
@@ -1120,7 +1133,11 @@ class _SettingItemState extends State<_SettingItem> {
|
||||
children: [
|
||||
for (final option in widget.setting.options ?? <String>[])
|
||||
AppSheetOption.choice(
|
||||
title: Text(option),
|
||||
title: Text(
|
||||
widget.countryOptions
|
||||
? extensionCountryName(sheetContext, option)
|
||||
: option,
|
||||
),
|
||||
selected: option == widget.value,
|
||||
onTap: () => Navigator.pop(sheetContext, option),
|
||||
),
|
||||
|
||||
@@ -23,6 +23,7 @@ import 'package:spotiflac_android/widgets/app_icon.dart';
|
||||
import 'package:spotiflac_android/widgets/app_loading_indicator.dart';
|
||||
import 'package:spotiflac_android/widgets/mornye_chrome.dart';
|
||||
import 'package:spotiflac_android/widgets/settings_group.dart';
|
||||
import 'package:spotiflac_android/widgets/home_feed_region_setting.dart';
|
||||
import 'package:spotiflac_android/widgets/app_sliver_header.dart';
|
||||
|
||||
class ExtensionsPage extends ConsumerStatefulWidget {
|
||||
@@ -125,6 +126,7 @@ class _ExtensionsPageState extends ConsumerState<ExtensionsPage> {
|
||||
_MetadataPriorityItem(),
|
||||
_SearchProviderSelector(),
|
||||
_HomeFeedProviderSelector(),
|
||||
HomeFeedRegionSetting(),
|
||||
],
|
||||
),
|
||||
),
|
||||
|
||||
@@ -0,0 +1,24 @@
|
||||
import 'package:flutter/widgets.dart';
|
||||
import 'package:spotiflac_android/l10n/l10n.dart';
|
||||
|
||||
String extensionCountryName(BuildContext context, String code) {
|
||||
final l10n = context.l10n;
|
||||
return switch (code.toUpperCase()) {
|
||||
'US' => l10n.regionCountryUS,
|
||||
'GB' => l10n.regionCountryGB,
|
||||
'FR' => l10n.regionCountryFR,
|
||||
'DE' => l10n.regionCountryDE,
|
||||
'IT' => l10n.regionCountryIT,
|
||||
'ES' => l10n.regionCountryES,
|
||||
'JP' => l10n.regionCountryJP,
|
||||
'KR' => l10n.regionCountryKR,
|
||||
'IN' => l10n.regionCountryIN,
|
||||
'ID' => l10n.regionCountryID,
|
||||
'BR' => l10n.regionCountryBR,
|
||||
'MX' => l10n.regionCountryMX,
|
||||
'AU' => l10n.regionCountryAU,
|
||||
'CA' => l10n.regionCountryCA,
|
||||
'XK' => l10n.regionCountryXK,
|
||||
_ => code.toUpperCase(),
|
||||
};
|
||||
}
|
||||
@@ -0,0 +1,87 @@
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:flutter_riverpod/flutter_riverpod.dart';
|
||||
import 'package:spotiflac_android/l10n/l10n.dart';
|
||||
import 'package:spotiflac_android/providers/explore_provider.dart';
|
||||
import 'package:spotiflac_android/providers/extension_provider.dart';
|
||||
import 'package:spotiflac_android/utils/extension_country_name.dart';
|
||||
import 'package:spotiflac_android/widgets/app_bottom_sheet.dart';
|
||||
import 'package:spotiflac_android/widgets/app_snack_bar.dart';
|
||||
import 'package:spotiflac_android/widgets/settings_group.dart';
|
||||
|
||||
class HomeFeedRegionSetting extends ConsumerStatefulWidget {
|
||||
const HomeFeedRegionSetting({super.key});
|
||||
|
||||
@override
|
||||
ConsumerState<HomeFeedRegionSetting> createState() =>
|
||||
_HomeFeedRegionSettingState();
|
||||
}
|
||||
|
||||
class _HomeFeedRegionSettingState extends ConsumerState<HomeFeedRegionSetting> {
|
||||
bool _saving = false;
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
final extension = ref.watch(homeFeedExtensionProvider);
|
||||
final setting = extension?.homeFeedRegionSetting;
|
||||
if (extension == null || setting == null) return const SizedBox.shrink();
|
||||
final saved = ref.watch(extensionSettingsProvider(extension.id));
|
||||
final selected = (saved.value?[setting.key] ?? setting.defaultValue)
|
||||
?.toString();
|
||||
return SettingsItem(
|
||||
icon: Icons.public,
|
||||
title: context.l10n.extensionsHomeFeedCountry,
|
||||
subtitle: saved.hasError
|
||||
? context.friendlyError(saved.error)
|
||||
: '${extensionCountryName(context, selected ?? '')} · ${extension.displayName}',
|
||||
showDivider: false,
|
||||
onTap: _saving
|
||||
? null
|
||||
: () async {
|
||||
if (saved.hasError) {
|
||||
ref.invalidate(extensionSettingsProvider(extension.id));
|
||||
return;
|
||||
}
|
||||
final region = await showAppScrollableBottomSheet<String>(
|
||||
context: context,
|
||||
title: context.l10n.extensionsHomeFeedCountry,
|
||||
subtitle:
|
||||
setting.description ??
|
||||
context.l10n.extensionsHomeFeedCountryDescription,
|
||||
builder: (sheetContext, controller) => SingleChildScrollView(
|
||||
controller: controller,
|
||||
child: Column(
|
||||
mainAxisSize: MainAxisSize.min,
|
||||
children: [
|
||||
for (final code in setting.options!)
|
||||
AppSheetOption.choice(
|
||||
title: Text(extensionCountryName(sheetContext, code)),
|
||||
subtitle: Text(code.toUpperCase()),
|
||||
selected:
|
||||
code.toUpperCase() == selected?.toUpperCase(),
|
||||
onTap: () => Navigator.pop(sheetContext, code),
|
||||
),
|
||||
],
|
||||
),
|
||||
),
|
||||
);
|
||||
if (!mounted || region == null) return;
|
||||
setState(() => _saving = true);
|
||||
final success = await ref
|
||||
.read(extensionProvider.notifier)
|
||||
.setHomeFeedRegion(extension.id, region);
|
||||
if (!mounted || !context.mounted) return;
|
||||
setState(() => _saving = false);
|
||||
if (!success) {
|
||||
showAppSnackBar(
|
||||
context,
|
||||
content: Text(
|
||||
context.l10n.snackbarError(
|
||||
context.friendlyError(ref.read(extensionProvider).error),
|
||||
),
|
||||
),
|
||||
);
|
||||
}
|
||||
},
|
||||
);
|
||||
}
|
||||
}
|
||||
Reference in new issue
Block a user