More search options (#1107)

This commit is contained in:
Imran Remtulla
2023-11-24 17:59:59 -05:00
parent 3890c4ffb9
commit bcb4567382
20 changed files with 221 additions and 156 deletions

View File

@@ -223,7 +223,7 @@
"moveNonInstalledAppsToBottom": "Premjesti neinstalirane aplikacije na dno prikaza aplikacija", "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)", "gitlabPATLabel": "GitLab token za lični pristup\n(Omogućava pretraživanje i bolje otkrivanje APK-a)",
"about": "O nama", "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", "checkOnStart": "Provjerite ima li novosti pri pokretanju",
"tryInferAppIdFromCode": "Pokušati otkriti ID aplikacije iz izvornog koda", "tryInferAppIdFromCode": "Pokušati otkriti ID aplikacije iz izvornog koda",
"removeOnExternalUninstall": "Automatski ukloni eksterno deinstalirane aplikacije", "removeOnExternalUninstall": "Automatski ukloni eksterno deinstalirane aplikacije",

View File

@@ -223,7 +223,7 @@
"moveNonInstalledAppsToBottom": "Přesunout nenainstalované aplikace na konec zobrazení Aplikace", "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)", "gitlabPATLabel": "GitLab Personal Access Token\n(Umožňuje vyhledávání a lepší zjišťování APK)",
"about": "About", "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í", "checkOnStart": "Zkontrolovat jednou při spuštění",
"tryInferAppIdFromCode": "Pokusit se určit ID aplikace ze zdrojového kódu", "tryInferAppIdFromCode": "Pokusit se určit ID aplikace ze zdrojového kódu",
"removeOnExternalUninstall": "Automaticky odstranit externě odinstalované aplikace", "removeOnExternalUninstall": "Automaticky odstranit externě odinstalované aplikace",

View File

@@ -223,7 +223,7 @@
"moveNonInstalledAppsToBottom": "Nicht installierte Apps ans Ende der Apps Ansicht verschieben", "moveNonInstalledAppsToBottom": "Nicht installierte Apps ans Ende der Apps Ansicht verschieben",
"gitlabPATLabel": "GitLab Personal Access Token\n(Aktiviert Suche und bessere APK Entdeckung)", "gitlabPATLabel": "GitLab Personal Access Token\n(Aktiviert Suche und bessere APK Entdeckung)",
"about": "Über", "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", "checkOnStart": "Überprüfe einmalig beim Start",
"tryInferAppIdFromCode": "Versuche, die App-ID aus dem Quellcode zu ermitteln", "tryInferAppIdFromCode": "Versuche, die App-ID aus dem Quellcode zu ermitteln",
"removeOnExternalUninstall": "Automatisches Entfernen von extern deinstallierten Apps", "removeOnExternalUninstall": "Automatisches Entfernen von extern deinstallierten Apps",

View File

@@ -223,7 +223,7 @@
"moveNonInstalledAppsToBottom": "Move non-installed Apps to bottom of Apps view", "moveNonInstalledAppsToBottom": "Move non-installed Apps to bottom of Apps view",
"gitlabPATLabel": "GitLab Personal Access Token\n(Enables Search and Better APK Discovery)", "gitlabPATLabel": "GitLab Personal Access Token\n(Enables Search and Better APK Discovery)",
"about": "About", "about": "About",
"requiresCredentialsInSettings": "This needs additional credentials (in Settings)", "requiresCredentialsInSettings": "{} needs additional credentials (in Settings)",
"checkOnStart": "Check for updates on startup", "checkOnStart": "Check for updates on startup",
"tryInferAppIdFromCode": "Try inferring App ID from source code", "tryInferAppIdFromCode": "Try inferring App ID from source code",
"removeOnExternalUninstall": "Automatically remove externally uninstalled Apps", "removeOnExternalUninstall": "Automatically remove externally uninstalled Apps",

View File

@@ -223,7 +223,7 @@
"moveNonInstalledAppsToBottom": "Move non-installed Apps to bottom of Apps view", "moveNonInstalledAppsToBottom": "Move non-installed Apps to bottom of Apps view",
"gitlabPATLabel": "GitLab Personal Access Token\n(Enables Search and Better APK Discovery)", "gitlabPATLabel": "GitLab Personal Access Token\n(Enables Search and Better APK Discovery)",
"about": "About", "about": "About",
"requiresCredentialsInSettings": "This needs additional credentials (in Settings)", "requiresCredentialsInSettings": "{}: This needs additional credentials (in Settings)",
"checkOnStart": "Check for updates on startup", "checkOnStart": "Check for updates on startup",
"tryInferAppIdFromCode": "Try inferring App ID from source code", "tryInferAppIdFromCode": "Try inferring App ID from source code",
"removeOnExternalUninstall": "Automatically remove externally uninstalled Apps", "removeOnExternalUninstall": "Automatically remove externally uninstalled Apps",

View File

@@ -223,7 +223,7 @@
"moveNonInstalledAppsToBottom": "برنامه های نصب نشده را به نمای پایین برنامه ها منتقل کنید", "moveNonInstalledAppsToBottom": "برنامه های نصب نشده را به نمای پایین برنامه ها منتقل کنید",
"gitlabPATLabel": "رمز دسترسی شخصی GitLab\n(جستجو و کشف بهتر APK را فعال میکند)", "gitlabPATLabel": "رمز دسترسی شخصی GitLab\n(جستجو و کشف بهتر APK را فعال میکند)",
"about": "درباره", "about": "درباره",
"requiresCredentialsInSettings": "این به اعتبارنامه های اضافی نیاز دارد (در تنظیمات)", "requiresCredentialsInSettings": "{}: این به اعتبارنامه های اضافی نیاز دارد (در تنظیمات)",
"checkOnStart": "بررسی در شروع", "checkOnStart": "بررسی در شروع",
"tryInferAppIdFromCode": "شناسه برنامه را از کد منبع استنباط کنید", "tryInferAppIdFromCode": "شناسه برنامه را از کد منبع استنباط کنید",
"removeOnExternalUninstall": "حذف خودکار برنامه های حذف نصب شده خارجی", "removeOnExternalUninstall": "حذف خودکار برنامه های حذف نصب شده خارجی",

View File

@@ -223,7 +223,7 @@
"moveNonInstalledAppsToBottom": "Move non-installed Apps to bottom of Apps view", "moveNonInstalledAppsToBottom": "Move non-installed Apps to bottom of Apps view",
"gitlabPATLabel": "GitLab Personal Access Token\n(Enables Search and Better APK Discovery)", "gitlabPATLabel": "GitLab Personal Access Token\n(Enables Search and Better APK Discovery)",
"about": "About", "about": "About",
"requiresCredentialsInSettings": "This needs additional credentials (in Settings)", "requiresCredentialsInSettings": "{}: This needs additional credentials (in Settings)",
"checkOnStart": "Check for updates on startup", "checkOnStart": "Check for updates on startup",
"tryInferAppIdFromCode": "Try inferring App ID from source code", "tryInferAppIdFromCode": "Try inferring App ID from source code",
"removeOnExternalUninstall": "Automatically remove externally uninstalled Apps", "removeOnExternalUninstall": "Automatically remove externally uninstalled Apps",

View File

@@ -223,7 +223,7 @@
"moveNonInstalledAppsToBottom": "Helyezze át a nem telepített appokat az App nézet aljára", "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)", "gitlabPATLabel": "GitLab Personal Access Token\n(Engedélyezi a Keresést és jobb APK felfedezés)",
"about": "Rólunk", "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", "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", "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", "removeOnExternalUninstall": "A külsőleg eltávolított appok auto. eltávolítása",

View File

@@ -223,7 +223,7 @@
"moveNonInstalledAppsToBottom": "Sposta le app non installate in fondo alla lista", "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)", "gitlabPATLabel": "GitLab Personal Access Token\n(attiva la ricerca e migliora la rilevazione di apk)",
"about": "Informazioni", "about": "Informazioni",
"requiresCredentialsInSettings": "Servono credenziali aggiuntive (in Impostazioni)", "requiresCredentialsInSettings": "{}: Servono credenziali aggiuntive (in Impostazioni)",
"checkOnStart": "Controlla una volta all'avvio", "checkOnStart": "Controlla una volta all'avvio",
"tryInferAppIdFromCode": "Prova a dedurre l'ID dell'app dal codice sorgente", "tryInferAppIdFromCode": "Prova a dedurre l'ID dell'app dal codice sorgente",
"removeOnExternalUninstall": "Rimuovi automaticamente app disinstallate esternamente", "removeOnExternalUninstall": "Rimuovi automaticamente app disinstallate esternamente",

View File

@@ -223,7 +223,7 @@
"moveNonInstalledAppsToBottom": "未インストールのアプリをアプリ一覧の下部に移動させる", "moveNonInstalledAppsToBottom": "未インストールのアプリをアプリ一覧の下部に移動させる",
"gitlabPATLabel": "GitLab パーソナルアクセストークン\n(検索とより良いAPK検出の有効化)", "gitlabPATLabel": "GitLab パーソナルアクセストークン\n(検索とより良いAPK検出の有効化)",
"about": "概要", "about": "概要",
"requiresCredentialsInSettings": "これには追加の認証が必要です (設定にて)", "requiresCredentialsInSettings": "{}: これには追加の認証が必要です (設定にて)",
"checkOnStart": "起動時にアップデートを確認する", "checkOnStart": "起動時にアップデートを確認する",
"tryInferAppIdFromCode": "ソースコードからApp IDを推測する", "tryInferAppIdFromCode": "ソースコードからApp IDを推測する",
"removeOnExternalUninstall": "外部でアンインストールされたアプリを自動的に削除する", "removeOnExternalUninstall": "外部でアンインストールされたアプリを自動的に削除する",

View File

@@ -223,7 +223,7 @@
"moveNonInstalledAppsToBottom": "Verplaats niet-geïnstalleerde apps naar de onderkant van de apps-weergave", "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)", "gitlabPATLabel": "GitLab Personal Access Token\n(Maakt het mogelijk beter te zoeken naar APK's)",
"about": "Over", "about": "Over",
"requiresCredentialsInSettings": "Dit vereist aanvullende referenties (in Instellingen)", "requiresCredentialsInSettings": "{}: Dit vereist aanvullende referenties (in Instellingen)",
"checkOnStart": "Controleren op updates bij opstarten", "checkOnStart": "Controleren op updates bij opstarten",
"tryInferAppIdFromCode": "Probeer de app-ID af te leiden uit de broncode", "tryInferAppIdFromCode": "Probeer de app-ID af te leiden uit de broncode",
"removeOnExternalUninstall": "Automatisch extern verwijderde apps verwijderen", "removeOnExternalUninstall": "Automatisch extern verwijderde apps verwijderen",

View File

@@ -223,7 +223,7 @@
"moveNonInstalledAppsToBottom": "Przenieś niezainstalowane aplikacje na dół widoku aplikacji", "moveNonInstalledAppsToBottom": "Przenieś niezainstalowane aplikacje na dół widoku aplikacji",
"gitlabPATLabel": "Osobisty token dostępu GitLab\n(Umożliwia wyszukiwanie i lepsze wykrywanie APK)", "gitlabPATLabel": "Osobisty token dostępu GitLab\n(Umożliwia wyszukiwanie i lepsze wykrywanie APK)",
"about": "Więcej informacji", "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", "checkOnStart": "Sprawdź aktualizacje przy uruchomieniu",
"tryInferAppIdFromCode": "Spróbuj wywnioskować identyfikator aplikacji z kodu źródłowego", "tryInferAppIdFromCode": "Spróbuj wywnioskować identyfikator aplikacji z kodu źródłowego",
"removeOnExternalUninstall": "Automatyczne usuń odinstalowane zewnętrznie aplikacje", "removeOnExternalUninstall": "Automatyczne usuń odinstalowane zewnętrznie aplikacje",

View File

@@ -223,7 +223,7 @@
"moveNonInstalledAppsToBottom": "Mover Apps não instalados para o fundo da visão de Apps", "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)", "gitlabPATLabel": "Token de Acceso Pessoal do Gitlab\n(Ativa Pesquisa e Melhor Descoberta de APKs)",
"about": "Sobre", "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 ", "checkOnStart": "Checar por atualizações ao iniciar ",
"tryInferAppIdFromCode": "Tente inferir o ID do App pelo código fonte", "tryInferAppIdFromCode": "Tente inferir o ID do App pelo código fonte",
"removeOnExternalUninstall": "Remover automaticamente Apps desinstalados externamente", "removeOnExternalUninstall": "Remover automaticamente Apps desinstalados externamente",

View File

@@ -223,7 +223,7 @@
"moveNonInstalledAppsToBottom": "Отображать неустановленные приложения внизу списка", "moveNonInstalledAppsToBottom": "Отображать неустановленные приложения внизу списка",
"gitlabPATLabel": "Персональный токен доступа GitLab\n(включает поиск и улучшает обнаружение APK)", "gitlabPATLabel": "Персональный токен доступа GitLab\n(включает поиск и улучшает обнаружение APK)",
"about": "Описание", "about": "Описание",
"requiresCredentialsInSettings": "Для этого требуются дополнительные учетные данные (в настройках)", "requiresCredentialsInSettings": "{}: Для этого требуются дополнительные учетные данные (в настройках)",
"checkOnStart": "Проверять наличие обновлений при запуске", "checkOnStart": "Проверять наличие обновлений при запуске",
"tryInferAppIdFromCode": "Попытаться определить ID приложения из исходного кода", "tryInferAppIdFromCode": "Попытаться определить ID приложения из исходного кода",
"removeOnExternalUninstall": "Автоматически убирать из списка удаленные извне приложения", "removeOnExternalUninstall": "Автоматически убирать из списка удаленные извне приложения",

View File

@@ -223,7 +223,7 @@
"moveNonInstalledAppsToBottom": "Move non-installed Apps to bottom of Apps view", "moveNonInstalledAppsToBottom": "Move non-installed Apps to bottom of Apps view",
"gitlabPATLabel": "GitLab Personal Access Token\n(Enables Search and Better APK Discovery)", "gitlabPATLabel": "GitLab Personal Access Token\n(Enables Search and Better APK Discovery)",
"about": "Om", "about": "Om",
"requiresCredentialsInSettings": "This needs additional credentials (in Settings)", "requiresCredentialsInSettings": "{}: This needs additional credentials (in Settings)",
"checkOnStart": "Kolla efter uppdateringar vid start", "checkOnStart": "Kolla efter uppdateringar vid start",
"tryInferAppIdFromCode": "Try inferring App ID from source code", "tryInferAppIdFromCode": "Try inferring App ID from source code",
"removeOnExternalUninstall": "Automatically remove externally uninstalled Apps", "removeOnExternalUninstall": "Automatically remove externally uninstalled Apps",

View File

@@ -223,7 +223,7 @@
"moveNonInstalledAppsToBottom": "Yüklenmemiş Uygulamaları Uygulamalar Görünümünün Altına Taşı", "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)", "gitlabPATLabel": "GitLab Kişisel Erişim Belirteci\n(Arama ve Daha İyi APK Keşfi İçin)",
"about": "Hakkında", "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", "checkOnStart": "Başlangıçta güncellemeleri kontrol et",
"tryInferAppIdFromCode": "Uygulama kimliğini kaynak kodundan çıkarma girişimi", "tryInferAppIdFromCode": "Uygulama kimliğini kaynak kodundan çıkarma girişimi",
"removeOnExternalUninstall": "Harici kaldırmada otomatik olarak kaldırılan uygulamalar", "removeOnExternalUninstall": "Harici kaldırmada otomatik olarak kaldırılan uygulamalar",

View File

@@ -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", "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)", "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", "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", "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", "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", "removeOnExternalUninstall": "Tự động xóa ứng dụng đã gỡ cài đặt bên ngoài",

