Compare commits

...

9 Commits

Author SHA1 Message Date
2c6e95f902 Updates 2025-07-02 16:00:29 -04:00
7dbf3ac102 Try using foreground tasks for reliability 2025-07-01 15:05:26 -04:00
6055ae6a69 Merge pull request #2360 from solokot/main
Update Russian translation
2025-06-21 12:44:52 -04:00
2223616f2a Update Russian translation
Unification of terminology, correction of machine translation errors
2025-06-18 13:52:03 +03:00
39b76a41cf Merge pull request #2357 from ImranR98/dev
Fix versionCode issue for universal builds (#2352)
2025-06-15 14:44:24 -04:00
377ebeeca2 Increment version 2025-06-15 14:44:14 -04:00
c88f55af5a Fix versionCode issue for universal builds (#2352) 2025-06-15 14:42:58 -04:00
2dca74edb2 Update README.md 2025-06-13 17:53:09 -04:00
e35fd1e01e Merge pull request #2350 from ImranR98/dev
- Minor wording changes (#2226, #2313, #2317, #2338)
- HTML bugfix: Incorrect URL resolution on redirected pages (#2315)
2025-06-13 17:23:40 -04:00
14 changed files with 300 additions and 198 deletions

View File

@ -1,6 +1,6 @@
# ![Obtainium Icon](./assets/graphics/icon_small.png) Obtainium
<div align="center"><a href="https://github.com/Safouene1/support-palestine-banner/blob/master/Markdown-pages/Support.md"><img src="https://raw.githubusercontent.com/Safouene1/support-palestine-banner/master/banner-support.svg" alt="Support Palestine" style="width: 100%;"></a></div>
[![Ceasefire Now](https://badge.techforpalestine.org/default)](https://techforpalestine.org/learn-more)
# ![Obtainium Icon](./assets/graphics/icon_small.png) Obtainium
Get Android app updates straight from the source.

View File

@ -90,7 +90,7 @@ androidComponents {
onVariants { variant ->
variant.outputs.forEach { output ->
val name = output.filters.find { it.filterType == ABI }?.identifier
val baseAbiCode = abiCodes[name]
val baseAbiCode = abiCodes[name] ?: 0
if (baseAbiCode != null) {
output.versionCode.set(baseAbiCode + ((output.versionCode.get() ?: 0) * 10))
}

View File

@ -61,6 +61,13 @@
android:enabled="true"
android:exported="true"
android:permission="android.permission.INTERACT_ACROSS_USERS_FULL" />
<service
android:name="com.pravera.flutter_foreground_task.service.ForegroundService"
android:foregroundServiceType="specialUse"
android:exported="false" />
<meta-data
android:name="dev.imranr.obtainium.service.NOTIFICATION_ICON"
android:resource="@drawable/ic_notification" />
</application>
<uses-permission android:name="android.permission.INTERNET" />
<uses-permission android:name="android.permission.REQUEST_INSTALL_PACKAGES" />
@ -74,6 +81,8 @@
android:name="android.permission.WRITE_EXTERNAL_STORAGE"
android:maxSdkVersion="29" />
<uses-permission android:name="android.permission.QUERY_ALL_PACKAGES" />
<uses-permission android:name="android.permission.FOREGROUND_SERVICE" />
<uses-permission android:name="android.permission.FOREGROUND_SERVICE_SPECIAL_USE" />
<!-- Required to query activities that can process text, see:
https://developer.android.com/training/package-visibility and
https://developer.android.com/reference/android/content/Intent#ACTION_PROCESS_TEXT.

Binary file not shown.

After

Width:  |  Height:  |  Size: 965 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 843 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.5 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 2.7 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 4.0 KiB

View File

@ -330,6 +330,8 @@
"documentationLinksNote": "The Obtainium GitHub page linked below contains links to videos, articles, discussions and other resources that will help you understand how to use the app.",
"batteryOptimizationNote": "Note that background downloads may work more reliably if you disable OS battery optimizations for Obtainium.",
"fileDeletionError": "Failed to delete file (try deleting it manually then try again): \"{}\"",
"foregroundService": "Obtainium foreground service",
"fgServiceNotice": "This notification is required for background update checking (it can be hidden in the OS settings)",
"removeAppQuestion": {
"one": "Remove app?",
"other": "Remove apps?"

View File

@ -1,7 +1,7 @@
{
"invalidURLForSource": "Неверный URL-адрес приложения: {}",
"noReleaseFound": "Не удалось найти подходящий релиз",
"noVersionFound": "Не удалось определить версию релиза",
"noReleaseFound": "Не удалось найти подходящий выпуск",
"noVersionFound": "Не удалось определить версию выпуска",
"urlMatchesNoSource": "URL-адрес не соответствует известному источнику",
"cantInstallOlderVersion": "Невозможно установить более старую версию приложения",
"appIdMismatch": "ID загруженного пакета не совпадает с существующим ID приложения",
@ -9,12 +9,12 @@
"placeholder": "Заполнитель",
"someErrors": "Возникли некоторые ошибки",
"unexpectedError": "Неожиданная ошибка",
"ok": "Ок",
"ok": "OK",
"and": "и",
"githubPATLabel": "Персональный токен доступа GitHub\n(увеличивает лимит запросов)",
"includePrereleases": "Включить предварительные релизы",
"includePrereleases": "Включать предварительные выпуски",
"fallbackToOlderReleases": "Откатываться к предыдущей версии",
"filterReleaseTitlesByRegEx": "Фильтровать заголовки релизов\n(регулярное выражение)",
"filterReleaseTitlesByRegEx": "Фильтровать заголовки выпусков\n(регулярное выражение)",
"invalidRegEx": "Неверное регулярное выражение",
"noDescription": "Нет описания",
"cancel": "Отмена",
@ -32,13 +32,13 @@
"source": "Источник",
"app": "Приложение",
"appsFromSourceAreTrackOnly": "Приложения из этого источника настроены только для отслеживания",
"youPickedTrackOnly": "Вы выбрали опцию 'Только для отслеживания'",
"youPickedTrackOnly": "Выбрана настройка «только для отслеживания»",
"trackOnlyAppDescription": "Приложение будет отслеживаться на предмет обновлений, но Obtainium не сможет загрузить или установить его",
"cancelled": "Отменено",
"appAlreadyAdded": "Приложение уже добавлено",
"alreadyUpToDateQuestion": "Приложение уже обновлено?",
"addApp": "Добавить",
"appSourceURL": "URL-источник приложения",
"appSourceURL": "URL-адрес источника приложения",
"error": "Ошибка",
"add": "Добавить",
"searchSomeSourcesLabel": "Поиск (в некоторых источниках)",
@ -63,10 +63,10 @@
"removeSelectedApps": "Удалить выбранные приложения",
"updateX": "Обновить {}",
"installX": "Установить {}",
"markXTrackOnlyAsUpdated": "Отметить {}\n(Только для отслеживания)\nкак обновленное",
"markXTrackOnlyAsUpdated": "Отметить {}\n(только для отслеживания)\nкак обновлённое",
"changeX": "Изменить {}",
"installUpdateApps": "Установить/Обновить приложения",
"installUpdateSelectedApps": "Установить/Обновить выбранные приложения",
"installUpdateApps": "Установить/обновить приложения",
"installUpdateSelectedApps": "Установить/обновить выбранные приложения",
"markXSelectedAppsAsUpdated": "Выбрано приложений: {}. Отметить как обновлённые?",
"no": "Нет",
"yes": "Да",
@ -74,10 +74,10 @@
"pinToTop": "Закрепить сверху",
"unpinFromTop": "Открепить",
"resetInstallStatusForSelectedAppsQuestion": "Сбросить статус установки для выбранных приложений?",
"installStatusOfXWillBeResetExplanation": "Статус установки для выбранных приложений будет сброшен.\n\nЭто может помочь, если версия приложения, отображаемая в Obtainium, некорректная из-за неудачных обновлений или других проблем",
"installStatusOfXWillBeResetExplanation": "Статус установки для выбранных приложений будет сброшен.\n\nЭто может помочь, если версия приложения, отображаемая в Obtainium, некорректная (из-за неудачных обновлений или других проблем).",
"customLinkMessage": "Эти ссылки работают на устройствах с установленным Obtainium",
"shareAppConfigLinks": "Поделиться конфигурацией приложения в виде HTML-ссылки",
"shareSelectedAppURLs": "Поделиться выбранными URL-адресами приложений",
"shareSelectedAppURLs": "Поделиться URL-адресами выбранных приложений",
"resetInstallStatus": "Сбросить статус установки",
"more": "Ещё",
"removeOutdatedFilter": "Удалить фильтр для устаревших приложений",
@ -86,7 +86,7 @@
"filterApps": "Фильтровать приложения",
"appName": "Название приложения",
"author": "Автор",
"upToDateApps": "Приложения со свежими обновлениями",
"upToDateApps": "Приложения с обновлениями",
"nonInstalledApps": "Неустановленные приложения",
"importExport": "Данные",
"settings": "Настройки",
@ -97,12 +97,12 @@
"obtainiumImport": "Импорт в Obtainium",
"importFromURLList": "Импорт из списка URL-адресов",
"searchQuery": "Поисковый запрос",
"appURLList": "Список URL приложений",
"appURLList": "Список URL-адресов приложений",
"line": "Строка",
"searchX": "Поиск {}",
"noResults": "Результатов не найдено",
"noResults": "Ничего не найдено",
"importX": "Импорт {}",
"importedAppsIdDisclaimer": "Импортированные приложения могут неверно отображаться как неустановленные.\nДля исправления этой проблемы повторно установите их через Obtainium.\nЭто не должно повлиять на данные приложения.\n\nПроблемы возникают только при импорте из URL-адреса и сторонних источников",
"importedAppsIdDisclaimer": "Импортированные приложения могут неверно отображаться как неустановленные.\nДля исправления этой проблемы повторно установите их через Obtainium.\nЭто не должно повлиять на данные приложения.\n\nПроблемы возникают только при импорте из URL-адреса и сторонних источников.",
"importErrors": "Ошибка импорта",
"importedXOfYApps": "Импортировано приложений: {} из {}",
"followingURLsHadErrors": "При импорте следующие URL-адреса содержали ошибки:",
@ -112,12 +112,12 @@
"theme": "Тема",
"dark": "Тёмная",
"light": "Светлая",
"followSystem": "Системная",
"followSystem": "Как в системе",
"followSystemThemeExplanation": "Следование системной теме возможно только при использовании сторонних приложений",
"useBlackTheme": "Использовать чёрную тему",
"appSortBy": "Сортировка приложений",
"authorName": "Автор/Название",
"nameAuthor": "Название/Автор",
"authorName": "Автор/название",
"nameAuthor": "Название/автор",
"asAdded": "В порядке добавления",
"appSortOrder": "Порядок",
"ascending": "По возрастанию",
@ -126,12 +126,12 @@
"neverManualOnly": "Никогда — только вручную",
"appearance": "Внешний вид",
"showWebInAppView": "Показывать исходную веб-страницу на странице приложения",
"pinUpdates": "Отображать обновления приложений сверху списка",
"pinUpdates": "Показывать обновления приложений сверху списка",
"updates": "Обновления",
"sourceSpecific": "Настройки источников",
"appSource": "Источник",
"noLogs": "Нет журналов",
"appLogs": "Логи",
"appLogs": "Журналы",
"close": "Закрыть",
"share": "Поделиться",
"appNotFound": "Приложение не найдено",
@ -141,21 +141,21 @@
"deviceSupportsXArch": "Ваше устройство поддерживает архитектуру процессора {}",
"deviceSupportsFollowingArchs": "Ваше устройство поддерживает следующие архитектуры процессора:",
"warning": "Предупреждение",
"sourceIsXButPackageFromYPrompt": "Источник приложения — '{}', но пакет для установки получен из '{}'. Продолжить?",
"sourceIsXButPackageFromYPrompt": "Источник приложения — «{}», но пакет для установки получен из «{}». Продолжить?",
"updatesAvailable": "Доступны обновления",
"updatesAvailableNotifDescription": "Уведомляет о наличии обновлений для одного или нескольких приложений в Obtainium",
"noNewUpdates": "Нет новых обновлений",
"xHasAnUpdate": "{} есть обновление",
"noNewUpdates": "Нет обновлений",
"xHasAnUpdate": "Для {} есть обновление",
"appsUpdated": "Приложения обновлены",
"appsNotUpdated": "Не удалось обновить приложения",
"appsUpdatedNotifDescription": "Уведомляет об обновлении одного или нескольких приложений в фоновом режиме",
"xWasUpdatedToY": "{} была обновлена до версии {}",
"xWasUpdatedToY": "{} обновлено до версии {}",
"xWasNotUpdatedToY": "Не удалось обновить {} до версии {}",
"errorCheckingUpdates": "Ошибка при проверке обновлений",
"errorCheckingUpdatesNotifDescription": "Уведомление о завершении проверки обновлений в фоновом режиме с ошибкой",
"appsRemoved": "Приложение удалено",
"appsRemovedNotifDescription": "Уведомление об удалении одного или несколько приложений из-за ошибок при их загрузке",
"xWasRemovedDueToErrorY": "{} был удален из-за ошибки: {}",
"xWasRemovedDueToErrorY": "{} удалено из-за ошибки: {}",
"completeAppInstallation": "Завершение установки приложения",
"obtainiumMustBeOpenToInstallApps": "Obtainium должен быть открыт для установки приложений",
"completeAppInstallationNotifDescription": "Уведомление о необходимости открыть Obtainium для завершения установки приложения",
@ -172,7 +172,7 @@
"installedVersionX": "Установленная версия: {}",
"lastUpdateCheckX": "Последняя проверка: {}",
"remove": "Удалить",
"yesMarkUpdated": "Да, отметить как обновленное",
"yesMarkUpdated": "Да, отметить как обновлённое",
"fdroid": "Официальные репозитории F-Droid",
"appIdOrName": "ID или название приложения",
"appId": "ID приложения",
@ -180,39 +180,39 @@
"reposHaveMultipleApps": "В хранилище несколько приложений",
"fdroidThirdPartyRepo": "Сторонние репозитории F-Droid",
"install": "Установить",
"markInstalled": "Пометить как установленное",
"markInstalled": "Отметить установленным",
"update": "Обновить",
"markUpdated": "Отметить обновлённым",
"additionalOptions": "Дополнительные настройки",
"disableVersionDetection": "Отключить обнаружение версии",
"noVersionDetectionExplanation": "Эта настройка должна использоваться только для приложений, где обнаружение версии не работает корректно",
"disableVersionDetection": "Отключить определение версии",
"noVersionDetectionExplanation": "Эта настройка должна использоваться только для приложений, где определение версии не работает корректно",
"downloadingX": "Загрузка {}",
"downloadX": "Скачать {}",
"downloadX": "Загрузить {}",
"downloadedX": "Загружено {}",
"releaseAsset": "Релизный объект",
"releaseAsset": "Объект выпуска",
"downloadNotifDescription": "Уведомляет пользователя о прогрессе загрузки приложения",
"noAPKFound": "APK не найден",
"noVersionDetection": "Обнаружение версий отключено",
"noAPKFound": "APK-файл не найден",
"noVersionDetection": "Определение версий отключено",
"categorize": "Категоризировать",
"categories": "Категории",
"category": "Категория",
"noCategory": "Без категории",
"noCategories": "Без категорий",
"deleteCategoriesQuestion": "Удалить категории?",
"categoryDeleteWarning": "Все приложения в удаленных категориях будут помечены как без категории",
"categoryDeleteWarning": "Все приложения в удалённых категориях будут помечены как «без категории»",
"addCategory": "Добавить категорию",
"label": "Метка",
"language": "Язык",
"copiedToClipboard": "Скопировано в буфер обмена",
"storagePermissionDenied": "Отказано в доступе к хранилищу",
"selectedCategorizeWarning": "Это заменит все текущие настройки категорий для выбранных приложений",
"filterAPKsByRegEx": "Отфильтровать APK-файлы\n(регулярное выражение)",
"filterAPKsByRegEx": "Фильтровать APK-файлы\n(регулярное выражение)",
"removeFromObtainium": "Удалить из Obtainium",
"uninstallFromDevice": "Удалить с устройства",
"onlyWorksWithNonVersionDetectApps": "Работает только для приложений с отключенным определением версии",
"releaseDateAsVersion": "Дата выпуска вместо версии",
"releaseTitleAsVersion": "Используйте название релиза в качестве строки версии",
"releaseDateAsVersionExplanation": "Этот параметр следует использовать только для приложений, в которых определение версии не работает правильно, но имеется дата выпуска",
"onlyWorksWithNonVersionDetectApps": "Работает только для приложений с отключённым определением версии",
"releaseDateAsVersion": "Использовать дату выпуска в качестве версии",
"releaseTitleAsVersion": "Использовать название выпуска в качестве версии",
"releaseDateAsVersionExplanation": "Эта настройка должна использоваться только для приложений, в которых определение версии не работает правильно, но имеется дата выпуска",
"changes": "Изменения",
"releaseDate": "Дата выпуска",
"importFromURLsInFile": "Импорт из файла URL-адресов (например: OPML)",
@ -220,56 +220,56 @@
"versionDetection": "Определение версии",
"standardVersionDetection": "Стандартное",
"groupByCategory": "Группировать по категориям",
"autoApkFilterByArch": опытаться отфильтровать APK-файлы по архитектуре процессора",
"autoLinkFilterByArch": опытайтесь отфильтровать ссылки по архитектуре процессора, если это возможно",
"autoApkFilterByArch": "Пытаться фильтровать APK по архитектуре процессора",
"autoLinkFilterByArch": "Пытаться фильтровать ссылки по архитектуре процессора, если это возможно",
"overrideSource": "Переопределить источник",
"dontShowAgain": "Не показывать снова",
"dontShowTrackOnlyWarnings": "Не показывать предупреждения о только отслеживаемых приложениях",
"dontShowAPKOriginWarnings": "Не показывать предупреждения об отличающемся источнике APK-файлов",
"moveNonInstalledAppsToBottom": "Отображать неустановленные приложения внизу списка",
"moveNonInstalledAppsToBottom": "Показывать неустановленные приложения внизу списка",
"gitlabPATLabel": "Персональный токен доступа GitLab",
"about": "Описание",
"requiresCredentialsInSettings": "{}: Для этого требуются дополнительные учетные данные (в настройках)",
"requiresCredentialsInSettings": "{}: для этого требуются дополнительные учётные данные (в настройках)",
"checkOnStart": "Проверять наличие обновлений при запуске",
"tryInferAppIdFromCode": опытаться определить ID приложения из исходного кода",
"removeOnExternalUninstall": "Автоматически убирать из списка удаленные извне приложения",
"tryInferAppIdFromCode": "Пытаться определить ID приложения из исходного кода",
"removeOnExternalUninstall": "Автоматически убирать из списка удалённые извне приложения",
"pickHighestVersionCode": "Автовыбор актуальной версии кода APK",
"checkUpdateOnDetailPage": "Проверять наличие обновлений при открытии страницы приложения",
"disablePageTransitions": "Отключить анимацию перехода между страницами",
"reversePageTransitions": "Реверс анимации перехода между страницами",
"minStarCount": "Минимальное количество звёзд",
"addInfoBelow": "Добавьте эту информацию ниже",
"addInfoInSettings": "Добавьте эту информацию в Настройки",
"githubSourceNote": "Используя ключ API можно обойти лимит запросов GitHub",
"addInfoInSettings": "Добавьте эту информацию в «Настройки»",
"githubSourceNote": "Используя ключ API, можно обойти ограничение запросов GitHub",
"sortByLastLinkSegment": "Сортировать только по последнему сегменту ссылки",
"filterReleaseNotesByRegEx": "Фильтровать примечания к выпуску\n(регулярное выражение)",
"customLinkFilterRegex": "Пользовательский фильтр ссылок APK\n(регулярное выражение, по умолчанию: '.apk$')",
"customLinkFilterRegex": "Пользовательский фильтр ссылок APK\n(регулярное выражение, по умолчанию: «.apk$»)",
"appsPossiblyUpdated": "Попытки обновления приложений",
"appsPossiblyUpdatedNotifDescription": "Уведомление о возможных обновлениях одного или нескольких приложений в фоновом режиме",
"xWasPossiblyUpdatedToY": "{} возможно был обновлен до {}",
"xWasPossiblyUpdatedToY": "{} возможно был обновлён до {}",
"enableBackgroundUpdates": "Включить обновления в фоне",
"backgroundUpdateReqsExplanation": "Фоновые обновления могут быть возможны не для всех приложений",
"backgroundUpdateLimitsExplanation": "Успешность фоновой установки можно определить только после открытия Obtainium",
"verifyLatestTag": "Проверять тег 'latest'",
"intermediateLinkRegex": "Фильтр для \"промежуточной\" ссылки для посещения",
"verifyLatestTag": "Проверять метку «latest»",
"intermediateLinkRegex": "Фильтр для «промежуточной» ссылки для посещения",
"filterByLinkText": "Фильтрация ссылок по тексту ссылки",
"intermediateLinkNotFound": "Промежуточная ссылка не найдена",
"intermediateLink": "Промежуточная ссылка",
"exemptFromBackgroundUpdates": "Исключить из фоновых обновлений (если включено)",
"bgUpdatesOnWiFiOnly": "Отключить фоновые обновления, если нет соединения с Wi-Fi",
"bgUpdatesWhileChargingOnly": "Отключение фоновых обновлений при отсутствии зарядки",
"autoSelectHighestVersionCode": "Автоматически выбирать APK с актуальной версией кода",
"versionExtractionRegEx": "Регулярное выражение для извлечения версии",
"trimVersionString": "Обрезка строки версии с помощью RegEx",
"matchGroupToUseForX": "Группа соответствия, которую следует использовать для \"{}\"",
"matchGroupToUse": "Выберите группу для использования",
"highlightTouchTargets": "Выделить менее очевидные элементы управления касанием",
"bgUpdatesOnWiFiOnly": "Отключить фоновые обновления, если нет подключения к Wi-Fi",
"bgUpdatesWhileChargingOnly": "Отключить фоновые обновления, если не идёт зарядка",
"autoSelectHighestVersionCode": "Автовыбор APK с актуальной версией кода",
"versionExtractionRegEx": "Извлечение версии\n(регулярное выражение)",
"trimVersionString": "Обрезка строки версии\n(регулярное выражение)",
"matchGroupToUseForX": "Группа соответствия для «{}»",
"matchGroupToUse": "Выбрать группу соответствия",
"highlightTouchTargets": "Выделять менее очевидные элементы управления касанием",
"pickExportDir": "Выбрать каталог для экспорта",
"autoExportOnChanges": "Автоэкспорт при изменениях",
"includeSettings": "Включить настройки",
"filterVersionsByRegEx": "Фильтровать версии по регулярному выражению",
"trySelectingSuggestedVersionCode": опробуйте выбрать предложенный код версии APK",
"dontSortReleasesList": "Сохранить порядок релизов от API",
"trySelectingSuggestedVersionCode": "Пробовать выбрать предложенный код версии APK",
"dontSortReleasesList": "Сохранить порядок выпусков от API",
"reverseSort": "Обратная сортировка",
"takeFirstLink": "Взять первую ссылку",
"skipSort": "Пропустить сортировку",
@ -290,18 +290,18 @@
"onlyCheckInstalledOrTrackOnlyApps": "Проверять обновления только у установленных или отслеживаемых приложений",
"supportFixedAPKURL": "Поддержка фиксированных URL-адресов APK",
"selectX": "Выбрать {}",
"parallelDownloads": "Разрешить параллельные загрузки",
"parallelDownloads": "Использовать параллельные загрузки",
"useShizuku": "Использовать Shizuku или Sui для установки",
"shizukuBinderNotFound": "Совместимый сервис Shizuku не найден, возможно он не запущен",
"shizukuOld": "Устаревшая версия Shizuku (<11), обновите",
"shizukuOldAndroidWithADB": "Shizuku работает на Android < 8.1 с ADB, обновите Android или используйте Sui",
"shizukuPretendToBeGooglePlay": "Указать Google Play как источник установки (если используется Shizuku)",
"shizukuPretendToBeGooglePlay": "Указывать Google Play как источник установки (если используется Shizuku)",
"useSystemFont": "Использовать системный шрифт",
"useVersionCodeAsOSVersion": "Использовать код версии приложения как версию, обнаруженную ОС",
"requestHeader": "Заголовок запроса",
"useLatestAssetDateAsReleaseDate": "Использовать последнюю загрузку ресурса в качестве даты выпуска",
"useLatestAssetDateAsReleaseDate": "Использовать последнюю загрузку объекта в качестве даты выпуска",
"defaultPseudoVersioningMethod": "Метод псевдоверсионирования по умолчанию",
"partialAPKHash": "Частичный хэш APK",
"partialAPKHash": "Частичный хеш APK",
"APKLinkHash": "Хеш ссылки APK",
"directAPKLink": "Прямая ссылка на APK",
"pseudoVersionInUse": "Используется псевдоверсия",
@ -309,17 +309,17 @@
"latest": "Последний",
"invertRegEx": "Инвертировать регулярное выражение",
"note": "Примечание",
"selfHostedNote": "Выпадающий список \"{}\" можно использовать для доступа к самостоятельно размещенным/настроенным экземплярам любого источника.",
"badDownload": "APK не удалось разобрать (несовместимая или неполная загрузка)",
"beforeNewInstallsShareToAppVerifier": "Поделитесь новыми приложениями с AppVerifier (если доступно)",
"selfHostedNote": "Выпадающий список «{}» можно использовать для доступа к самостоятельно размещённым/настроенным экземплярам любого источника.",
"badDownload": "Не удалось разобрать APK (несовместимая или неполная загрузка)",
"beforeNewInstallsShareToAppVerifier": "Делиться новыми приложениями с AppVerifier (если доступно)",
"appVerifierInstructionToast": "Поделитесь с AppVerifier, а затем вернитесь сюда, когда будете готовы.",
"wiki": "Помощь/Вики",
"crowdsourcedConfigsLabel": "Конфигурации приложений на основе краудсорсинга (используйте на свой страх и риск)",
"crowdsourcedConfigsShort": "Конфиги приложений с помощью краудсорсинга",
"crowdsourcedConfigsShort": "Краудсорсинг конфигураций приложений",
"allowInsecure": "Разрешить небезопасные HTTP-запросы",
"stayOneVersionBehind": "Не отставайте от последней версии",
"useFirstApkOfVersion": "Автоматический выбор первого из нескольких APK",
"refreshBeforeDownload": "Обновляйте информацию о приложении перед загрузкой",
"stayOneVersionBehind": "Оставаться на одну версию ниже последней",
"useFirstApkOfVersion": "Автовыбор первого из нескольких APK",
"refreshBeforeDownload": "Обновлять информацию о приложении перед загрузкой",
"tencentAppStore": "Tencent App Store",
"coolApk": "CoolApk",
"vivoAppStore": "vivo App Store (CN)",
@ -327,67 +327,80 @@
"smartname": "Имя (умное)",
"sortMethod": "Метод сортировки",
"welcome": "Добро пожаловать",
"documentationLinksNote": "На странице Obtainium GitHub, ссылка на которую приведена ниже, содержатся ссылки на видео, статьи, обсуждения и другие ресурсы, которые помогут вам понять, как пользоваться приложением.",
"batteryOptimizationNote": "Обратите внимание, что фоновая загрузка может работать более надежно, если отключить оптимизацию батареи ОС для Obtainium.",
"fileDeletionError": "Не удалось удалить файл (попробуйте удалить его вручную, а затем повторите попытку): \"{}\"",
"documentationLinksNote": "На странице Obtainium в GitHub, ссылка на которую приведена ниже, содержатся ссылки на видео, статьи, обсуждения и другие ресурсы, которые помогут вам понять, как пользоваться приложением.",
"batteryOptimizationNote": "Обратите внимание, что фоновая загрузка может работать более надёжно, если отключить оптимизацию батареи ОС для Obtainium.",
"fileDeletionError": "Не удалось удалить файл (попробуйте удалить его вручную, а затем повторите попытку): «{}»",
"removeAppQuestion": {
"one": "Удалить приложение?",
"other": "Удалить приложения?"
},
"tooManyRequestsTryAgainInMinutes": {
"one": "Слишком много запросов (ограничение скорости) — попробуйте снова через {} минуту",
"other": "Слишком много запросов (ограничение скорости) — попробуйте снова через {} минуты"
"few": "Слишком много запросов (ограничение скорости) — попробуйте снова через {} минуты",
"other": "Слишком много запросов (ограничение скорости) — попробуйте снова через {} минут"
},
"bgUpdateGotErrorRetryInMinutes": {
"one": "При проверке обновлений в фоновом режиме возникла ошибка {}, повторная проверка будет запланирована через {} минуту",
"other": "При проверке обновлений в фоновом режиме возникла ошибка {}, повторная проверка будет запланирована через {} минуты"
"one": "При проверке обновлений в фоновом режиме возникла ошибка {}, повторная проверка будет через {} минуту",
"few": "При проверке обновлений в фоновом режиме возникла ошибка {}, повторная проверка будет через {} минуты",
"other": "При проверке обновлений в фоновом режиме возникла ошибка {}, повторная проверка будет через {} минут"
},
"bgCheckFoundUpdatesWillNotifyIfNeeded": {
"one": "В ходе проверки обновления в фоновом режиме было обнаружено {} обновление — Пользователю будет отправлено уведомление, если это необходимо",
"other": "В ходе проверки обновления в фоновом режиме было обнаружено {} обновленийПользователю будет отправлено уведомление, если это необходимо"
"one": "При проверке в фоновом режиме было обнаружено {} обновление — пользователь будет уведомлен по необходимости",
"one": "При проверке в фоновом режиме было обнаружено {} обновленияпользователь будет уведомлен по необходимости",
"other": "При проверке в фоновом режиме было обнаружено {} обновлений — пользователь будет уведомлен по необходимости"
},
"apps": {
"one": "{} приложение",
"few": "{} приложения",
"other": "{} приложений"
},
"url": {
"one": "{} URL-адрес",
"other": "{} URL-адреса"
"few": "{} URL-адреса",
"other": "{} URL-адресов"
},
"minute": {
"one": "{} минута",
"other": "{} минуты"
"few": "{} минуты",
"other": "{} минут"
},
"hour": {
"one": "{} час",
"few": "{} часа",
"other": "{} часов"
},
"day": {
"one": "{} день",
"few": "{} дня",
"other": "{} дней"
},
"clearedNLogsBeforeXAfterY": {
"one": "Очищен {n} журнал (до = {before}, после = {after})",
"few": "Очищены {n} журнала (до = {before}, после = {after})",
"other": "Очищено {n} журналов (до = {before}, после = {after})"
},
"xAndNMoreUpdatesAvailable": {
"one": "У {} и еще 1 приложения есть обновление",
"one": "У {} и ещё 1 приложения есть обновление",
"other": "У {} и ещё {} приложений есть обновления"
},
"xAndNMoreUpdatesInstalled": {
"one": "{} и ещё 1 приложение были обновлены",
"few": "{} и ещё {} приложения были обновлены",
"other": "{} и ещё {} приложений были обновлены"
},
"xAndNMoreUpdatesFailed": {
"one": "Не удалось обновить {} и ещё 1 приложение",
"few": "Не удалось обновить {} и ещё {} приложения",
"other": "Не удалось обновить {} и ещё {} приложений"
},
"xAndNMoreUpdatesPossiblyInstalled": {
"one": "{} и ещё 1 приложение могли быть обновлены",
"few": "{} и ещё {} приложения могли быть обновлены",
"other": "{} и ещё {} приложений могли быть обновлены"
},
"apk": {
"one": "{} APK",
"other": "{} APKи"
"one": "{} APK-файл",
"few": "{} APK-файла",
"other": "{} APK-файлов"
}
}

View File

@ -9,16 +9,15 @@ import 'package:obtainium/providers/native_provider.dart';
import 'package:obtainium/providers/notifications_provider.dart';
import 'package:obtainium/providers/settings_provider.dart';
import 'package:obtainium/providers/source_provider.dart';
import 'package:permission_handler/permission_handler.dart';
import 'package:provider/provider.dart';
import 'package:dynamic_color/dynamic_color.dart';
import 'package:device_info_plus/device_info_plus.dart';
import 'package:background_fetch/background_fetch.dart';
import 'package:easy_localization/easy_localization.dart';
// ignore: implementation_imports
import 'package:easy_localization/src/easy_localization_controller.dart';
// ignore: implementation_imports
import 'package:easy_localization/src/localization.dart';
import 'package:flutter_foreground_task/flutter_foreground_task.dart';
List<MapEntry<Locale, String>> supportedLocales = const [
MapEntry(Locale('en'), 'English'),
@ -86,16 +85,31 @@ Future<void> loadTranslations() async {
}
@pragma('vm:entry-point')
void backgroundFetchHeadlessTask(HeadlessTask task) async {
String taskId = task.taskId;
bool isTimeout = task.timeout;
if (isTimeout) {
print('BG update task timed out.');
BackgroundFetch.finish(taskId);
return;
void startCallback() {
FlutterForegroundTask.setTaskHandler(MyTaskHandler());
}
class MyTaskHandler extends TaskHandler {
static const String incrementCountCommand = 'incrementCount';
@override
Future<void> onStart(DateTime timestamp, TaskStarter starter) async {
print('onStart(starter: ${starter.name})');
bgUpdateCheck('bg_check', null);
}
await bgUpdateCheck(taskId, null);
BackgroundFetch.finish(taskId);
@override
void onRepeatEvent(DateTime timestamp) {
bgUpdateCheck('bg_check', null);
}
@override
Future<void> onDestroy(DateTime timestamp, bool isTimeout) async {
print('Foreground service onDestroy(isTimeout: $isTimeout)');
}
@override
void onReceiveData(Object data) {}
}
void main() async {
@ -119,6 +133,7 @@ void main() async {
}
final np = NotificationsProvider();
await np.initialize();
FlutterForegroundTask.initCommunicationPort();
runApp(
MultiProvider(
providers: [
@ -136,7 +151,6 @@ void main() async {
),
),
);
BackgroundFetch.registerHeadlessTask(backgroundFetchHeadlessTask);
}
class Obtainium extends StatefulWidget {
@ -152,32 +166,81 @@ class _ObtainiumState extends State<Obtainium> {
@override
void initState() {
super.initState();
initPlatformState();
// FlutterForegroundTask.addTaskDataCallback(onReceiveForegroundServiceData);
WidgetsBinding.instance.addPostFrameCallback((_) {
requestNonOptionalPermissions();
initForegroundService();
});
}
Future<void> initPlatformState() async {
await BackgroundFetch.configure(
BackgroundFetchConfig(
minimumFetchInterval: 15,
stopOnTerminate: false,
startOnBoot: true,
enableHeadless: true,
requiresBatteryNotLow: false,
requiresCharging: false,
requiresStorageNotLow: false,
requiresDeviceIdle: false,
requiredNetworkType: NetworkType.ANY,
Future<void> requestNonOptionalPermissions() async {
final NotificationPermission notificationPermission =
await FlutterForegroundTask.checkNotificationPermission();
if (notificationPermission != NotificationPermission.granted) {
await FlutterForegroundTask.requestNotificationPermission();
}
if (!await FlutterForegroundTask.isIgnoringBatteryOptimizations) {
await FlutterForegroundTask.requestIgnoreBatteryOptimization();
}
}
void initForegroundService() {
FlutterForegroundTask.init(
androidNotificationOptions: AndroidNotificationOptions(
channelId: 'bg_update',
channelName: tr('foregroundService'),
channelDescription: tr('foregroundService'),
onlyAlertOnce: true,
),
iosNotificationOptions: const IOSNotificationOptions(
showNotification: false,
playSound: false,
),
foregroundTaskOptions: ForegroundTaskOptions(
eventAction: ForegroundTaskEventAction.repeat(900000),
autoRunOnBoot: true,
autoRunOnMyPackageReplaced: true,
allowWakeLock: true,
allowWifiLock: true,
),
(String taskId) async {
await bgUpdateCheck(taskId, null);
BackgroundFetch.finish(taskId);
},
(String taskId) async {
context.read<LogsProvider>().add('BG update task timed out.');
BackgroundFetch.finish(taskId);
},
);
if (!mounted) return;
}
Future<ServiceRequestResult?> startForegroundService(bool restart) async {
if (await FlutterForegroundTask.isRunningService) {
if (restart) {
return FlutterForegroundTask.restartService();
}
} else {
return FlutterForegroundTask.startService(
serviceTypes: [ForegroundServiceTypes.specialUse],
serviceId: 666,
notificationTitle: tr('foregroundService'),
notificationText: tr('fgServiceNotice'),
notificationIcon: NotificationIcon(
metaDataName: 'dev.imranr.obtainium.service.NOTIFICATION_ICON',
),
callback: startCallback,
);
}
return null;
}
stopForegroundService() async {
if (await FlutterForegroundTask.isRunningService) {
return FlutterForegroundTask.stopService();
}
}
// void onReceiveForegroundServiceData(Object data) {
// print('onReceiveTaskData: $data');
// }
@override
void dispose() {
// Remove a callback to receive data sent from the TaskHandler.
// FlutterForegroundTask.removeTaskDataCallback(onReceiveForegroundServiceData);
super.dispose();
}
@override
@ -186,15 +249,18 @@ class _ObtainiumState extends State<Obtainium> {
AppsProvider appsProvider = context.read<AppsProvider>();
LogsProvider logs = context.read<LogsProvider>();
NotificationsProvider notifs = context.read<NotificationsProvider>();
if (settingsProvider.updateInterval == 0) {
stopForegroundService();
} else {
startForegroundService(false);
}
if (settingsProvider.prefs == null) {
settingsProvider.initializeSettings();
} else {
bool isFirstRun = settingsProvider.checkAndFlipFirstRun();
if (isFirstRun) {
logs.add('This is the first ever run of Obtainium.');
// If this is the first run, ask for notification permissions and add Obtainium to the Apps list
Permission.notification.request();
// If this is the first run, add Obtainium to the Apps list
if (!fdroid) {
getInstalledInfo(obtainiumId)
.then((value) {
@ -236,68 +302,71 @@ class _ObtainiumState extends State<Obtainium> {
notifs.checkLaunchByNotif();
});
return DynamicColorBuilder(
builder: (ColorScheme? lightDynamic, ColorScheme? darkDynamic) {
// Decide on a colour/brightness scheme based on OS and user settings
ColorScheme lightColorScheme;
ColorScheme darkColorScheme;
if (lightDynamic != null &&
darkDynamic != null &&
settingsProvider.useMaterialYou) {
lightColorScheme = lightDynamic.harmonized();
darkColorScheme = darkDynamic.harmonized();
} else {
lightColorScheme = ColorScheme.fromSeed(
seedColor: settingsProvider.themeColor,
return WithForegroundTask(
child: DynamicColorBuilder(
builder: (ColorScheme? lightDynamic, ColorScheme? darkDynamic) {
// Decide on a colour/brightness scheme based on OS and user settings
ColorScheme lightColorScheme;
ColorScheme darkColorScheme;
if (lightDynamic != null &&
darkDynamic != null &&
settingsProvider.useMaterialYou) {
lightColorScheme = lightDynamic.harmonized();
darkColorScheme = darkDynamic.harmonized();
} else {
lightColorScheme = ColorScheme.fromSeed(
seedColor: settingsProvider.themeColor,
);
darkColorScheme = ColorScheme.fromSeed(
seedColor: settingsProvider.themeColor,
brightness: Brightness.dark,
);
}
// set the background and surface colors to pure black in the amoled theme
if (settingsProvider.useBlackTheme) {
darkColorScheme = darkColorScheme
.copyWith(surface: Colors.black)
.harmonized();
}
if (settingsProvider.useSystemFont) NativeFeatures.loadSystemFont();
return MaterialApp(
title: 'Obtainium',
localizationsDelegates: context.localizationDelegates,
supportedLocales: context.supportedLocales,
locale: context.locale,
navigatorKey: globalNavigatorKey,
debugShowCheckedModeBanner: false,
theme: ThemeData(
useMaterial3: true,
colorScheme: settingsProvider.theme == ThemeSettings.dark
? darkColorScheme
: lightColorScheme,
fontFamily: settingsProvider.useSystemFont
? 'SystemFont'
: 'Montserrat',
),
darkTheme: ThemeData(
useMaterial3: true,
colorScheme: settingsProvider.theme == ThemeSettings.light
? lightColorScheme
: darkColorScheme,
fontFamily: settingsProvider.useSystemFont
? 'SystemFont'
: 'Montserrat',
),
home: Shortcuts(
shortcuts: <LogicalKeySet, Intent>{
LogicalKeySet(LogicalKeyboardKey.select):
const ActivateIntent(),
},
child: const HomePage(),
),
);
darkColorScheme = ColorScheme.fromSeed(
seedColor: settingsProvider.themeColor,
brightness: Brightness.dark,
);
}
// set the background and surface colors to pure black in the amoled theme
if (settingsProvider.useBlackTheme) {
darkColorScheme = darkColorScheme
.copyWith(surface: Colors.black)
.harmonized();
}
if (settingsProvider.useSystemFont) NativeFeatures.loadSystemFont();
return MaterialApp(
title: 'Obtainium',
localizationsDelegates: context.localizationDelegates,
supportedLocales: context.supportedLocales,
locale: context.locale,
navigatorKey: globalNavigatorKey,
debugShowCheckedModeBanner: false,
theme: ThemeData(
useMaterial3: true,
colorScheme: settingsProvider.theme == ThemeSettings.dark
? darkColorScheme
: lightColorScheme,
fontFamily: settingsProvider.useSystemFont
? 'SystemFont'
: 'Montserrat',
),
darkTheme: ThemeData(
useMaterial3: true,
colorScheme: settingsProvider.theme == ThemeSettings.light
? lightColorScheme
: darkColorScheme,
fontFamily: settingsProvider.useSystemFont
? 'SystemFont'
: 'Montserrat',
),
home: Shortcuts(
shortcuts: <LogicalKeySet, Intent>{
LogicalKeySet(LogicalKeyboardKey.select): const ActivateIntent(),
},
child: const HomePage(),
),
);
},
},
),
);
}
}

View File

@ -104,10 +104,10 @@ packages:
dependency: "direct main"
description:
name: battery_plus
sha256: a0409fe7d21905987eb1348ad57c634f913166f14f0c8936b73d3f5940fac551
sha256: fb794c34cee2e4ea31005fb17ff15e1d904951ec7f15eedead741021870ee834
url: "https://pub.dev"
source: hosted
version: "6.2.1"
version: "6.2.2"
battery_plus_platform_interface:
dependency: transitive
description:
@ -216,18 +216,18 @@ packages:
dependency: "direct main"
description:
name: device_info_plus
sha256: "0c6396126421b590089447154c5f98a5de423b70cfb15b1578fd018843ee6f53"
sha256: "98f28b42168cc509abc92f88518882fd58061ea372d7999aecc424345c7bff6a"
url: "https://pub.dev"
source: hosted
version: "11.4.0"
version: "11.5.0"
device_info_plus_platform_interface:
dependency: transitive
description:
name: device_info_plus_platform_interface
sha256: "0b04e02b30791224b31969eb1b50d723498f402971bff3630bca2ba839bd1ed2"
sha256: e1ea89119e34903dca74b883d0dd78eb762814f97fb6c76f35e9ff74d261a18f
url: "https://pub.dev"
source: hosted
version: "7.0.2"
version: "7.0.3"
dynamic_color:
dependency: "direct main"
description:
@ -377,6 +377,14 @@ packages:
url: "https://pub.dev"
source: hosted
version: "0.7.1"
flutter_foreground_task:
dependency: "direct main"
description:
name: flutter_foreground_task
sha256: "9f1b25a81db95d7119d2c5cffc654048cbdd49d4056183e1beadc1a6a38f3e29"
url: "https://pub.dev"
source: hosted
version: "9.1.0"
flutter_keyboard_visibility:
dependency: transitive
description:
@ -995,10 +1003,10 @@ packages:
dependency: transitive
description:
name: synchronized
sha256: "0669c70faae6270521ee4f05bffd2919892d42d1276e6c495be80174b6bc0ef6"
sha256: c254ade258ec8282947a0acbbc90b9575b4f19673533ee46f2f6e9b3aeefd7c0
url: "https://pub.dev"
source: hosted
version: "3.3.1"
version: "3.4.0"
term_glyph:
dependency: transitive
description:
@ -1201,4 +1209,4 @@ packages:
version: "6.3.0"
sdks:
dart: ">=3.8.1 <4.0.0"
flutter: ">=3.27.0"
flutter: ">=3.29.0"

View File

@ -16,7 +16,7 @@ publish_to: 'none' # Remove this line if you wish to publish to pub.dev
# https://developer.apple.com/library/archive/documentation/General/Reference/InfoPlistKeyReference/Articles/CoreFoundationKeys.html
# In Windows, build-name is used as the major, minor, and patch parts
# of the product and file versions while build-number is used as the build suffix.
version: 1.1.57+2314
version: 1.1.58+2315
environment:
sdk: ^3.8.1
@ -94,6 +94,7 @@ dependencies:
# package. See that file for information about deactivating specific lint
# rules and activating additional ones.
flutter_lints: ^6.0.0
flutter_foreground_task: ^9.1.0
flutter_launcher_icons:
android: "ic_launcher"