648 Commits

Author SHA1 Message Date
Imran Remtulla
78be901e92 Set parallel downloads default to true; hide action banner when fewer than 2 pending updates/installs 2026-07-20 01:13:05 +01:00
Imran Remtulla
05dd135665 Unify form input styling: global borderless filled theme, rename Settings* components to generic names, merge settings_widgets.dart into ui_widgets.dart, convert SegmentedButtons to DropdownMenus, add per-app checkbox bulk update dialog, compact SliverAppBars, inline trailing icon buttons, newline-terminated error messages for breathing room, ConnectedCard defaults to zero padding (#3107, #3100) 2026-07-17 21:33:26 +01:00
Imran Remtulla
78cc9042be Replace bulky SliverAppBar.large headers with compact SliverAppBar; add 3-way action banner setting (All/Updates/None); skip confirmation dialog for update-only banner clicks; replace all-or-nothing obtain dialog toggles with per-app checkbox list (#3107, #3100) 2026-07-17 12:16:02 +01:00
Imran Remtulla
001b41683d Fix spacing, source names, and redundant button
- Remove double top padding on add-app and import-from-URL-list pages:
  content area no longer adds MediaQuery.padding.top since the
  AppBar/SliverAppBar already accounts for system bar insets.

- Fix source names showing raw translation keys in group headers and
  filter dropdown. Eight sources used name = tr(key) in their
  constructor, which resolved before translations loaded at startup.
  Changed to @override String get name => tr(key) so resolution is
  always lazy. Store sourceIdentifier (stable) instead of name in
  AppInMemory.sourceType; resolve to display name at presentation
  time via a lookup map built from SourceProvider.sources.

- Remove redundant crowdsourced-app-configs button from the settings
  page app bar (an ActionChip on the add-app page already provides
  the same link).
2026-07-15 20:53:16 +01:00
Imran Remtulla
7ab50177f7 Fix UI unresponsiveness during update checks (#3043)
- Memoize findStandardFormatsForVersion with bounded 4096-entry cache,
  and deduplicate the 436 generated version regex patterns. This
  collapses the per-GitHub-app sort from ~500K regex evaluations to
  ~100, and caches results across all version reconciliation calls.

- Optimize GitHub _sortGitHubReleases: date sort (the default) now
  skips regex computation entirely; smartname sorts precompute formats
  once per release instead of re-evaluating per sort-comparison pair.

- Inline app list computation into AppsPage.build() instead of using
  a three-layer caching system (context.select, pipelineSignature,
  _computeListData). With memoization making rebuilds ~2ms, the
  complexity of signature hashing and cache field management is
  unnecessary.

- Cache getAppsDir() to avoid redundant platform-channel calls per
  app during saveApps batches.
2026-07-15 17:04:37 +01:00
Imran Remtulla
6a68966718 Try fixing refresh performance (3043) + UI fixes 2026-07-15 11:22:06 +01:00
Imran Remtulla
e223028237 Fix UI unresponsiveness during update checks with time-based save throttling instead of count-based batching (#3043) 2026-07-15 10:28:59 +01:00
Imran Remtulla
4de395454f - Removed tabbed UI (#3059, #3069)
- Improved update check UI performance (#3043)
- Improved app list loading UI (#2666)
- Other minor UI tweaks
- Improve install result detection for external installers in foreground
2026-07-15 00:10:39 +01:00
Imran Remtulla
b396db35fc Prevent background retry tasks from spawning duplicate install tasks; only the initial check runs the install phase 2026-07-14 15:07:00 +01:00
Imran Remtulla
4442bb478f Make showActionBannerForUpdateOnly true by default 2026-07-14 13:16:18 +01:00
Imran Remtulla
6320ab94c1 Add HandshakeException retry with staggered random delays for concurrent TLS failures during batch update checks (#3008) 2026-07-14 09:36:55 +01:00
Imran Remtulla
9db6e52b20 Fix force-all update checks ignoring only-check-installed-or-track-only-apps setting (#3063) 2026-07-12 20:47:42 +01:00
Imran Remtulla
68a7a21746 Add disable-swipe setting and always-use-phone-layout option (#3039)(#3045). Pin app-detail actions bar to bottom (#3044). Fix settings scroll on wide/trackpad devices (#3046). 2026-07-11 19:02:53 +01:00
Imran Remtulla
4fd0efc5e6 Restore determinate progress bar during app list refresh (#3029) 2026-07-09 20:37:19 +01:00
Imran Remtulla
579e77233d Apply German translations from issue, add lowerCaseUnlessLang helper (#3030) 2026-07-09 19:37:43 +01:00
Imran Remtulla
dd2c9bb149 Fix external cache directory and app_data directory crashes on custom ROMs by falling back to internal storage (#3020) 2026-07-09 17:15:39 +01:00
Imran Remtulla
b485ad59ab perf: memoize app list pipeline, preload icons, fix form validation
- apps.dart: cache filtered/sorted/grouped app list results keyed by
  pipeline signature, avoiding O(n) recomputation on every rebuild
  when only progress ticks or selection changes fire

- add_app.dart: skip parent rebuild when URL text changes without
  switching the detected source; track _urlValid separately so the
  Add button correctly enables/disables; _prevValid prevents early
  return from skipping setState when validity flips

- generated_form_renderer.dart: replace broken validateTextField
  which couldn't find TextFormField inside TypeAheadField wrappers
  with a _fieldKeys list for direct FormFieldState.isValid checks

- apps_provider_lifecycle.dart: preload app icons from disk cache
  after initial loadApps() so scrolling never triggers disk I/O
2026-07-08 19:57:39 +01:00
Imran Remtulla
ee30e860bd Fix external storage crash on custom ROMs by falling back to internal storage [3003, 3020] 2026-07-08 16:00:08 +01:00
Imran Remtulla
9e2e0bddd1 Fix #3018 crash on resume with pending share intent, #3015 add-app nav bar overlap, #3019 banner-only-for-updates setting 2026-07-08 15:04:25 +01:00
Imran Remtulla
f14a11acf7 Prevent stale translations by converting form-item fields and names to getters
All form-item list fields (additionalSourceAppSpecificSettingFormItems,
sourceConfigSettingFormItems, searchQuerySettingFormItems) and
_commonAppSettingFormItems now use getters or computed keys instead of
frozen tr() at construction time, so labels pick up the current locale
on every access via the renderer's tr() wrapping.

- _commonAppSettingFormItems: final field -> getter
- additionalSourceAppSpecificSettingFormItems: mutable field -> getter (12 sources)
- sourceConfigSettingFormItems: mutable field -> getter (GitHub, GitLab)
- searchQuerySettingFormItems: mutable field -> getter (GitHub, Codeberg)
- HTML's intermediateFormItems/commonFormItems/finalStepFormitems: var -> getters
- FDroidRepo's runOnAddAppInputChange: list mutation -> _appIdFromUrl bool
- Source names: tr('fdroid') etc. -> raw key string; display sites wrap with tr()
- completeInstallationNotification: module-level final -> getter
- GitHubStars.name/requiredArgs: fields -> getters; MassAppUrlSource -> abstract getters
- Removed memoization caches (_combinedItemsCache, _hasAppSpecificSettingsCache,
  _flatCombinedFormItemsCache) now superseded by getter rebuilding
- Renderer: tr() wrapping on dropdown option values
2026-07-07 23:50:50 +01:00
Imran Remtulla
feac7f74cd fix: BG install polling, debug appId support, forceAll manual check, and crash fix
- BG install polling: increase from 8s (16×500ms) to 10s (20×500ms).
  Remove rollback of installedVersion on poll timeout — optimistic
  pre-set now sticks since foreground resume syncs from device truth.
  Add logging for confirmed/timed out with baseline and current state.
- Installer ownership check now supports debug suffix (.debug) and
  fdroid suffix (.fdroid) in addition to base obtainiumId in both
  stock_installer.dart and apps_provider_install.dart.
- Manual BG check button ("Run background check now") now passes
  forceAll:true — bypasses per-app lastUpdateCheck interval filter and
  enableBackgroundUpdates/interval==0 gate, so it unconditionally
  checks all apps.
- Bump pinned android_package_installer fork to include fix for
  IllegalStateException crash when multiple session-based installs
  fire SESSION_API_PACKAGE_INSTALLED intents in one cycle.
2026-07-07 19:34:19 +01:00
Imran Remtulla
03dc7ef77b chore: remove stale debug logs and spurious getInstalledInfo error logging
- Remove all APPS REFRESH debug logs from apps.dart and
  apps_provider_updates.dart (trace logging for refresh diagnostics no
  longer needed)
- Remove getInstalledInfo error log entirely: "app not installed" is
  always an expected condition, not an error. Remove printErr parameter
  and all printErr: false at call sites
2026-07-07 00:39:57 +01:00
Imran Remtulla
712ab8891d fix: restore RefreshIndicator spinner and remove stuck LinearProgressIndicator
- Fix RefreshIndicator spinner disappearing immediately after pull-to-refresh:
  changed onRefresh from a new closure (identity changes every build) to a
  stable direct method reference, preventing RefreshIndicator.didUpdateWidget
  from cancelling the in-progress animation on parent rebuilds
- Fix refresh checking 0 apps: the per-app lastUpdateCheck interval filter
  was being applied to user-triggered foreground refreshes. Added forceAll
  parameter to checkUpdates so the foreground path checks all apps regardless
  of when they were last checked (the interval filter is for BG periodic
  idle checks only). BG retry path also simplified to use specificIds directly
- Remove LinearProgressIndicator and refreshingSince field: the determinate
  bar was always stuck at 0% (lastUpdateCheck not in pipelineSignature) and
  even with that fixed it would jump 0→100% in one frame since all apps are
  fetched in parallel. RefreshIndicator spinner provides the only feedback now
- Fix switchToPage(0) ~2s delay: removed stale waitUntil call that waited
  for apps page state to become null — was a holdover from main's 4-tab
  architecture where pages were created/destroyed; unnecessary with 2 permanent
  tabs on dev
- Fix deep link Add page URL field empty: timing race where GeneratedForm's
  init notification (isBuilding) overwrote userInput before processInitialUrl
  could set the URL. Moved userInput assignment inside if (!isBuilding) guard.
  Also: restore showError in linkFn, add query-param extraction, soften
  getSource gate in interpretLink
- Add trace logging throughout refresh → checkUpdates flow
2026-07-07 00:34:59 +01:00
Imran Remtulla
d82eb9e1f4 refactor: simplify background update checking with workmanager
- Replace background_fetch + flutter_foreground_task with workmanager (^0.9.0+3)
- Fixed 15-min periodic task with ExistingPeriodicWorkPolicy.keep — no
  dynamic scheduling, no cancelAll, no settings listener
- Single callbackDispatcher replaces two entry points (headless task +
  foreground service TaskHandler)
- Removed lastCompletedBGCheckTime — per-app lastUpdateCheck with
  interval-based filter determines which apps are due
- Removed batch-of-8 chunking — single Future.wait on all apps
- _bgRunUpdateCheck returns ({updates, errors, toThrow}) record —
  notification dispatch and install mode handled by bgUpdateCheck
- Retries via one-off WorkManager task with OS-enforced initialDelay +
  WiFi/charging constraints (no blocking, no recursion)
- _runBGInstallMode takes explicit List<String> of app IDs — no
  auto-discovery ambiguity. Stale pending installs from prior cycles
  discovered via findAppIdsWithPendingUpdates + dedup
- Removed canInstallSilentlyInBackground checks from install mode —
  performed once in notification loop, passed explicitly
- Removed flutter_secure_storage and _migrateSecureCreds (was never
  populated on any release, blocked DNS in bg isolates)
- Merged exemptToNotify into toNotify (fixes notification collision bug)
- Error notification IDs now in safe range 105–10000 (above fixed IDs)
- Retry task names include random suffix (collision-proof)
- Added await initializeSettings() before settings reads (race fix)
- Improved logging: entry, gate-checks, notifications summary, retry
  decisions, install counts, pre-existing vs new, completion
- Added "Run background check now" button to Updates settings card

Removed: foreground service declaration, FOREGROUND_SERVICE_SPECIAL_USE
permission, useFGService, showBatteryOptimizationPrompt, 4 orphaned
translation keys, ~100 lines of dead code.
2026-07-06 23:17:54 +01:00
Imran Remtulla
ab60373574 experiment: replace background_fetch + flutter_foreground_task with workmanager
THIS IS EXPERIMENTAL AND MAY NEED TO BE REVERTED.

- Replace background_fetch (^1.7.0) and flutter_foreground_task (^9.2.2)
  with workmanager (^0.9.0+3)
- Single callbackDispatcher replaces both the old headless task and the
  foreground service TaskHandler — simplifies to one code path
- WorkManager constraints enforce WiFi-only and charging-only at the OS
  level instead of checking in Dart after waking the app
- Fix: enableBackgroundUpdates toggle now properly cancels/resumes
  WorkManager tasks (previously the toggle was UI-only and didn't
  actually stop background checks)
- Add "Run background check now" button to the bottom of the Updates
  settings card for manual testing
- Detailed logging at every stage: WorkManager init, task registration,
  callback invocation, completion, and crashes
- Removed: foreground service declaration, FOREGROUND_SERVICE_SPECIAL_USE
  permission, useFGService setting, showBatteryOptimizationPrompt setting,
  and 4 orphaned translation keys

Known risk: workmanager was previously tried and abandoned in Nov 2022
(PR #97) due to fluttercommunity/flutter_workmanager#415 and #363 (tasks
stopping after app closed / after 3-5 days). These issues remain open
but the package has had 4 years of updates since then.
2026-07-06 17:29:44 +01:00
Imran Remtulla
022c0f315a fix: purge flutter_secure_storage, fix all analyze warnings
- Remove flutter_secure_storage dependency (introduced and removed within
  this branch, never populated on any release — no migration needed)
- Drop _migrateSecureCreds which was causing "Software caused connection
  abort" errors in background isolates by accessing Android Keystore
  and blocking DNS resolution in the headless isolate
- Fix all flutter analyze warnings:
  - HeadlessTask -> HeadlessEvent (deprecated API)
  - Add final to bg task local variables
  - Add unawaited() to registerHeadlessTask call
2026-07-06 09:54:51 +01:00
Imran Remtulla
545e68df14 fix(creds): store credentials in plain SharedPreferences, not Keystore
FlutterSecureStorage uses platform channels (Android Keystore) that can block
indefinitely in a background Flutter isolate, starving the event loop and
preventing DNS resolution - causing 'SocketException: Failed host lookup' on
every background check. Revert to plain SharedPreferences like main. A one-shot
migration moves any existing Keystore-stored creds into prefs on first launch.
2026-07-06 03:55:47 +01:00
Imran Remtulla
397133dea1 fix(http): restore main's redirect range (300-399)
Dev narrowed the redirect-following range to 301-308 (excluding 303 See Other
and other valid 3xx codes). Restore the full 300-399 range matching main, so
standard HTTP redirects are followed correctly.
2026-07-06 03:30:39 +01:00
Imran Remtulla
99ffbc9cd2 fix(http): remove connectionTimeout from HttpClient creation
HttpService.createHttpClient set connectionTimeout: 30s on every HttpClient.
In the background isolate on real Android devices, DNS resolution can exceed
this when the network stack is cold, causing 'SocketException: Failed host
lookup' on every background update check. Restore main's behavior of no
timeout (null), which allows DNS to resolve at the system's pace.
2026-07-06 03:26:17 +01:00
Imran Remtulla
44d2b8c9b6 fix(bg): remove retry delay that never slept on main
The retryAfterXSeconds value was computed and logged as an expected inter-invocation
interval, and is already enforced via lastCompletedBGCheckTime. Sleeping within a
BackgroundFetch task would consume the limited execution window. Match main's
behavior: log the expected delay but don't actually sleep.
2026-07-05 21:45:46 +01:00
Imran Remtulla
f5b83b25df fix(bg): restore serial downloads in background tasks
Main's background-task path (forceParallelDownloads: true) used serial downloads
to be gentle on network/memory; dev's inverted condition ran them in parallel.
Restore main's semantics: parallel-only when forceParallelDownloads is false
AND the user's parallelDownloads setting is on, serial otherwise.
2026-07-05 21:30:30 +01:00
Imran Remtulla
466f06e64e fix(bg): keep lastCompletedBGCheckTime only when a check actually ran
bgUpdateCheck unconditionally set lastCompletedBGCheckTime after
_bgRunUpdateCheck returned, even when _bgRunUpdateCheck bailed early on the
enoughTimePassed guard. With the foreground-service interval at 15 min, every
fire reset the clock, so (lastCompleted + interval).isBefore(now) perpetually
failed and background checks never ran.

Move the enoughTimePassed guard to the outer bgUpdateCheck so the early return
skips the timestamp update, matching main where the guard and timestamp gate
the same block.
2026-07-05 19:16:57 +01:00
Imran Remtulla
3b5a5b39cd fix(settings): harden secure-storage init; stop swallowing export-dir SAF errors
- _loadSecureCache now wraps secure-storage read/write in try/catch and falls
  back to the legacy prefs value, so an unavailable Keystore/Keychain can no
  longer break settings initialization (a failure mode absent on main).
- getExportDir no longer swallows SAF exceptions or auto-disables the export
  directory after N errors; exceptions propagate as on main, so a transient
  SAF glitch can't silently drop a valid export dir.
2026-07-04 17:21:19 +01:00
Imran Remtulla
0911851ea4 fix(bg): don't run background update checks when interval is 'never'
_bgRunUpdateCheck's enoughTimePassed guard was inverted: with updateInterval == 0
('Never / manual only') it short-circuited to true and proceeded with the check,
whereas main returned early. Restore main's 'updateInterval != 0 && ...' so a
background task that fires while auto-checks are disabled skips the check.
2026-07-04 17:06:15 +01:00
Imran Remtulla
7e0803ca16 fix(download): cover progress setup with try/finally so cancel token can't leak
The download-progress setup and notify() ran before the try whose finally
clears the cancellation token, so a throw there leaked the token (blocking a
later cancel for the same app). Move the setup inside the try.
2026-07-04 16:31:53 +01:00
Imran Remtulla
2c7b2d7a5f fix(load): stop losing apps when compat migration or source lookup fails
Two changes combined to silently, permanently drop apps on load:
- App.fromJson threw a fatal error if appJSONCompatibilityModifiers failed
  (e.g. getSource throws for a URL no longer matching any source, like the
  removed Mullvad source); restore main's fallback to the unmigrated JSON.
- loadApps renamed the file to .corrupt on ANY error; restore main's behavior
  of only doing so for FormatException (genuine corruption) and otherwise
  skipping while keeping the file.

Also drop the compatVersion gating/stamp and run the (idempotent) legacy
migrations unconditionally like main, removing the gating fragility.
2026-07-04 16:31:53 +01:00
Imran Remtulla
e79a79d013 fix(notifications): widen download-notification ID space to avoid collisions
Download notification IDs were bounded to [100, 9099] via % 9000, causing
birthday-paradox collisions when many apps download concurrently (now common
since background downloads run in parallel) - colliding apps share a
notification, so progress updates and the per-notification cancel affect the
wrong one. Widen the modulo to ~2^31 so collisions are as rare as a raw
hashCode, while keeping IDs positive and clear of the fixed IDs.
2026-07-04 16:07:24 +01:00
Imran Remtulla
9b6b34ecb3 fix(install): only collect .apk from extracted bundles; clean up by resolved id
- installApkDir now matches only .apk files in an extracted bundle (matches
  main); a nested container is no longer handed to the installer
- the post-download cleanup loop keys off the resolved app id rather than the
  pre-change (possibly placeholder) id
2026-07-04 15:38:08 +01:00
Imran Remtulla
369a99a4f2 fix(download): preserve XAPK/APKS extension instead of forcing .apk
downloadFile forced any container URL (.xapk/.apkm/.apks) to a .apk filename,
so downloadApp treated the bundle as a plain APK and fed it to the APK parser
instead of extracting it (couldNotGetIdFromApk). Preserve the real container
extension; only 'attachment' content-disposition still defaults to .apk.
2026-07-04 15:38:08 +01:00
Imran Remtulla
4ac0376135 fix(install): keep downloaded APK when install is cancelled or pending
The install harness deleted the downloaded file for cancelled (null) and
already-installed/pending (code 3) outcomes; main preserved it so a retry could
reuse it without re-downloading. Only delete on success/error now.
2026-07-04 14:46:14 +01:00
Imran Remtulla
d8e298e70a fix: restore main's versionDetection default (absent -> non-standard)
Several call sites used getBool('versionDetection', defaultValue: true), so apps
missing the key (e.g. DirectAPKLink / legacy imports) were treated as standard
version detection - hiding mark-as-updated and running install-status
reconciliation main skipped. Default to false to match main's == true / != true
semantics.
2026-07-04 04:06:54 +01:00
Imran Remtulla
b4d3da4a69 fix(sources): default new apps to the last APK variant like main
getApp defaulted a new app's preferredApkIndex to 0 (first APK); main used the
last (apkUrls.length - 1). Restore main's default while still preserving an
existing app's chosen index on update.
2026-07-04 03:53:14 +01:00
Imran Remtulla
4dc1bda6e3 fix(app): let users accept a detected repository rename
Repo-rename detection still paused updates via hasPendingRepoRename, but the
accept flow/UI was removed, leaving renamed apps stuck permanently. Reinstate
acceptRepoRename and an M3-Expressive banner (built from ConnectedCard) on the
app detail page to adopt the new URL or dismiss it.
2026-07-04 03:37:29 +01:00
Imran Remtulla
0a223037c6 fix(import): import all settings again
An import whitelist with wrong key names (installerMode vs installMethod,
exportAppSettings vs exportSettings) and ~20 missing keys silently dropped most
settings on import. Restore main's behavior of importing every exported key.
2026-07-04 03:37:29 +01:00
Imran Remtulla
b97e20b676 fix(apps): stop saveApps from wiping icons/install-info
saveApps only fetched installed info/icon/name when attemptToCorrectInstallStatus
was true; the false path (background-install workaround, uninstall) nulled them
and deleted the cached icon, making app icons vanish. Restore main's behavior:
always fetch info/icon/name, gate only the install-status correction.
2026-07-04 03:37:29 +01:00
Imran Remtulla
b0fa5dd15c fix(install): resolve real package ID when adding apps with placeholder IDs
Two regressions from the immutable App refactor left newly added apps stuck
with a temporary placeholder ID (and crashed later installs on apps[tempId]!):
- isTempId only matched all-numeric IDs, but generateTempID now returns a
  sha256-hex prefix, so temp IDs weren't detected and the APK-download step
  that resolves the real package ID was skipped
- handleAPKIDChange copyWith'd its local App param (relying on the old in-place
  mutation) and returned only the File, so the resolved ID was dropped; it now
  returns the updated App and downloadApp uses it for the DownloadedApk/Dir
2026-07-04 02:58:14 +01:00
Imran Remtulla
35cd69a831 fix(install): decouple silent-install capability from background updates
canInstallSilently conflated two concepts: whether an app can be installed
without a prompt (device/app capability) and whether background updates are
enabled/allowed. This gated foreground silent installs on the background-update
toggle. Split into:
- canInstallSilently: pure capability (single APK + active installer's rules)
- canInstallSilentlyInBackground: layers the global toggle and per-app
  exemption on top, used only by the background flow

Also move the target-SDK requirement into StockInstaller, since it is specific
to the session installer and does not apply to Shizuku/external installers.
2026-07-03 23:01:55 +01:00
Imran Remtulla
b6dc396e78 feat(logs): dedicated logs page with virtualized list and floating toolbar
- Move the logs viewer from a modal dialog to its own page
- Render logs in a virtualized SliverList (was one giant Text) for smooth
  scrolling with many entries; keep text selectable via SelectionArea
- Consolidate day filter, share, clear, and jump-to-top/bottom into a single
  M3 Expressive floating toolbar docked via a Stack (no FAB entrance animation)
- Stop logging expected non-matches during source auto-detection in getSource,
  which spammed 'Source standardize error' lines on every refresh
2026-07-03 21:09:49 +01:00
Imran Remtulla
fa6b940388 feat(logs): add app/URL context to errors and log silent-install and fallback-polling decisions
- Attach the offending URL to ObtainiumError and surface it via toString
  so logs and error messages identify the app/URL involved
- Propagate URL context at source/install choke points (getApp, getSource,
  standardizeUrlWithRegex, download and install error paths)
- Log a specific reason whenever an app cannot be installed silently
- Log when install completion is detected via fallback polling instead of
  an installer return code (external installer)
2026-07-03 20:10:24 +01:00
Imran Remtulla
81a1f93b84 fix: remove dead acceptRepoRename method and unused easy_localization import 2026-07-03 16:34:02 +01:00