View File

@@ -223,7 +223,7 @@
"moveNonInstalledAppsToBottom": "将未安装应用置底", "moveNonInstalledAppsToBottom": "将未安装应用置底",
"gitlabPATLabel": "GitLab 个人访问令牌(启用搜索功能并增强 APK 发现)", "gitlabPATLabel": "GitLab 个人访问令牌(启用搜索功能并增强 APK 发现)",
"about": "相关文档", "about": "相关文档",
"requiresCredentialsInSettings": "此功能需要额外的凭据(在“设置”中添加)", "requiresCredentialsInSettings": "{}: 此功能需要额外的凭据(在“设置”中添加)",
"checkOnStart": "启动时进行一次检查", "checkOnStart": "启动时进行一次检查",
"tryInferAppIdFromCode": "尝试从源代码推断应用 ID", "tryInferAppIdFromCode": "尝试从源代码推断应用 ID",
"removeOnExternalUninstall": "自动删除已卸载的外部应用", "removeOnExternalUninstall": "自动删除已卸载的外部应用",

View File

@@ -254,13 +254,30 @@ class _AddAppPageState extends State<AddAppPage> {
], ],
); );
runSearch() async { runSearch({bool filtered = true}) async {
setState(() { setState(() {
searching = true; searching = true;
}); });
try { var sourceStrings = <String, List<String>>{};
var results = await Future.wait(sourceProvider.sources sourceProvider.sources
.where((e) => e.canSearch && !e.excludeFromMassSearch) .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) => searchSources.contains(e.name))
.map((e) async { .map((e) async {
try { try {
return await e.search(searchQuery); return await e.search(searchQuery);
@@ -268,6 +285,8 @@ class _AddAppPageState extends State<AddAppPage> {
if (err is! CredsNeededError) { if (err is! CredsNeededError) {
rethrow; rethrow;
} else { } else {
err.unexpected = true;
showError(err, context);
return <String, List<String>>{}; return <String, List<String>>{};
} }
} }
@@ -297,8 +316,8 @@ class _AddAppPageState extends State<AddAppPage> {
: await showDialog<List<String>?>( : await showDialog<List<String>?>(
context: context, context: context,
builder: (BuildContext ctx) { builder: (BuildContext ctx) {
return UrlSelectionModal( return SelectionModal(
urlsWithDescriptions: res, entries: res,
selectedByDefault: false, selectedByDefault: false,
onlyOneSelectionAllowed: true, onlyOneSelectionAllowed: true,
); );
@@ -470,8 +489,7 @@ class _AddAppPageState extends State<AddAppPage> {
const SizedBox( const SizedBox(
height: 16, height: 16,
), ),
...sourceProvider.sources ...sourceProvider.sources.map((e) => GestureDetector(
.map((e) => GestureDetector(
onTap: e.host != null onTap: e.host != null
? () { ? () {
launchUrlString('https://${e.host}', launchUrlString('https://${e.host}',
@@ -486,7 +504,6 @@ class _AddAppPageState extends State<AddAppPage> {
: TextDecoration.none, : TextDecoration.none,
fontStyle: FontStyle.italic), fontStyle: FontStyle.italic),
))) )))
]); ]);
return Scaffold( return Scaffold(

View File

@@ -208,8 +208,8 @@ class _ImportExportPageState extends State<ImportExportPage> {
await showDialog<List<String>?>( await showDialog<List<String>?>(
context: context, context: context,
builder: (BuildContext ctx) { builder: (BuildContext ctx) {
return UrlSelectionModal( return SelectionModal(
urlsWithDescriptions: urlsWithDescriptions, entries: urlsWithDescriptions,
selectedByDefault: false, selectedByDefault: false,
); );
}); });
@@ -269,8 +269,7 @@ class _ImportExportPageState extends State<ImportExportPage> {
await showDialog<List<String>?>( await showDialog<List<String>?>(
context: context, context: context,
builder: (BuildContext ctx) { builder: (BuildContext ctx) {
return UrlSelectionModal( return SelectionModal(entries: urlsWithDescriptions);
urlsWithDescriptions: urlsWithDescriptions);
}); });
if (selectedUrls != null) { if (selectedUrls != null) {
var errors = await appsProvider.addAppsByURL(selectedUrls); 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( return Scaffold(
backgroundColor: Theme.of(context).colorScheme.surface, backgroundColor: Theme.of(context).colorScheme.surface,
body: CustomScrollView(slivers: <Widget>[ body: CustomScrollView(slivers: <Widget>[
@@ -409,6 +413,49 @@ class _ImportExportPageState extends State<ImportExportPage> {
const Divider( const Divider(
height: 32, 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( TextButton(
onPressed: onPressed:
importInProgress ? null : urlListImport, importInProgress ? null : urlListImport,
@@ -424,27 +471,8 @@ class _ImportExportPageState extends State<ImportExportPage> {
)), )),
], ],
), ),
...sourceProvider.sources ...sourceProvider.massUrlSources.map((source) => Column(
.where((element) => element.canSearch) crossAxisAlignment: CrossAxisAlignment.stretch,
.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: [ children: [
const SizedBox(height: 8), const SizedBox(height: 8),
TextButton( TextButton(
@@ -455,8 +483,7 @@ class _ImportExportPageState extends State<ImportExportPage> {
}, },
child: Text( child: Text(
tr('importX', args: [source.name]))) tr('importX', args: [source.name])))
])) ])),
,
const Spacer(), const Spacer(),
const Divider( const Divider(
height: 32, height: 32,
@@ -532,38 +559,40 @@ class _ImportErrorDialogState extends State<ImportErrorDialog> {
} }
// ignore: must_be_immutable // ignore: must_be_immutable
class UrlSelectionModal extends StatefulWidget { class SelectionModal extends StatefulWidget {
UrlSelectionModal( SelectionModal(
{super.key, {super.key,
required this.urlsWithDescriptions, required this.entries,
this.selectedByDefault = true, 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 selectedByDefault;
bool onlyOneSelectionAllowed; bool onlyOneSelectionAllowed;
bool titlesAreLinks;
@override @override
State<UrlSelectionModal> createState() => _UrlSelectionModalState(); State<SelectionModal> createState() => _SelectionModalState();
} }
class _UrlSelectionModalState extends State<UrlSelectionModal> { class _SelectionModalState extends State<SelectionModal> {
Map<MapEntry<String, List<String>>, bool> urlWithDescriptionSelections = {}; Map<MapEntry<String, List<String>>, bool> entrySelections = {};
@override @override
void initState() { void initState() {
super.initState(); super.initState();
for (var url in widget.urlsWithDescriptions.entries) { for (var url in widget.entries.entries) {
urlWithDescriptionSelections.putIfAbsent(url, entrySelections.putIfAbsent(url,
() => widget.selectedByDefault && !widget.onlyOneSelectionAllowed); () => widget.selectedByDefault && !widget.onlyOneSelectionAllowed);
} }
if (widget.selectedByDefault && widget.onlyOneSelectionAllowed) { if (widget.selectedByDefault && widget.onlyOneSelectionAllowed) {
selectOnlyOne(widget.urlsWithDescriptions.entries.first.key); selectOnlyOne(widget.entries.entries.first.key);
} }
} }
selectOnlyOne(String url) { selectOnlyOne(String url) {
for (var uwd in urlWithDescriptionSelections.keys) { for (var e in entrySelections.keys) {
urlWithDescriptionSelections[uwd] = uwd.key == url; entrySelections[e] = e.key == url;
} }
} }
@@ -571,73 +600,88 @@ class _UrlSelectionModalState extends State<UrlSelectionModal> {
Widget build(BuildContext context) { Widget build(BuildContext context) {
return AlertDialog( return AlertDialog(
scrollable: true, scrollable: true,
title: Text( title: Text(tr('select')),
widget.onlyOneSelectionAllowed ? tr('selectURL') : tr('selectURLs')),
content: Column(children: [ content: Column(children: [
...urlWithDescriptionSelections.keys.map((urlWithD) { ...entrySelections.keys.map((entry) {
selectThis(bool? value) { selectThis(bool? value) {
setState(() { setState(() {
value ??= false; value ??= false;
if (value! && widget.onlyOneSelectionAllowed) { if (value! && widget.onlyOneSelectionAllowed) {
selectOnlyOne(urlWithD.key); selectOnlyOne(entry.key);
} else { } else {
urlWithDescriptionSelections[urlWithD] = value!; entrySelections[entry] = value!;
} }
}); });
} }
var urlLink = GestureDetector( var urlLink = GestureDetector(
onTap: () { onTap: !widget.titlesAreLinks
launchUrlString(urlWithD.key, ? null
: () {
launchUrlString(entry.key,
mode: LaunchMode.externalApplication); mode: LaunchMode.externalApplication);
}, },
child: Column( child: Column(
crossAxisAlignment: CrossAxisAlignment.start, crossAxisAlignment: CrossAxisAlignment.start,
children: [ children: [
Text( Text(
urlWithD.value[0], entry.value.isEmpty ? entry.key : entry.value[0],
style: const TextStyle( style: TextStyle(
decoration: TextDecoration.underline, decoration: widget.titlesAreLinks
? TextDecoration.underline
: null,
fontWeight: FontWeight.bold), fontWeight: FontWeight.bold),
textAlign: TextAlign.start, textAlign: TextAlign.start,
), ),
if (widget.titlesAreLinks)
Text( Text(
Uri.parse(urlWithD.key).host, Uri.parse(entry.key).host,
style: const TextStyle( style: const TextStyle(
decoration: TextDecoration.underline, fontSize: 12), decoration: TextDecoration.underline, fontSize: 12),
) )
], ],
)); ));
var descriptionText = Text( var descriptionText = entry.value.length <= 1
urlWithD.value[1].length > 128 ? const SizedBox.shrink()
? '${urlWithD.value[1].substring(0, 128)}...' : Text(
: urlWithD.value[1], entry.value[1].length > 128
style: const TextStyle(fontStyle: FontStyle.italic, fontSize: 12), ? '${entry.value[1].substring(0, 128)}...'
: entry.value[1],
style: const TextStyle(
fontStyle: FontStyle.italic, fontSize: 12),
); );
var selectedUrlsWithDs = urlWithDescriptionSelections.entries var selectedEntries =
.where((e) => e.value) entrySelections.entries.where((e) => e.value).toList();
.toList();
var singleSelectTile = ListTile( var singleSelectTile = ListTile(
title: urlLink, title: GestureDetector(
subtitle: GestureDetector( onTap: widget.titlesAreLinks
? null
: () {
selectThis(!(entrySelections[entry] ?? false));
},
child: urlLink,
),
subtitle: entry.value.length <= 1
? null
: GestureDetector(
onTap: () { onTap: () {
setState(() { setState(() {
selectOnlyOne(urlWithD.key); selectOnlyOne(entry.key);
}); });
}, },
child: descriptionText, child: descriptionText,
), ),
leading: Radio<String>( leading: Radio<String>(
value: urlWithD.key, value: entry.key,
groupValue: selectedUrlsWithDs.isEmpty groupValue: selectedEntries.isEmpty
? null ? null
: selectedUrlsWithDs.first.key.key, : selectedEntries.first.key.key,
onChanged: (value) { onChanged: (value) {
setState(() { setState(() {
selectOnlyOne(urlWithD.key); selectOnlyOne(entry.key);
}); });
}, },
), ),
@@ -645,7 +689,7 @@ class _UrlSelectionModalState extends State<UrlSelectionModal> {
var multiSelectTile = Row(children: [ var multiSelectTile = Row(children: [
Checkbox( Checkbox(
value: urlWithDescriptionSelections[urlWithD], value: entrySelections[entry],
onChanged: (value) { onChanged: (value) {
selectThis(value); selectThis(value);
}), }),
@@ -657,18 +701,28 @@ class _UrlSelectionModalState extends State<UrlSelectionModal> {
crossAxisAlignment: CrossAxisAlignment.start, crossAxisAlignment: CrossAxisAlignment.start,
mainAxisAlignment: MainAxisAlignment.center, mainAxisAlignment: MainAxisAlignment.center,
children: [ children: [
const SizedBox( SizedBox(
height: 8, height: entry.value.length <= 1 ? 16 : 8,
), ),
urlLink,
GestureDetector( GestureDetector(
onTap: widget.titlesAreLinks
? null
: () {
selectThis(!(entrySelections[entry] ?? false));
},
child: urlLink,
),
entry.value.length <= 1
? const SizedBox.shrink()
: GestureDetector(
onTap: () { onTap: () {
selectThis( selectThis(!(entrySelections[entry] ?? false));
!(urlWithDescriptionSelections[urlWithD] ?? false));
}, },
child: descriptionText, child: descriptionText,
), ),
const SizedBox( entry.value.length <= 1
? const SizedBox.shrink()
: const SizedBox(
height: 8, height: 8,
) )
], ],
@@ -687,12 +741,10 @@ class _UrlSelectionModalState extends State<UrlSelectionModal> {
}, },
child: Text(tr('cancel'))), child: Text(tr('cancel'))),
TextButton( TextButton(
onPressed: onPressed: entrySelections.values.where((b) => b).isEmpty
urlWithDescriptionSelections.values.where((b) => b).isEmpty
? null ? null
: () { : () {
Navigator.of(context).pop(urlWithDescriptionSelections Navigator.of(context).pop(entrySelections.entries
.entries
.where((entry) => entry.value) .where((entry) => entry.value)
.map((e) => e.key.key) .map((e) => e.key.key)
.toList()); .toList());
@@ -700,11 +752,7 @@ class _UrlSelectionModalState extends State<UrlSelectionModal> {
child: Text(widget.onlyOneSelectionAllowed child: Text(widget.onlyOneSelectionAllowed
? tr('pick') ? tr('pick')
: tr('importX', args: [ : tr('importX', args: [
plural( plural('url', entrySelections.values.where((b) => b).length)
'url',
urlWithDescriptionSelections.values
.where((b) => b)
.length)
]))) ])))
], ],
); );