diff --git a/assets/translations/de.json b/assets/translations/de.json index 88f9bb8..c88c168 100644 --- a/assets/translations/de.json +++ b/assets/translations/de.json @@ -217,6 +217,7 @@ "releaseDateAsVersionExplanation": "Diese Option sollte nur für Apps verwendet werden, bei denen die Versionserkennung nicht korrekt funktioniert, aber ein Veröffentlichungsdatum verfügbar ist.", "changes": "Änderungen", "releaseDate": "Veröffentlichungsdatum", + "importFromURLsInFile": "Import from URLs in File (like OPML)", "removeAppQuestion": { "one": "App entfernen?", "other": "App entfernen?" diff --git a/assets/translations/en.json b/assets/translations/en.json index c6c91bd..c271c8f 100644 --- a/assets/translations/en.json +++ b/assets/translations/en.json @@ -217,6 +217,7 @@ "releaseDateAsVersionExplanation": "This option should only be used for Apps where version detection does not work correctly, but a release date is available.", "changes": "Changes", "releaseDate": "Release Date", + "importFromURLsInFile": "Import from URLs in File (like OPML)", "removeAppQuestion": { "one": "Remove App?", "other": "Remove Apps?" diff --git a/assets/translations/fa.json b/assets/translations/fa.json index 999162b..452eedf 100644 --- a/assets/translations/fa.json +++ b/assets/translations/fa.json @@ -217,6 +217,7 @@ "releaseDateAsVersionExplanation": "This option should only be used for Apps where version detection does not work correctly, but a release date is available.", "changes": "Changes", "releaseDate": "Release Date", + "importFromURLsInFile": "Import from URLs in File (like OPML)", "removeAppQuestion": { "one": "برنامه حذف شود؟", "other": "برنامه ها حذف شوند؟" diff --git a/assets/translations/hu.json b/assets/translations/hu.json index c5745bf..95dacf0 100644 --- a/assets/translations/hu.json +++ b/assets/translations/hu.json @@ -216,6 +216,7 @@ "releaseDateAsVersionExplanation": "Ezt a beállítást csak olyan alkalmazásoknál szabad használni, ahol a verzió érzékelése nem működik megfelelően, de elérhető a kiadás dátuma.", "changes": "Változtatások", "releaseDate": "Kiadás dátuma", + "importFromURLsInFile": "Import from URLs in File (like OPML)", "removeAppQuestion": { "one": "Eltávolítja az alkalmazást?", "other": "Eltávolítja az alkalmazást?" diff --git a/assets/translations/it.json b/assets/translations/it.json index 7cc3307..ba3c839 100644 --- a/assets/translations/it.json +++ b/assets/translations/it.json @@ -217,6 +217,7 @@ "releaseDateAsVersionExplanation": "Questa opzione dovrebbe essere usata solo per le App in cui il rilevamento della versione non funziona correttamente, ma è disponibile una data di rilascio.", "changes": "Novità", "releaseDate": "Data di rilascio", + "importFromURLsInFile": "Import from URLs in File (like OPML)", "removeAppQuestion": { "one": "Rimuovere l'App?", "other": "Rimuovere le App?" diff --git a/assets/translations/ja.json b/assets/translations/ja.json index 3eb133d..807ea30 100644 --- a/assets/translations/ja.json +++ b/assets/translations/ja.json @@ -217,6 +217,7 @@ "releaseDateAsVersionExplanation": "このオプションは、バージョン検出が正しく機能しないアプリで、リリース日が利用可能な場合にのみ使用する必要があります。", "changes": "変更点", "releaseDate": "リリース日", + "importFromURLsInFile": "Import from URLs in File (like OPML)", "removeAppQuestion": { "one": "アプリを削除しますか?", "other": "アプリを削除しますか?" diff --git a/assets/translations/zh.json b/assets/translations/zh.json index 001a3ef..f16798d 100644 --- a/assets/translations/zh.json +++ b/assets/translations/zh.json @@ -217,6 +217,7 @@ "releaseDateAsVersionExplanation": "This option should only be used for Apps where version detection does not work correctly, but a release date is available.", "changes": "Changes", "releaseDate": "Release Date", + "importFromURLsInFile": "Import from URLs in File (like OPML)", "removeAppQuestion": { "one": "删除应用?", "other": "删除应用?" diff --git a/lib/pages/import_export.dart b/lib/pages/import_export.dart index df29739..71ec42c 100644 --- a/lib/pages/import_export.dart +++ b/lib/pages/import_export.dart @@ -41,6 +41,66 @@ class _ImportExportPageState extends State { ), ); + urlListImport({String? initValue, bool overrideInitValid = false}) { + showDialog?>( + context: context, + builder: (BuildContext ctx) { + return GeneratedFormModal( + initValid: overrideInitValid, + title: tr('importFromURLList'), + items: [ + [ + GeneratedFormTextField('appURLList', + defaultValue: initValue ?? '', + label: tr('appURLList'), + max: 7, + additionalValidators: [ + (dynamic value) { + if (value != null && value.isNotEmpty) { + var lines = value.trim().split('\n'); + for (int i = 0; i < lines.length; i++) { + try { + sourceProvider.getSource(lines[i]); + } catch (e) { + return '${tr('line')} ${i + 1}: $e'; + } + } + } + return null; + } + ]) + ] + ], + ); + }).then((values) { + if (values != null) { + var urls = (values['appURLList'] as String).split('\n'); + setState(() { + importInProgress = true; + }); + appsProvider.addAppsByURL(urls).then((errors) { + if (errors.isEmpty) { + showError(tr('importedX', args: [plural('apps', urls.length)]), + context); + } else { + showDialog( + context: context, + builder: (BuildContext ctx) { + return ImportErrorDialog( + urlsLength: urls.length, errors: errors); + }); + } + }).catchError((e) { + showError(e, context); + }).whenComplete(() { + setState(() { + importInProgress = false; + }); + }); + } + }); + } + return Scaffold( backgroundColor: Theme.of(context).colorScheme.surface, body: CustomScrollView(slivers: [ @@ -150,88 +210,60 @@ class _ImportExportPageState extends State { ], ) else - const Divider( - height: 32, - ), - TextButton( - onPressed: importInProgress - ? null - : () { - showDialog?>( - context: context, - builder: (BuildContext ctx) { - return GeneratedFormModal( - title: tr('importFromURLList'), - items: [ - [ - GeneratedFormTextField( - 'appURLList', - label: tr('appURLList'), - max: 7, - additionalValidators: [ - (dynamic value) { - if (value != null && - value.isNotEmpty) { - var lines = value - .trim() - .split('\n'); - for (int i = 0; - i < lines.length; - i++) { - try { - sourceProvider - .getSource( - lines[i]); - } catch (e) { - return '${tr('line')} ${i + 1}: $e'; - } - } - } - return null; - } - ]) - ] - ], - ); - }).then((values) { - if (values != null) { - var urls = - (values['appURLList'] as String) - .split('\n'); - setState(() { - importInProgress = true; - }); - appsProvider - .addAppsByURL(urls) - .then((errors) { - if (errors.isEmpty) { - showError( - tr('importedX', args: [ - plural('apps', urls.length) - ]), - context); - } else { - showDialog( - context: context, - builder: (BuildContext ctx) { - return ImportErrorDialog( - urlsLength: urls.length, - errors: errors); - }); - } - }).catchError((e) { - showError(e, context); - }).whenComplete(() { - setState(() { - importInProgress = false; + Column( + children: [ + const Divider( + height: 32, + ), + TextButton( + onPressed: importInProgress + ? null + : () { + urlListImport(); + }, + child: Text( + tr('importFromURLList'), + )), + const SizedBox(height: 8), + TextButton( + onPressed: importInProgress + ? null + : () { + FilePicker.platform + .pickFiles() + .then((result) { + if (result != null) { + urlListImport( + overrideInitValid: true, + initValue: + RegExp('https?://[^"]+') + .allMatches(File(result + .files + .single + .path!) + .readAsStringSync()) + .map((e) => + e.input.substring( + e.start, e.end)) + .toSet() + .toList() + .where((url) { + try { + sourceProvider + .getSource(url); + return true; + } catch (e) { + return false; + } + }).join('\n')); + } }); - }); - } - }); - }, - child: Text( - tr('importFromURLList'), - )), + }, + child: Text( + tr('importFromURLsInFile'), + )), + ], + ), ...sourceProvider.sources .where((element) => element.canSearch) .map((source) => Column( @@ -280,6 +312,7 @@ class _ImportExportPageState extends State { if (urlsWithDescriptions .isNotEmpty) { var selectedUrls = + // ignore: use_build_context_synchronously await showDialog< List< String>?>( @@ -314,6 +347,7 @@ class _ImportExportPageState extends State { ]), context); } else { + // ignore: use_build_context_synchronously showDialog( context: context, builder: @@ -391,6 +425,7 @@ class _ImportExportPageState extends State { e.toString()) .toList()); var selectedUrls = + // ignore: use_build_context_synchronously await showDialog< List?>( context: context, @@ -418,6 +453,7 @@ class _ImportExportPageState extends State { ]), context); } else { + // ignore: use_build_context_synchronously showDialog( context: context, builder: