mirror of
https://github.com/ImranR98/Obtainium.git
synced 2025-08-01 05:10:15 +02:00
More search options (#1107)
This commit is contained in:
@@ -223,7 +223,7 @@
|
||||
"moveNonInstalledAppsToBottom": "Premjesti neinstalirane aplikacije na dno prikaza aplikacija",
|
||||
"gitlabPATLabel": "GitLab token za lični pristup\n(Omogućava pretraživanje i bolje otkrivanje APK-a)",
|
||||
"about": "O nama",
|
||||
"requiresCredentialsInSettings": "Za ovo su potrebni dodatni akreditivi (u Postavkama)",
|
||||
"requiresCredentialsInSettings": "{}: Za ovo su potrebni dodatni akreditivi (u Postavkama)",
|
||||
"checkOnStart": "Provjerite ima li novosti pri pokretanju",
|
||||
"tryInferAppIdFromCode": "Pokušati otkriti ID aplikacije iz izvornog koda",
|
||||
"removeOnExternalUninstall": "Automatski ukloni eksterno deinstalirane aplikacije",
|
||||
|
@@ -223,7 +223,7 @@
|
||||
"moveNonInstalledAppsToBottom": "Přesunout nenainstalované aplikace na konec zobrazení Aplikace",
|
||||
"gitlabPATLabel": "GitLab Personal Access Token\n(Umožňuje vyhledávání a lepší zjišťování APK)",
|
||||
"about": "About",
|
||||
"requiresCredentialsInSettings": "Vyžaduje další pověření (v nastavení)",
|
||||
"requiresCredentialsInSettings": "{}: Vyžaduje další pověření (v nastavení)",
|
||||
"checkOnStart": "Zkontrolovat jednou při spuštění",
|
||||
"tryInferAppIdFromCode": "Pokusit se určit ID aplikace ze zdrojového kódu",
|
||||
"removeOnExternalUninstall": "Automaticky odstranit externě odinstalované aplikace",
|
||||
|
@@ -223,7 +223,7 @@
|
||||
"moveNonInstalledAppsToBottom": "Nicht installierte Apps ans Ende der Apps Ansicht verschieben",
|
||||
"gitlabPATLabel": "GitLab Personal Access Token\n(Aktiviert Suche und bessere APK Entdeckung)",
|
||||
"about": "Über",
|
||||
"requiresCredentialsInSettings": "Benötigt zusätzliche Anmeldedaten (in den Einstellungen)",
|
||||
"requiresCredentialsInSettings": "{}: Benötigt zusätzliche Anmeldedaten (in den Einstellungen)",
|
||||
"checkOnStart": "Überprüfe einmalig beim Start",
|
||||
"tryInferAppIdFromCode": "Versuche, die App-ID aus dem Quellcode zu ermitteln",
|
||||
"removeOnExternalUninstall": "Automatisches Entfernen von extern deinstallierten Apps",
|
||||
|
@@ -223,7 +223,7 @@
|
||||
"moveNonInstalledAppsToBottom": "Move non-installed Apps to bottom of Apps view",
|
||||
"gitlabPATLabel": "GitLab Personal Access Token\n(Enables Search and Better APK Discovery)",
|
||||
"about": "About",
|
||||
"requiresCredentialsInSettings": "This needs additional credentials (in Settings)",
|
||||
"requiresCredentialsInSettings": "{} needs additional credentials (in Settings)",
|
||||
"checkOnStart": "Check for updates on startup",
|
||||
"tryInferAppIdFromCode": "Try inferring App ID from source code",
|
||||
"removeOnExternalUninstall": "Automatically remove externally uninstalled Apps",
|
||||
|
@@ -223,7 +223,7 @@
|
||||
"moveNonInstalledAppsToBottom": "Move non-installed Apps to bottom of Apps view",
|
||||
"gitlabPATLabel": "GitLab Personal Access Token\n(Enables Search and Better APK Discovery)",
|
||||
"about": "About",
|
||||
"requiresCredentialsInSettings": "This needs additional credentials (in Settings)",
|
||||
"requiresCredentialsInSettings": "{}: This needs additional credentials (in Settings)",
|
||||
"checkOnStart": "Check for updates on startup",
|
||||
"tryInferAppIdFromCode": "Try inferring App ID from source code",
|
||||
"removeOnExternalUninstall": "Automatically remove externally uninstalled Apps",
|
||||
|
@@ -223,7 +223,7 @@
|
||||
"moveNonInstalledAppsToBottom": "برنامه های نصب نشده را به نمای پایین برنامه ها منتقل کنید",
|
||||
"gitlabPATLabel": "رمز دسترسی شخصی GitLab\n(جستجو و کشف بهتر APK را فعال میکند)",
|
||||
"about": "درباره",
|
||||
"requiresCredentialsInSettings": "این به اعتبارنامه های اضافی نیاز دارد (در تنظیمات)",
|
||||
"requiresCredentialsInSettings": "{}: این به اعتبارنامه های اضافی نیاز دارد (در تنظیمات)",
|
||||
"checkOnStart": "بررسی در شروع",
|
||||
"tryInferAppIdFromCode": "شناسه برنامه را از کد منبع استنباط کنید",
|
||||
"removeOnExternalUninstall": "حذف خودکار برنامه های حذف نصب شده خارجی",
|
||||
|
@@ -223,7 +223,7 @@
|
||||
"moveNonInstalledAppsToBottom": "Move non-installed Apps to bottom of Apps view",
|
||||
"gitlabPATLabel": "GitLab Personal Access Token\n(Enables Search and Better APK Discovery)",
|
||||
"about": "About",
|
||||
"requiresCredentialsInSettings": "This needs additional credentials (in Settings)",
|
||||
"requiresCredentialsInSettings": "{}: This needs additional credentials (in Settings)",
|
||||
"checkOnStart": "Check for updates on startup",
|
||||
"tryInferAppIdFromCode": "Try inferring App ID from source code",
|
||||
"removeOnExternalUninstall": "Automatically remove externally uninstalled Apps",
|
||||
|
@@ -223,7 +223,7 @@
|
||||
"moveNonInstalledAppsToBottom": "Helyezze át a nem telepített appokat az App nézet aljára",
|
||||
"gitlabPATLabel": "GitLab Personal Access Token\n(Engedélyezi a Keresést és jobb APK felfedezés)",
|
||||
"about": "Rólunk",
|
||||
"requiresCredentialsInSettings": "Ehhez további hitelesítő adatokra van szükség (a Beállításokban)",
|
||||
"requiresCredentialsInSettings": "{}: Ehhez további hitelesítő adatokra van szükség (a Beállításokban)",
|
||||
"checkOnStart": "Egyszer az alkalmazás indításakor is",
|
||||
"tryInferAppIdFromCode": "Próbálja kikövetkeztetni az app azonosítót a forráskódból",
|
||||
"removeOnExternalUninstall": "A külsőleg eltávolított appok auto. eltávolítása",
|
||||
|
@@ -223,7 +223,7 @@
|
||||
"moveNonInstalledAppsToBottom": "Sposta le app non installate in fondo alla lista",
|
||||
"gitlabPATLabel": "GitLab Personal Access Token\n(attiva la ricerca e migliora la rilevazione di apk)",
|
||||
"about": "Informazioni",
|
||||
"requiresCredentialsInSettings": "Servono credenziali aggiuntive (in Impostazioni)",
|
||||
"requiresCredentialsInSettings": "{}: Servono credenziali aggiuntive (in Impostazioni)",
|
||||
"checkOnStart": "Controlla una volta all'avvio",
|
||||
"tryInferAppIdFromCode": "Prova a dedurre l'ID dell'app dal codice sorgente",
|
||||
"removeOnExternalUninstall": "Rimuovi automaticamente app disinstallate esternamente",
|
||||
|
@@ -223,7 +223,7 @@
|
||||
"moveNonInstalledAppsToBottom": "未インストールのアプリをアプリ一覧の下部に移動させる",
|
||||
"gitlabPATLabel": "GitLab パーソナルアクセストークン\n(検索とより良いAPK検出の有効化)",
|
||||
"about": "概要",
|
||||
"requiresCredentialsInSettings": "これには追加の認証が必要です (設定にて)",
|
||||
"requiresCredentialsInSettings": "{}: これには追加の認証が必要です (設定にて)",
|
||||
"checkOnStart": "起動時にアップデートを確認する",
|
||||
"tryInferAppIdFromCode": "ソースコードからApp IDを推測する",
|
||||
"removeOnExternalUninstall": "外部でアンインストールされたアプリを自動的に削除する",
|
||||
|
@@ -223,7 +223,7 @@
|
||||
"moveNonInstalledAppsToBottom": "Verplaats niet-geïnstalleerde apps naar de onderkant van de apps-weergave",
|
||||
"gitlabPATLabel": "GitLab Personal Access Token\n(Maakt het mogelijk beter te zoeken naar APK's)",
|
||||
"about": "Over",
|
||||
"requiresCredentialsInSettings": "Dit vereist aanvullende referenties (in Instellingen)",
|
||||
"requiresCredentialsInSettings": "{}: Dit vereist aanvullende referenties (in Instellingen)",
|
||||
"checkOnStart": "Controleren op updates bij opstarten",
|
||||
"tryInferAppIdFromCode": "Probeer de app-ID af te leiden uit de broncode",
|
||||
"removeOnExternalUninstall": "Automatisch extern verwijderde apps verwijderen",
|
||||
|
@@ -223,7 +223,7 @@
|
||||
"moveNonInstalledAppsToBottom": "Przenieś niezainstalowane aplikacje na dół widoku aplikacji",
|
||||
"gitlabPATLabel": "Osobisty token dostępu GitLab\n(Umożliwia wyszukiwanie i lepsze wykrywanie APK)",
|
||||
"about": "Więcej informacji",
|
||||
"requiresCredentialsInSettings": "Wymaga to dodatkowych poświadczeń (w Ustawieniach)",
|
||||
"requiresCredentialsInSettings": "{}: Wymaga to dodatkowych poświadczeń (w Ustawieniach)",
|
||||
"checkOnStart": "Sprawdź aktualizacje przy uruchomieniu",
|
||||
"tryInferAppIdFromCode": "Spróbuj wywnioskować identyfikator aplikacji z kodu źródłowego",
|
||||
"removeOnExternalUninstall": "Automatyczne usuń odinstalowane zewnętrznie aplikacje",
|
||||
|
@@ -223,7 +223,7 @@
|
||||
"moveNonInstalledAppsToBottom": "Mover Apps não instalados para o fundo da visão de Apps",
|
||||
"gitlabPATLabel": "Token de Acceso Pessoal do Gitlab\n(Ativa Pesquisa e Melhor Descoberta de APKs)",
|
||||
"about": "Sobre",
|
||||
"requiresCredentialsInSettings": "Isso requer credenciais adicionais (em Configurações)",
|
||||
"requiresCredentialsInSettings": "{}: Isso requer credenciais adicionais (em Configurações)",
|
||||
"checkOnStart": "Checar por atualizações ao iniciar ",
|
||||
"tryInferAppIdFromCode": "Tente inferir o ID do App pelo código fonte",
|
||||
"removeOnExternalUninstall": "Remover automaticamente Apps desinstalados externamente",
|
||||
|
@@ -223,7 +223,7 @@
|
||||
"moveNonInstalledAppsToBottom": "Отображать неустановленные приложения внизу списка",
|
||||
"gitlabPATLabel": "Персональный токен доступа GitLab\n(включает поиск и улучшает обнаружение APK)",
|
||||
"about": "Описание",
|
||||
"requiresCredentialsInSettings": "Для этого требуются дополнительные учетные данные (в настройках)",
|
||||
"requiresCredentialsInSettings": "{}: Для этого требуются дополнительные учетные данные (в настройках)",
|
||||
"checkOnStart": "Проверять наличие обновлений при запуске",
|
||||
"tryInferAppIdFromCode": "Попытаться определить ID приложения из исходного кода",
|
||||
"removeOnExternalUninstall": "Автоматически убирать из списка удаленные извне приложения",
|
||||
|
@@ -223,7 +223,7 @@
|
||||
"moveNonInstalledAppsToBottom": "Move non-installed Apps to bottom of Apps view",
|
||||
"gitlabPATLabel": "GitLab Personal Access Token\n(Enables Search and Better APK Discovery)",
|
||||
"about": "Om",
|
||||
"requiresCredentialsInSettings": "This needs additional credentials (in Settings)",
|
||||
"requiresCredentialsInSettings": "{}: This needs additional credentials (in Settings)",
|
||||
"checkOnStart": "Kolla efter uppdateringar vid start",
|
||||
"tryInferAppIdFromCode": "Try inferring App ID from source code",
|
||||
"removeOnExternalUninstall": "Automatically remove externally uninstalled Apps",
|
||||
|
@@ -223,7 +223,7 @@
|
||||
"moveNonInstalledAppsToBottom": "Yüklenmemiş Uygulamaları Uygulamalar Görünümünün Altına Taşı",
|
||||
"gitlabPATLabel": "GitLab Kişisel Erişim Belirteci\n(Arama ve Daha İyi APK Keşfi İçin)",
|
||||
"about": "Hakkında",
|
||||
"requiresCredentialsInSettings": "Bu, ek kimlik bilgilerine ihtiyaç duyar (Ayarlar'da)",
|
||||
"requiresCredentialsInSettings": "{}: Bu, ek kimlik bilgilerine ihtiyaç duyar (Ayarlar'da)",
|
||||
"checkOnStart": "Başlangıçta güncellemeleri kontrol et",
|
||||
"tryInferAppIdFromCode": "Uygulama kimliğini kaynak kodundan çıkarma girişimi",
|
||||
"removeOnExternalUninstall": "Harici kaldırmada otomatik olarak kaldırılan uygulamalar",
|
||||
|
@@ -223,7 +223,7 @@
|
||||
"moveNonInstalledAppsToBottom": "Di chuyển Ứng dụng chưa được cài đặt xuống cuối chế độ xem Ứng dụng",
|
||||
"gitlabPATLabel": "Mã thông báo truy cập cá nhân GitLab\n(Cho phép tìm kiếm và khám phá APK tốt hơn)",
|
||||
"about": "Giới thiệu",
|
||||
"requiresCredentialsInSettings": "Điều này cần thông tin xác thực bổ sung (trong Cài đặt)",
|
||||
"requiresCredentialsInSettings": "{}: Điều này cần thông tin xác thực bổ sung (trong Cài đặt)",
|
||||
"checkOnStart": "Kiểm tra các bản cập nhật khi khởi động",
|
||||
"tryInferAppIdFromCode": "Thử suy ra ID ứng dụng từ mã nguồn",
|
||||
"removeOnExternalUninstall": "Tự động xóa ứng dụng đã gỡ cài đặt bên ngoài",
|
||||
|
@@ -223,7 +223,7 @@
|
||||
"moveNonInstalledAppsToBottom": "将未安装应用置底",
|
||||
"gitlabPATLabel": "GitLab 个人访问令牌(启用搜索功能并增强 APK 发现)",
|
||||
"about": "相关文档",
|
||||
"requiresCredentialsInSettings": "此功能需要额外的凭据(在“设置”中添加)",
|
||||
"requiresCredentialsInSettings": "{}: 此功能需要额外的凭据(在“设置”中添加)",
|
||||
"checkOnStart": "启动时进行一次检查",
|
||||
"tryInferAppIdFromCode": "尝试从源代码推断应用 ID",
|
||||
"removeOnExternalUninstall": "自动删除已卸载的外部应用",
|
||||
|
@@ -254,13 +254,30 @@ class _AddAppPageState extends State<AddAppPage> {
|
||||
],
|
||||
);
|
||||
|
||||
runSearch() async {
|
||||
runSearch({bool filtered = true}) async {
|
||||
setState(() {
|
||||
searching = true;
|
||||
});
|
||||
var sourceStrings = <String, List<String>>{};
|
||||
sourceProvider.sources
|
||||
.where((e) => e.canSearch && !e.excludeFromMassSearch)
|
||||
.forEach((s) {
|
||||
sourceStrings[s.name] = [s.name];
|
||||
});
|
||||
try {
|
||||
var searchSources = await showDialog<List<String>?>(
|
||||
context: context,
|
||||
builder: (BuildContext ctx) {
|
||||
return SelectionModal(
|
||||
entries: sourceStrings,
|
||||
selectedByDefault: true,
|
||||
onlyOneSelectionAllowed: false,
|
||||
titlesAreLinks: false,
|
||||
);
|
||||
}) ??
|
||||
[];
|
||||
var results = await Future.wait(sourceProvider.sources
|
||||
.where((e) => e.canSearch && !e.excludeFromMassSearch)
|
||||
.where((e) => searchSources.contains(e.name))
|
||||
.map((e) async {
|
||||
try {
|
||||
return await e.search(searchQuery);
|
||||
@@ -268,6 +285,8 @@ class _AddAppPageState extends State<AddAppPage> {
|
||||
if (err is! CredsNeededError) {
|
||||
rethrow;
|
||||
} else {
|
||||
err.unexpected = true;
|
||||
showError(err, context);
|
||||
return <String, List<String>>{};
|
||||
}
|
||||
}
|
||||
@@ -297,8 +316,8 @@ class _AddAppPageState extends State<AddAppPage> {
|
||||
: await showDialog<List<String>?>(
|
||||
context: context,
|
||||
builder: (BuildContext ctx) {
|
||||
return UrlSelectionModal(
|
||||
urlsWithDescriptions: res,
|
||||
return SelectionModal(
|
||||
entries: res,
|
||||
selectedByDefault: false,
|
||||
onlyOneSelectionAllowed: true,
|
||||
);
|
||||
@@ -470,23 +489,21 @@ class _AddAppPageState extends State<AddAppPage> {
|
||||
const SizedBox(
|
||||
height: 16,
|
||||
),
|
||||
...sourceProvider.sources
|
||||
.map((e) => GestureDetector(
|
||||
onTap: e.host != null
|
||||
? () {
|
||||
launchUrlString('https://${e.host}',
|
||||
mode: LaunchMode.externalApplication);
|
||||
}
|
||||
: null,
|
||||
child: Text(
|
||||
'${e.name}${e.enforceTrackOnly ? ' ${tr('trackOnlyInBrackets')}' : ''}${e.canSearch ? ' ${tr('searchableInBrackets')}' : ''}',
|
||||
style: TextStyle(
|
||||
decoration: e.host != null
|
||||
? TextDecoration.underline
|
||||
: TextDecoration.none,
|
||||
fontStyle: FontStyle.italic),
|
||||
)))
|
||||
|
||||
...sourceProvider.sources.map((e) => GestureDetector(
|
||||
onTap: e.host != null
|
||||
? () {
|
||||
launchUrlString('https://${e.host}',
|
||||
mode: LaunchMode.externalApplication);
|
||||
}
|
||||
: null,
|
||||
child: Text(
|
||||
'${e.name}${e.enforceTrackOnly ? ' ${tr('trackOnlyInBrackets')}' : ''}${e.canSearch ? ' ${tr('searchableInBrackets')}' : ''}',
|
||||
style: TextStyle(
|
||||
decoration: e.host != null
|
||||
? TextDecoration.underline
|
||||
: TextDecoration.none,
|
||||
fontStyle: FontStyle.italic),
|
||||
)))
|
||||
]);
|
||||
|
||||
return Scaffold(
|
||||
|
@@ -208,8 +208,8 @@ class _ImportExportPageState extends State<ImportExportPage> {
|
||||
await showDialog<List<String>?>(
|
||||
context: context,
|
||||
builder: (BuildContext ctx) {
|
||||
return UrlSelectionModal(
|
||||
urlsWithDescriptions: urlsWithDescriptions,
|
||||
return SelectionModal(
|
||||
entries: urlsWithDescriptions,
|
||||
selectedByDefault: false,
|
||||
);
|
||||
});
|
||||
@@ -269,8 +269,7 @@ class _ImportExportPageState extends State<ImportExportPage> {
|
||||
await showDialog<List<String>?>(
|
||||
context: context,
|
||||
builder: (BuildContext ctx) {
|
||||
return UrlSelectionModal(
|
||||
urlsWithDescriptions: urlsWithDescriptions);
|
||||
return SelectionModal(entries: urlsWithDescriptions);
|
||||
});
|
||||
if (selectedUrls != null) {
|
||||
var errors = await appsProvider.addAppsByURL(selectedUrls);
|
||||
@@ -300,6 +299,11 @@ class _ImportExportPageState extends State<ImportExportPage> {
|
||||
});
|
||||
}
|
||||
|
||||
var sourceStrings = <String, List<String>>{};
|
||||
sourceProvider.sources.where((e) => e.canSearch).forEach((s) {
|
||||
sourceStrings[s.name] = [s.name];
|
||||
});
|
||||
|
||||
return Scaffold(
|
||||
backgroundColor: Theme.of(context).colorScheme.surface,
|
||||
body: CustomScrollView(slivers: <Widget>[
|
||||
@@ -409,6 +413,49 @@ class _ImportExportPageState extends State<ImportExportPage> {
|
||||
const Divider(
|
||||
height: 32,
|
||||
),
|
||||
Row(
|
||||
children: [
|
||||
Expanded(
|
||||
child: TextButton(
|
||||
onPressed: importInProgress
|
||||
? null
|
||||
: () async {
|
||||
var searchSourceName =
|
||||
await showDialog<
|
||||
List<String>?>(
|
||||
context: context,
|
||||
builder:
|
||||
(BuildContext
|
||||
ctx) {
|
||||
return SelectionModal(
|
||||
entries:
|
||||
sourceStrings,
|
||||
selectedByDefault:
|
||||
false,
|
||||
onlyOneSelectionAllowed:
|
||||
true,
|
||||
titlesAreLinks:
|
||||
false,
|
||||
);
|
||||
}) ??
|
||||
[];
|
||||
var searchSource =
|
||||
sourceProvider.sources
|
||||
.where((e) =>
|
||||
searchSourceName
|
||||
.contains(
|
||||
e.name))
|
||||
.toList();
|
||||
if (searchSource.isNotEmpty) {
|
||||
runSourceSearch(
|
||||
searchSource[0]);
|
||||
}
|
||||
},
|
||||
child: Text(tr('searchX',
|
||||
args: [tr('source')])))),
|
||||
],
|
||||
),
|
||||
const SizedBox(height: 8),
|
||||
TextButton(
|
||||
onPressed:
|
||||
importInProgress ? null : urlListImport,
|
||||
@@ -424,39 +471,19 @@ class _ImportExportPageState extends State<ImportExportPage> {
|
||||
)),
|
||||
],
|
||||
),
|
||||
...sourceProvider.sources
|
||||
.where((element) => element.canSearch)
|
||||
.map((source) => Column(
|
||||
crossAxisAlignment:
|
||||
CrossAxisAlignment.stretch,
|
||||
children: [
|
||||
const SizedBox(height: 8),
|
||||
TextButton(
|
||||
onPressed: importInProgress
|
||||
? null
|
||||
: () {
|
||||
runSourceSearch(source);
|
||||
},
|
||||
child: Text(
|
||||
tr('searchX', args: [source.name])))
|
||||
]))
|
||||
,
|
||||
...sourceProvider.massUrlSources
|
||||
.map((source) => Column(
|
||||
crossAxisAlignment:
|
||||
CrossAxisAlignment.stretch,
|
||||
children: [
|
||||
const SizedBox(height: 8),
|
||||
TextButton(
|
||||
onPressed: importInProgress
|
||||
? null
|
||||
: () {
|
||||
runMassSourceImport(source);
|
||||
},
|
||||
child: Text(
|
||||
tr('importX', args: [source.name])))
|
||||
]))
|
||||
,
|
||||
...sourceProvider.massUrlSources.map((source) => Column(
|
||||
crossAxisAlignment: CrossAxisAlignment.stretch,
|
||||
children: [
|
||||
const SizedBox(height: 8),
|
||||
TextButton(
|
||||
onPressed: importInProgress
|
||||
? null
|
||||
: () {
|
||||
runMassSourceImport(source);
|
||||
},
|
||||
child: Text(
|
||||
tr('importX', args: [source.name])))
|
||||
])),
|
||||
const Spacer(),
|
||||
const Divider(
|
||||
height: 32,
|
||||
@@ -532,38 +559,40 @@ class _ImportErrorDialogState extends State<ImportErrorDialog> {
|
||||
}
|
||||
|
||||
// ignore: must_be_immutable
|
||||
class UrlSelectionModal extends StatefulWidget {
|
||||
UrlSelectionModal(
|
||||
class SelectionModal extends StatefulWidget {
|
||||
SelectionModal(
|
||||
{super.key,
|
||||
required this.urlsWithDescriptions,
|
||||
required this.entries,
|
||||
this.selectedByDefault = true,
|
||||
this.onlyOneSelectionAllowed = false});
|
||||
this.onlyOneSelectionAllowed = false,
|
||||
this.titlesAreLinks = true});
|
||||
|
||||
Map<String, List<String>> urlsWithDescriptions;
|
||||
Map<String, List<String>> entries;
|
||||
bool selectedByDefault;
|
||||
bool onlyOneSelectionAllowed;
|
||||
bool titlesAreLinks;
|
||||
|
||||
@override
|
||||
State<UrlSelectionModal> createState() => _UrlSelectionModalState();
|
||||
State<SelectionModal> createState() => _SelectionModalState();
|
||||
}
|
||||
|
||||
class _UrlSelectionModalState extends State<UrlSelectionModal> {
|
||||
Map<MapEntry<String, List<String>>, bool> urlWithDescriptionSelections = {};
|
||||
class _SelectionModalState extends State<SelectionModal> {
|
||||
Map<MapEntry<String, List<String>>, bool> entrySelections = {};
|
||||
@override
|
||||
void initState() {
|
||||
super.initState();
|
||||
for (var url in widget.urlsWithDescriptions.entries) {
|
||||
urlWithDescriptionSelections.putIfAbsent(url,
|
||||
for (var url in widget.entries.entries) {
|
||||
entrySelections.putIfAbsent(url,
|
||||
() => widget.selectedByDefault && !widget.onlyOneSelectionAllowed);
|
||||
}
|
||||
if (widget.selectedByDefault && widget.onlyOneSelectionAllowed) {
|
||||
selectOnlyOne(widget.urlsWithDescriptions.entries.first.key);
|
||||
selectOnlyOne(widget.entries.entries.first.key);
|
||||
}
|
||||
}
|
||||
|
||||
selectOnlyOne(String url) {
|
||||
for (var uwd in urlWithDescriptionSelections.keys) {
|
||||
urlWithDescriptionSelections[uwd] = uwd.key == url;
|
||||
for (var e in entrySelections.keys) {
|
||||
entrySelections[e] = e.key == url;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -571,73 +600,88 @@ class _UrlSelectionModalState extends State<UrlSelectionModal> {
|
||||
Widget build(BuildContext context) {
|
||||
return AlertDialog(
|
||||
scrollable: true,
|
||||
title: Text(
|
||||
widget.onlyOneSelectionAllowed ? tr('selectURL') : tr('selectURLs')),
|
||||
title: Text(tr('select')),
|
||||
content: Column(children: [
|
||||
...urlWithDescriptionSelections.keys.map((urlWithD) {
|
||||
...entrySelections.keys.map((entry) {
|
||||
selectThis(bool? value) {
|
||||
setState(() {
|
||||
value ??= false;
|
||||
if (value! && widget.onlyOneSelectionAllowed) {
|
||||
selectOnlyOne(urlWithD.key);
|
||||
selectOnlyOne(entry.key);
|
||||
} else {
|
||||
urlWithDescriptionSelections[urlWithD] = value!;
|
||||
entrySelections[entry] = value!;
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
var urlLink = GestureDetector(
|
||||
onTap: () {
|
||||
launchUrlString(urlWithD.key,
|
||||
mode: LaunchMode.externalApplication);
|
||||
},
|
||||
onTap: !widget.titlesAreLinks
|
||||
? null
|
||||
: () {
|
||||
launchUrlString(entry.key,
|
||||
mode: LaunchMode.externalApplication);
|
||||
},
|
||||
child: Column(
|
||||
crossAxisAlignment: CrossAxisAlignment.start,
|
||||
children: [
|
||||
Text(
|
||||
urlWithD.value[0],
|
||||
style: const TextStyle(
|
||||
decoration: TextDecoration.underline,
|
||||
entry.value.isEmpty ? entry.key : entry.value[0],
|
||||
style: TextStyle(
|
||||
decoration: widget.titlesAreLinks
|
||||
? TextDecoration.underline
|
||||
: null,
|
||||
fontWeight: FontWeight.bold),
|
||||
textAlign: TextAlign.start,
|
||||
),
|
||||
Text(
|
||||
Uri.parse(urlWithD.key).host,
|
||||
style: const TextStyle(
|
||||
decoration: TextDecoration.underline, fontSize: 12),
|
||||
)
|
||||
if (widget.titlesAreLinks)
|
||||
Text(
|
||||
Uri.parse(entry.key).host,
|
||||
style: const TextStyle(
|
||||
decoration: TextDecoration.underline, fontSize: 12),
|
||||
)
|
||||
],
|
||||
));
|
||||
|
||||
var descriptionText = Text(
|
||||
urlWithD.value[1].length > 128
|
||||
? '${urlWithD.value[1].substring(0, 128)}...'
|
||||
: urlWithD.value[1],
|
||||
style: const TextStyle(fontStyle: FontStyle.italic, fontSize: 12),
|
||||
);
|
||||
var descriptionText = entry.value.length <= 1
|
||||
? const SizedBox.shrink()
|
||||
: Text(
|
||||
entry.value[1].length > 128
|
||||
? '${entry.value[1].substring(0, 128)}...'
|
||||
: entry.value[1],
|
||||
style: const TextStyle(
|
||||
fontStyle: FontStyle.italic, fontSize: 12),
|
||||
);
|
||||
|
||||
var selectedUrlsWithDs = urlWithDescriptionSelections.entries
|
||||
.where((e) => e.value)
|
||||
.toList();
|
||||
var selectedEntries =
|
||||
entrySelections.entries.where((e) => e.value).toList();
|
||||
|
||||
var singleSelectTile = ListTile(
|
||||
title: urlLink,
|
||||
subtitle: GestureDetector(
|
||||
onTap: () {
|
||||
setState(() {
|
||||
selectOnlyOne(urlWithD.key);
|
||||
});
|
||||
},
|
||||
child: descriptionText,
|
||||
),
|
||||
leading: Radio<String>(
|
||||
value: urlWithD.key,
|
||||
groupValue: selectedUrlsWithDs.isEmpty
|
||||
title: GestureDetector(
|
||||
onTap: widget.titlesAreLinks
|
||||
? null
|
||||
: selectedUrlsWithDs.first.key.key,
|
||||
: () {
|
||||
selectThis(!(entrySelections[entry] ?? false));
|
||||
},
|
||||
child: urlLink,
|
||||
),
|
||||
subtitle: entry.value.length <= 1
|
||||
? null
|
||||
: GestureDetector(
|
||||
onTap: () {
|
||||
setState(() {
|
||||
selectOnlyOne(entry.key);
|
||||
});
|
||||
},
|
||||
child: descriptionText,
|
||||
),
|
||||
leading: Radio<String>(
|
||||
value: entry.key,
|
||||
groupValue: selectedEntries.isEmpty
|
||||
? null
|
||||
: selectedEntries.first.key.key,
|
||||
onChanged: (value) {
|
||||
setState(() {
|
||||
selectOnlyOne(urlWithD.key);
|
||||
selectOnlyOne(entry.key);
|
||||
});
|
||||
},
|
||||
),
|
||||
@@ -645,7 +689,7 @@ class _UrlSelectionModalState extends State<UrlSelectionModal> {
|
||||
|
||||
var multiSelectTile = Row(children: [
|
||||
Checkbox(
|
||||
value: urlWithDescriptionSelections[urlWithD],
|
||||
value: entrySelections[entry],
|
||||
onChanged: (value) {
|
||||
selectThis(value);
|
||||
}),
|
||||
@@ -657,20 +701,30 @@ class _UrlSelectionModalState extends State<UrlSelectionModal> {
|
||||
crossAxisAlignment: CrossAxisAlignment.start,
|
||||
mainAxisAlignment: MainAxisAlignment.center,
|
||||
children: [
|
||||
const SizedBox(
|
||||
height: 8,
|
||||
SizedBox(
|
||||
height: entry.value.length <= 1 ? 16 : 8,
|
||||
),
|
||||
urlLink,
|
||||
GestureDetector(
|
||||
onTap: () {
|
||||
selectThis(
|
||||
!(urlWithDescriptionSelections[urlWithD] ?? false));
|
||||
},
|
||||
child: descriptionText,
|
||||
onTap: widget.titlesAreLinks
|
||||
? null
|
||||
: () {
|
||||
selectThis(!(entrySelections[entry] ?? false));
|
||||
},
|
||||
child: urlLink,
|
||||
),
|
||||
const SizedBox(
|
||||
height: 8,
|
||||
)
|
||||
entry.value.length <= 1
|
||||
? const SizedBox.shrink()
|
||||
: GestureDetector(
|
||||
onTap: () {
|
||||
selectThis(!(entrySelections[entry] ?? false));
|
||||
},
|
||||
child: descriptionText,
|
||||
),
|
||||
entry.value.length <= 1
|
||||
? const SizedBox.shrink()
|
||||
: const SizedBox(
|
||||
height: 8,
|
||||
)
|
||||
],
|
||||
))
|
||||
]);
|
||||
@@ -687,24 +741,18 @@ class _UrlSelectionModalState extends State<UrlSelectionModal> {
|
||||
},
|
||||
child: Text(tr('cancel'))),
|
||||
TextButton(
|
||||
onPressed:
|
||||
urlWithDescriptionSelections.values.where((b) => b).isEmpty
|
||||
? null
|
||||
: () {
|
||||
Navigator.of(context).pop(urlWithDescriptionSelections
|
||||
.entries
|
||||
.where((entry) => entry.value)
|
||||
.map((e) => e.key.key)
|
||||
.toList());
|
||||
},
|
||||
onPressed: entrySelections.values.where((b) => b).isEmpty
|
||||
? null
|
||||
: () {
|
||||
Navigator.of(context).pop(entrySelections.entries
|
||||
.where((entry) => entry.value)
|
||||
.map((e) => e.key.key)
|
||||
.toList());
|
||||
},
|
||||
child: Text(widget.onlyOneSelectionAllowed
|
||||
? tr('pick')
|
||||
: tr('importX', args: [
|
||||
plural(
|
||||
'url',
|
||||
urlWithDescriptionSelections.values
|
||||
.where((b) => b)
|
||||
.length)
|
||||
plural('url', entrySelections.values.where((b) => b).length)
|
||||
])))
|
||||
],
|
||||
);
|
||||
|
Reference in New Issue
Block a user