[imp]: Show repository availability and add localizations
This commit is contained in:
+15
-1
@@ -354,5 +354,19 @@
|
||||
"auth_all_apps_desc": "Require authentication for all installations",
|
||||
"auth_all_apps_w_anti_feat": "Apps with Anti-Features",
|
||||
"auth_all_apps_w_anti_feat_desc": "Require authentication only for apps that have anti-features.",
|
||||
"support_the_developer":"Support the Developer"
|
||||
"support_the_developer":"Support the Developer",
|
||||
"available_on_repository": "Available on: {repositoryName}",
|
||||
"@available_on_repository": {
|
||||
"description": "Label showing the primary repository name for an app.",
|
||||
"placeholders": {
|
||||
"repositoryName": {}
|
||||
}
|
||||
},
|
||||
"also_available_from_repositories": "Also available from: {repositoryNames}",
|
||||
"@also_available_from_repositories": {
|
||||
"description": "Label listing additional repository names where the app is available.",
|
||||
"placeholders": {
|
||||
"repositoryNames": {}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1686,6 +1686,18 @@ abstract class AppLocalizations {
|
||||
/// In en, this message translates to:
|
||||
/// **'Support the Developer'**
|
||||
String get support_the_developer;
|
||||
|
||||
/// Label showing the primary repository name for an app.
|
||||
///
|
||||
/// In en, this message translates to:
|
||||
/// **'Available on: {repositoryName}'**
|
||||
String available_on_repository(Object repositoryName);
|
||||
|
||||
/// Label listing additional repository names where the app is available.
|
||||
///
|
||||
/// In en, this message translates to:
|
||||
/// **'Also available from: {repositoryNames}'**
|
||||
String also_available_from_repositories(Object repositoryNames);
|
||||
}
|
||||
|
||||
class _AppLocalizationsDelegate
|
||||
|
||||
@@ -859,4 +859,14 @@ class AppLocalizationsCs extends AppLocalizations {
|
||||
|
||||
@override
|
||||
String get support_the_developer => 'Support the Developer';
|
||||
|
||||
@override
|
||||
String available_on_repository(Object repositoryName) {
|
||||
return 'Available on: $repositoryName';
|
||||
}
|
||||
|
||||
@override
|
||||
String also_available_from_repositories(Object repositoryNames) {
|
||||
return 'Also available from: $repositoryNames';
|
||||
}
|
||||
}
|
||||
|
||||
@@ -860,4 +860,14 @@ class AppLocalizationsDe extends AppLocalizations {
|
||||
|
||||
@override
|
||||
String get support_the_developer => 'Support the Developer';
|
||||
|
||||
@override
|
||||
String available_on_repository(Object repositoryName) {
|
||||
return 'Available on: $repositoryName';
|
||||
}
|
||||
|
||||
@override
|
||||
String also_available_from_repositories(Object repositoryNames) {
|
||||
return 'Also available from: $repositoryNames';
|
||||
}
|
||||
}
|
||||
|
||||
@@ -857,4 +857,14 @@ class AppLocalizationsEn extends AppLocalizations {
|
||||
|
||||
@override
|
||||
String get support_the_developer => 'Support the Developer';
|
||||
|
||||
@override
|
||||
String available_on_repository(Object repositoryName) {
|
||||
return 'Available on: $repositoryName';
|
||||
}
|
||||
|
||||
@override
|
||||
String also_available_from_repositories(Object repositoryNames) {
|
||||
return 'Also available from: $repositoryNames';
|
||||
}
|
||||
}
|
||||
|
||||
@@ -837,6 +837,16 @@ class AppLocalizationsZh extends AppLocalizations {
|
||||
|
||||
@override
|
||||
String get support_the_developer => 'Support the Developer';
|
||||
|
||||
@override
|
||||
String available_on_repository(Object repositoryName) {
|
||||
return 'Available on: $repositoryName';
|
||||
}
|
||||
|
||||
@override
|
||||
String also_available_from_repositories(Object repositoryNames) {
|
||||
return 'Also available from: $repositoryNames';
|
||||
}
|
||||
}
|
||||
|
||||
/// The translations for Chinese, as used in China (`zh_CN`).
|
||||
|
||||
@@ -327,12 +327,9 @@ class AppProvider extends ChangeNotifier {
|
||||
/// This is useful when displaying app details to show which repositories host the app
|
||||
Future<FDroidApp> enrichAppWithRepositories(
|
||||
FDroidApp app,
|
||||
RepositoriesProvider? repositoriesProvider,
|
||||
) async {
|
||||
if (repositoriesProvider == null) {
|
||||
return app;
|
||||
}
|
||||
|
||||
RepositoriesProvider repositoriesProvider, {
|
||||
bool allowNetworkFallback = true,
|
||||
}) async {
|
||||
try {
|
||||
// Fast path: app already carries multi-repo info.
|
||||
if (app.availableRepositories != null &&
|
||||
@@ -410,8 +407,9 @@ class AppProvider extends ChangeNotifier {
|
||||
|
||||
// If DB misses but multiple repos are enabled, do a limited network fallback
|
||||
// to recover selector visibility for first-time sync.
|
||||
// Note: disable network fallback for lazy-loaded contexts like All Versions sheet
|
||||
if (availableReposList.length <= 1 && enabledRepos.length > 1) {
|
||||
if (allowNetworkFallback &&
|
||||
availableReposList.length <= 1 &&
|
||||
enabledRepos.length > 1) {
|
||||
for (final repo in enabledRepos) {
|
||||
if (repo.url == app.repositoryUrl) continue;
|
||||
if (availableReposList.any((r) => r.url == repo.url)) continue;
|
||||
@@ -419,8 +417,7 @@ class AppProvider extends ChangeNotifier {
|
||||
final exists = await _apiService.repositoryContainsPackage(
|
||||
app.packageName,
|
||||
repo.url,
|
||||
allowNetworkFallback:
|
||||
false, // Use only database, no network fallback
|
||||
allowNetworkFallback: true,
|
||||
);
|
||||
|
||||
if (exists) {
|
||||
|
||||
@@ -2574,6 +2574,31 @@ class AppExtraInfoSection extends StatefulWidget {
|
||||
}
|
||||
|
||||
class _AppExtraInfoSectionState extends State<AppExtraInfoSection> {
|
||||
String _normalizeRepoUrl(String url) {
|
||||
var normalized = url.trim();
|
||||
if (normalized.endsWith('/index-v2.json')) {
|
||||
normalized = normalized.substring(
|
||||
0,
|
||||
normalized.length - '/index-v2.json'.length,
|
||||
);
|
||||
}
|
||||
if (normalized.endsWith('/')) {
|
||||
normalized = normalized.substring(0, normalized.length - 1);
|
||||
}
|
||||
return normalized;
|
||||
}
|
||||
|
||||
String _normalizeRepoPath(String url) {
|
||||
final normalized = _normalizeRepoUrl(url);
|
||||
final uri = Uri.tryParse(normalized);
|
||||
if (uri == null) return normalized;
|
||||
var path = uri.path;
|
||||
if (path.endsWith('/')) {
|
||||
path = path.substring(0, path.length - 1);
|
||||
}
|
||||
return '${uri.host}$path';
|
||||
}
|
||||
|
||||
List<MapEntry<String, String>> _buildDonationItems() {
|
||||
final entries = <MapEntry<String, String>>[];
|
||||
final seen = <String>{};
|
||||
@@ -2763,8 +2788,64 @@ class _AppExtraInfoSectionState extends State<AppExtraInfoSection> {
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
final donationItems = _buildDonationItems();
|
||||
if (donationItems.isEmpty) {
|
||||
return const SizedBox.shrink();
|
||||
final repositoriesProvider = context.watch<RepositoriesProvider>();
|
||||
if (repositoriesProvider.repositories.isEmpty &&
|
||||
!repositoriesProvider.isLoading) {
|
||||
WidgetsBinding.instance.addPostFrameCallback((_) {
|
||||
if (!mounted) return;
|
||||
context.read<RepositoriesProvider>().loadRepositories();
|
||||
});
|
||||
}
|
||||
final configuredRepos = repositoriesProvider.repositories;
|
||||
final availableRepos =
|
||||
widget.app.availableRepositories ?? const <RepositorySource>[];
|
||||
|
||||
final currentRepoUrl = _normalizeRepoUrl(widget.app.repositoryUrl);
|
||||
final currentRepoPath = _normalizeRepoPath(widget.app.repositoryUrl);
|
||||
final currentRepoHost = Uri.tryParse(currentRepoUrl)?.host ?? '';
|
||||
final currentConfiguredName = configuredRepos
|
||||
.where(
|
||||
(repo) =>
|
||||
_normalizeRepoUrl(repo.url) == currentRepoUrl ||
|
||||
_normalizeRepoPath(repo.url) == currentRepoPath ||
|
||||
(currentRepoHost.isNotEmpty &&
|
||||
(Uri.tryParse(_normalizeRepoUrl(repo.url))?.host ?? '') ==
|
||||
currentRepoHost),
|
||||
)
|
||||
.map((repo) => repo.name)
|
||||
.firstWhere((name) => name.isNotEmpty, orElse: () => '');
|
||||
|
||||
final currentAvailableRepo = availableRepos.firstWhere(
|
||||
(repo) => _normalizeRepoUrl(repo.url) == currentRepoUrl,
|
||||
orElse: () => RepositorySource(name: '', url: ''),
|
||||
);
|
||||
|
||||
final currentRepositoryName = currentConfiguredName.isNotEmpty
|
||||
? currentConfiguredName
|
||||
: (currentAvailableRepo.name.isNotEmpty
|
||||
? currentAvailableRepo.name
|
||||
: AppLocalizations.of(context)!.unknown);
|
||||
|
||||
final otherRepositoryNames = <String>{};
|
||||
for (final repo in availableRepos) {
|
||||
final repoUrl = _normalizeRepoUrl(repo.url);
|
||||
if (repoUrl == currentRepoUrl) continue;
|
||||
|
||||
final configuredName = configuredRepos
|
||||
.where(
|
||||
(r) =>
|
||||
_normalizeRepoUrl(r.url) == repoUrl ||
|
||||
_normalizeRepoPath(r.url) == _normalizeRepoPath(repo.url),
|
||||
)
|
||||
.map((r) => r.name)
|
||||
.firstWhere((name) => name.isNotEmpty, orElse: () => '');
|
||||
|
||||
final resolvedName = configuredName.isNotEmpty
|
||||
? configuredName
|
||||
: repo.name;
|
||||
if (resolvedName.isNotEmpty) {
|
||||
otherRepositoryNames.add(resolvedName);
|
||||
}
|
||||
}
|
||||
|
||||
return Column(
|
||||
@@ -2807,21 +2888,36 @@ class _AppExtraInfoSectionState extends State<AppExtraInfoSection> {
|
||||
],
|
||||
),
|
||||
SizedBox(height: 16),
|
||||
MListHeader(
|
||||
icon: Symbols.volunteer_activism_rounded,
|
||||
title: AppLocalizations.of(context)!.support_the_developer,
|
||||
),
|
||||
MListView(
|
||||
items: [
|
||||
for (final item in donationItems)
|
||||
MListItemData(
|
||||
title: item.key,
|
||||
subtitle: item.value,
|
||||
suffix: const Icon(Symbols.open_in_new_rounded),
|
||||
onTap: () => _openDonateLink(context, item.key, item.value),
|
||||
),
|
||||
],
|
||||
if (donationItems.isNotEmpty) ...[
|
||||
MListHeader(
|
||||
icon: Symbols.volunteer_activism_rounded,
|
||||
title: AppLocalizations.of(context)!.support_the_developer,
|
||||
),
|
||||
MListView(
|
||||
items: [
|
||||
for (final item in donationItems)
|
||||
MListItemData(
|
||||
title: item.key,
|
||||
subtitle: item.value,
|
||||
suffix: const Icon(Symbols.open_in_new_rounded),
|
||||
onTap: () => _openDonateLink(context, item.key, item.value),
|
||||
),
|
||||
],
|
||||
),
|
||||
],
|
||||
SizedBox(height: 16),
|
||||
Text(
|
||||
AppLocalizations.of(
|
||||
context,
|
||||
)!.available_on_repository(currentRepositoryName),
|
||||
style: Theme.of(context).textTheme.labelMedium,
|
||||
),
|
||||
if (otherRepositoryNames.isNotEmpty)
|
||||
Text(
|
||||
AppLocalizations.of(context)!.also_available_from_repositories(
|
||||
otherRepositoryNames.join(', '),
|
||||
),
|
||||
),
|
||||
],
|
||||
);
|
||||
}
|
||||
@@ -2887,6 +2983,7 @@ class _AllVersionsSectionState extends State<_AllVersionsSection> {
|
||||
final enrichedApp = await appProvider.enrichAppWithRepositories(
|
||||
widget.app,
|
||||
context.read<RepositoriesProvider>(),
|
||||
allowNetworkFallback: false,
|
||||
);
|
||||
|
||||
void addFallbackCurrentRepo() {
|
||||
|
||||
Reference in New Issue
Block a user