18 Commits

Author SHA1 Message Date
Imran Remtulla
dbf9a1047c Tweak category component 2026-07-19 17:58:15 +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
6ae36c41ff Add disable-swipe setting and always-use-phone-layout option (#3039)(#3045). Pin app-detail actions bar to bottom of scroll (#3044). Fix settings scroll on wide/trackpad devices (#3046). Fix TV navigation regressions across all dialogs, text fields, search bar, radio picker, and app list checkboxes (#3053). Deduplicate TV text-field focus pattern into public TvTextFieldFocus widget. 2026-07-12 04:21:36 +01:00
Imran Remtulla
579e77233d Apply German translations from issue, add lowerCaseUnlessLang helper (#3030) 2026-07-09 19:37:43 +01:00
Imran Remtulla
0c2226a4b2 fix(categories): don't overwrite an existing category's color on create
Creating a category whose name already exists replaced its color with a new
random one; main no-oped on duplicates. Guard the create path and inform the
user instead.
2026-07-04 14:46:14 +01:00
Imran Remtulla
6e2c2c592c fix: resolve bugs, dead code, unsafe patterns, and infrastructure issues
HIGH severity:
- Fix Completer double-completion masking CheckUpdatesException as StateError
- Delete orphaned APK files on install-already-pending status code
- Stop transient source-load errors from permanently deleting user app entries
- Await saveApps in setCategories to avoid data inconsistency
- Guard packageManager.openApp double-tap behind installed check
- Fix hashCode.abs() overflow on min int value in DownloadNotification

MEDIUM severity:
- Wrap checkETagHeader client.send in try/finally to prevent resource leak
- Remove unused loadingCompleter dead code in loadApps
- Pass additionalSettings (not {}) in apkcombo sourceRequest call
- Remove dead getRequestHeaders call in coolapk
- Add missing == true on boolean setting checks in fdroidrepo
- Zero-pad month/day in itchio date version string
- Deep-copy form items by value in izzyondroid and codeberg constructors
- Remove redundant double-reverse for sort=none in github release sorting
- Clamp negative gradient stop to valid range in app_list_tile
- Show WebView error state when main frame resource fails to load
- Defer WebView appId changes until page load completes
- Defer category pruning side effect out of build into post-frame callback
- Expand URL regex in import_export to match whitespace-delimited URLs
- Remove future-update-in-build side effect for source notes in add_app
- Add items hash to form reinit check alongside widget key

LOW severity:
- Add 'name' field to all 11 AppSource subclasses that lacked it
- Remove unused source_provider imports from app_detail_widgets, category_editor
- Rename copy-pasted bool show param to bool value on non-show setter pairs
- Delete dead proguard-rules.pro (Gson is not a dependency)
- Remove npm package files from git tracking in assets/translations
- Fix DEVELOPER_GUIDE test directory reference
2026-07-02 14:38:17 +01:00
Imran Remtulla
a8567f9005 fix: critical bugs, race conditions, unsafe casts, and code quality issues
- source_provider: fix isTempId regex (was non-raw string, never matched)
- source_provider: fix dead branch in preferredApkIndex (always 0)
- source_provider: stop bumping compatVersion on failed JSON migration
- apps_provider_updates: fix updateCheckCompleter race condition
- apps_provider: fix downloadFile delete-then-rename data loss risk
- apps_provider: fix updateInterval=0 disabling all background checks
- apps_provider_install: fix OBB in-memory loading (use file copy)
- apps_provider_install: fix getHost crash on single-segment hostnames
- apps_provider_lifecycle: fix fire-and-forget file deletes
- settings_provider: fix unsafe generic cast in _get<T>
- settings_provider: fix export dir removed on transient SAF error
- logs_provider: log cleanup errors instead of silently discarding
- sourcehut: add explicit import for intl/DateFormat
- notifications: avoid notification ID collisions with fixed system IDs
- notifications: guard importanceToPriority lookup with fallback
- notifications: use StringBuffer instead of O(n^2) string concat
- gitlab: escape standardUrl in regex; guard trailing ?/& on empty PAT
- html: stop mutating passed-in additionalSettings map
- main: capture providers before background fetch callbacks
- settings: remove catchError setState that could undo user toggle
- app: guard _closePage with mounted check
- import_export: wrap jsonDecode in try/catch; use whereType<String>
- github: add bounds checks on split()[1] index access
- generated_form_renderer: compare keys by identity, not toString()
- analysis passes cleanly (no issues)
2026-07-02 14:05:47 +01:00
Imran Remtulla
cf3caa2872 fix: squash bugs, remove dead code, deduplicate, improve error handling and naming
HIGH SEVERITY:
- Check HTTP status code before streaming download body to avoid wasted bandwidth
- Only clean up .part files in old-download cleanup, not legitimate APKs
- Fix getDefaultValuesFromFormItems crash on empty form item lists (reduce -> fold)
- Guard hosts list index access with firstOrNull / .first to prevent RangeError
- Clean up extracted APK directory on disk when package info is null
- Add 5-minute timeout to foreground busy-wait loop
- Fix isEnglish() false positive by falling back to false instead of tr('and')=='and'
- Use dart-define APP_VERSION from build.sh instead of hardcoded kPackageVersion

MEDIUM SEVERITY:
- Fix GitHub /latest endpoint URL reconstruction (split/join corrupting query params)
- Add try-catch for corrupt JSON in categories getter
- Check arch-filtered APK list emptiness and throw NoAPKError if empty
- Cache combinedAppSpecificSettingFormItems per AppSource instance
- Replace fragile didUpdateWidget condition with setEquals
- Replace putIfAbsent with direct assignment in _resetEntrySelections
- Remove double notify() call in import path (saveApps already calls notify)
- Added unawaited wrapper for async log calls in rethrowOrWrapError

LOW SEVERITY:
- Remove unused Logger? field from AppsProvider
- Remove redundant _obtainium prefix from private constants
- Remove redundant recursive:true on File.deleteSync
- Remove redundant .toList() copy on Uint8List write
- Make sourceUrl final (use const obtainiumUrl)
- Remove duplicated _updateSourceNote inline in add_app.dart build()
- Fix logs clear message showing literal 'null'
- Remove duplicate // ignore: implementation_imports comment
2026-07-02 13:25:29 +01:00
Imran
f85d9206c7 Rearchitect (#3002)
* architecture: consolidate and simplify

- Collapse 14 directories into 4 (app_sources, components, pages, providers)
- Replace DI container with inline construction in main.dart
- Fold page controllers back into their pages (one file per screen)
- Merge small files into their natural homes (removing 40 files)
- Standardize error handling across all 28 app sources (rethrowOrWrapError)
- Make App immutable with copyWith() replacing deepCopy()
- Replace globalNavigatorKey with go_router
- Add error boundaries, source health monitor, typed settings
- Eliminate all silent catch blocks
- Add tests and CI pipeline
- Extend lint rules

97 files → 56 files. 0 errors, 4/4 tests passing.

* fix: remove unused go_router, restore internal navigation

- Drop go_router (ShellRoute broke tab switching and disabled the
  tablet/TV two-pane layout); revert to MaterialApp + navigatorKey
- Move appNavigatorKey into main.dart; delete router.dart
- Remove HomePage.child branch so the internal switcher drives the UI
- Fix dead 'Restart' button in the error fallback screen
- Remove TypedSettings []= mutation backdoor
- Persist clamped preferredApkIndex to the in-memory app map
- Use settings.getBool('trackOnly') instead of raw map access

* fix: remove dead rethrowOrWrapError, add missing import, update stale comments and docs

* fix: satisfy lint rules, wrap fire-and-forget futures, drop unused CI

- Fix use_build_context_synchronously via mounted/context.mounted guards
- Wrap deliberate fire-and-forget futures in unawaited()
- Document static app-lifetime event bus for close_sinks
- Apply dart format and dart fix across the codebase
- Drop avoid_catches_without_on_clauses (noisy in source scrapers)
- Remove test.yml CI workflow
- Rename test/services to test/providers

* fix: show real HTTP error messages, remove source health monitor and dead event-bus code

- Bypass deferred localization for HTTP_ERROR so the real reason phrase/status is shown instead of a generic 'unexpected error'
- Remove SourceHealthMonitor entirely (per-instance state made it ineffective for background checks)
- Simplify save-notification bus to StreamController<void>; drop unused AppRepositoryEvent/AppRepositoryEventType and events getter
- Scrub stale docs (SourceHealthMonitor, event type, deleted test.yml workflow)

* fix: preserve installedVersion on import, correct stale theme doc reference

* fix: type logger as Logger?, simplify ErrorWidget builder, document _sentinel, resolve TextDirection conflict

- Change dynamic logger to Logger? in AppsProvider for type safety (the
  field was unused but the contract should be explicit).
- Replace MaterialApp wrapper in ErrorWidget.builder with a minimal
  Directionality + Scaffold; MaterialApp is wasteful for a one-shot
  error screen.
- Add a doc comment explaining the _sentinel pattern used by
  App.copyWith to distinguish unset from explicitly-null nullable fields.
- Hide TextDirection from easy_localization (which re-exports intl's
  conflicting TextDirection enum that lacks ltr/rtl) so the Flutter
  dart:ui TextDirection.ltr resolves correctly.

* fix: register SourceForge, restore Palestine banner, drop dead code and tests

- Register the implemented SourceForge source in _buildSources() so the
  README's supported-sources list is accurate.
- Restore the Support Palestine banner in README.md (unintentionally removed).
- Remove unused ConfigProvider, CredentialHealth, getCredentialHealth, and
  the never-injected _configProvider path from settings_provider.
- Remove unit tests and their test-only dev dependencies.
2026-07-02 01:58:45 +01:00
Imran Remtulla
6d8dfe33a8 chore: standardize code quality, logging, docs, and dependencies
- Replace print/debugPrint with LogsProvider across 11 files
- Convert all Future.ignore() to unawaited() across 7 files
- Fix import ordering (dart: first) in apps_provider, source_provider, coolapk
- Remove unnecessary late keywords (apps, githubstars)
- Add const where applicable (EdgeInsets, TextStyle)
- Standardize mounted → context.mounted across StatefulWidget pages
- Reduce pickedSource! null assertions in add_app (14→2)
- Break up 320-line _fetchReleaseDetails into 5 methods in github.dart
- Extract share callbacks from bottom sheet in apps.dart
- Replace Provider.of with context.read in ui_widgets
- Remove unused dart:ui import, fix clampDouble usage
- Add TypeError catch in App.fromJson() for corrupt JSON
- Preserve original error details in 8 catch blocks
- Remove redundant lint ignores (unnecessary_non_null_assertion, invalid_return_type)
- Update DEVELOPER_GUIDE for theme.dart, app_info_dialog, logging, naming
- Upgrade cross_file transitive dependency
2026-07-01 12:33:26 +01:00
Imran Remtulla
f514a37a31 fix: squash bugs, simplify code, clean comments, update docs
Bugs fixed:
- main.dart: await bgUpdateCheck in onStart (was fire-and-forget)
- github.dart: fix wrong array index in verifyLatestTag fallback (releases[0] -> releases[releases.length-1])
- category_editor: add didUpdateWidget to sync stale selected state
- apkpure.dart: handle null version_name in fold; fix stayOneVersionBehind broken without fallbackToOlderReleases
- neutroncode.dart: rewrite date parsing to handle month-first formats correctly; use DateTime.tryParse
- huaweiappgallery: replace assert with runtime fallback for overridden hosts
- uptodown.dart: fix dead second date format (try/catch per format)
- vivoappstore.dart: add missing ? null-safe operator on json['data']
- app.dart: disable remove button when app is null; add mounted check after await
- custom_errors.dart: override CheckUpdatesException.toString() to show actual errors
- InstallError: remove broken .substring(7) on enum name
- Google verification warning: remove year-2026 gate
- apps.dart: fix catchError return type mismatch; add missing SilentUpdateAttemptNotification cancel
- add_app.dart: remove dead unreachable ternary branch

Simplifications:
- apps_provider_install: replace Map<Object?,Object?> with typed _InstallResult class
- source_provider: flatten _resolveAppId 7-deep ?? chain into early returns; defensive copy additionalSettings before mutation
- settings_provider: migrate legacy exportSetting bool->int in init, simplify getter
- apps_provider: decompose 4-level nested ternary in bgUpdateCheck
- izzyondroid: replace try/catch flow control with host-based branching
- githubstars: create SettingsProvider once per import, not per page
- direct_apk_link: set versionDetectionDisallowed, decouple from hardcoded type check
- main.dart: guard against duplicate listener registration
- settings.dart: move init out of build() into postFrameCallback
- apps.dart: inline one-line _showMoreOptions wrapper
- github.dart: fix prerrelsSkipped typo; add doc to _fetchReleaseDetails
- apkmirror.dart: remove redundant tautology in null check

Comments & docs:
- Add file headers to custom_errors, theme, main_fdroid, native_provider, motion, ui_shapes
- Condense verbose comments in generated_form, itchio, liteapks
- Fix custom_app_bar inaccurate collapse comment
- Move coolapk maintenance note to class-level doc
- Fix html.dart triple negative boolean expression
- Add doc to loadTranslations; add comments to _ObtainiumState vars
- Fix custom_app_bar inaccurate comment
- Add TODO comments to theme.dart deprecated year2023 usage
- Restore // ignore: comments for known analyzer false-positive
- README: add SourceForge, explain Track-Only, fix badge URL, mention F-Droid flavor
2026-07-01 11:28:07 +01:00
Imran Remtulla
3579891889 fix: source robustness, APKCombo, download-progress UI, and perf
App sources:
- Harden sources against malformed/changed responses (null, parse, index
  and empty-version guards): rustore, tencent, apkmirror, gitlab, aptoide,
  fdroid(repo), sourcehut, huaweiappgallery, itchio, izzyondroid,
  telegramapp, uptodown, vivoappstore, etc.
- APKCombo: fix and enable (issue #341) - unwrap "/r2?u=" links to the
  signed Cloudflare R2 URL, drop the Host header that 403'd the R2
  download, take one build per variant, and parse the release date;
  register the source.
- Remove orphaned Mullvad source (never registered).
- F-Droid version-regex filter selects the newest match; fix Jenkins
  changelog link; GitLab reuses one GitHub helper instance; GitHubStars
  reuses one GitHub instance.

UI / behaviour:
- Fix download progress bar never updating on the app detail page and the
  apps list (provider select on an in-place-mutated AppInMemory never
  fired a rebuild).
- Fix single-category gradient crash, dead "installing" tile state, silent
  swipe-install errors, deep-link dedup, mounted/context guards in
  import/export and add-app, and ExportSection future caching.
- generated_form: GeneratedFormSwitch.disabled is now applied; clone()
  preserves disabled and autoCompleteOptions.

Performance:
- Cache SettingsProvider.categories (was rebuilding a Map + re-parsing JSON
  on every access, over-rebuilding category selectors).
- Memoize the flattened combined form items used by the per-app JSON
  migration at load.

Tooling / config:
- build.sh: set -euo pipefail; make the --build-id=none workaround
  idempotent and glob-safe.
- pubspec: branch-track git dependencies.
- Remove unused config_keys.dart (consolidate on raw setting keys).
2026-06-29 15:04:30 +01:00
Imran Remtulla
0bae4251cd fix: bugs, performance, and code quality improvements across codebase
- Fix GitHub rate limit time calculation (was ignoring current time)
- Fix HTTP client leaks on redirect exhaustion and early download returns
- Drain StreamedResponse before closing client in checkETagHeader
- Fix null assertion crash in checkUpdate when app ID not found
- Remove duplicate null check in getObtainiumHttpError
- Replace empty catch blocks with typed exceptions and comments
- Fix potential null dereference in AppFilePicker and CategorySelector
- Fix category sort comparator transitivity violation for null values
- Call LogsProvider.close() on app dispose (was never called)
- Replace print() with debugPrint() for consistency
- Replace sync I/O with async variants (readAsString, writeAsString, rename, list, stat)
- Convert installedAppsData list scan to O(1) map lookup in loadApps
- Pre-group apps by category to avoid O(n*m) per-category scans
- Use Set for alreadyAddedUrls to enable O(1) contains lookups
- Add sourceType cache to AppInMemory to avoid re-detecting source on filter
- Use context.select instead of context.watch to reduce unnecessary rebuilds
- Extract interval slider to dedicated widget to avoid full settings page rebuilds
- Extract _FormSwitchRow to prevent switch rebuilds on text field changes
- Add cacheWidth/cacheHeight to AppIcon image decoding
- Batch URL import with concurrency limit of 4 instead of sequential
- Guard auto-export timer callback with _disposed check
- Defer _lastBackgroundSave clear until loadApps completes
- Simplify findExistingUpdates complex nested logic
- Refactor 7 app sources to use standardizeUrlWithRegex helper
- Replace 25+ magic numbers with named constants
- Clean up late keyword on immutable fields in App and APKDetails
2026-06-29 11:04:26 +01:00
Imran Remtulla
e436405a1c robustness + perf: defend against OOM, crashes, rebuild amplification, and stale state
- apps_provider_install: stream-decompress gzip tarballs >64MB to temp file
  instead of loading into memory; async readAsBytes for APK sharing
- html.dart: null-guard res.request.url fallback; cap intermediate link
  depth at 10
- github.dart: null-check regex matches before .start/.end assertions
- home.dart: use context.select for isTV to avoid full-shell rebuilds on
  every settings change
- app_list_tile.dart: replace MediaQuery.sizeOf with LayoutBuilder;
  guard category gradient division-by-zero for single-category apps
- add_app.dart: cache FutureBuilder future so it does not re-fetch on
  every rebuild
- import_export.dart: add didUpdateWidget to SelectionModal to reset
  stale entrySelections when parent rebuilds with new entries
- generated_form.dart: dispose old TextEditingControllers before re-init
- logs_provider.dart: make db connection static (shared across instances);
  add close() method
- fdroid.dart: guard changelog substring against UTF-16 surrogate splits
- ui_widgets.dart: await Clipboard.setData; change showConfirmDialog
  content from Object? to Widget?; use consistent TextButton type in
  HighlightableButton; add semanticLabel to EmptyState icon
- app.dart: await _getUpdate in RefreshIndicator; show loading spinner
  in WebView instead of empty Container
- analysis_options.yaml: enable stricter lint rules
2026-06-29 01:48:46 +01:00
Imran Remtulla
4aa84c35c1 UX/a11y: settings loading state, APK-origin opt-out, link/swatch semantics
- Settings: show a loading spinner instead of a blank screen while prefs load
- APK-origin warning dialog: add a "don't show again" checkbox (sets
  hideAPKOriginWarning) so the choice isn't buried in settings
- Accessibility: announce LinkText as a link; exclude decorative AppIcon images
  from semantics; mark category color swatches as selectable buttons with their
  selected state; expose the category long-press-to-edit via a tooltip and a
  semantic long-press action
- Settings: extract the ~85-line update-interval slider tile into
  _buildIntervalSliderTile to shrink the build() method
2026-06-28 17:56:18 +01:00
Imran Remtulla
e2a24b01a3 Fix correctness bugs: download hang, allowInsecure, leaks, retries, crashes
- downloadFile: break out of the partial-download wait loop when the temp file
  disappears (a concurrent download finished it), instead of polling forever
- downloadFile: propagate allowInsecure to the actual APK download (previously
  only the header request honored it, so insecure downloads failed)
- checkPartialDownloadHash: close the HttpClient (was leaked on every call)
- bgUpdateCheck: actually wait retryAfterXSeconds before retrying so rate-limited
  hosts aren't hammered
- native_provider: await FontLoader.load() before marking the system font loaded
- Guard fragile source parsing against crashes (LiteAPKs empty array, APKMirror
  missing ' by ', GitHub null/non-string tag_name)
- Add mounted guards before setState across async gaps (category editor, logs)
- GeneratedForm: dispose TextEditingControllers (were never disposed)
- NotificationsProvider: guard against double initialize()
- main: run the launch-by-notification check once instead of on every build
  (was re-showing the same dialog)
2026-06-28 17:35:47 +01:00
Imran Remtulla
72dd2cddf0 Decompose large build() methods, optimize providers, fix bugs, and improve accessibility
- Extract the god build() methods in the apps/app/add_app pages into focused, named helper methods
- Memoize AppSource instances and SettingsProvider platform calls; drop a duplicate source-config lookup
- Batch loadApps notifications, replace the busy-wait with a Completer lock, and debounce auto-export
- Add a compatVersion stamp so already-migrated apps skip one-time legacy migrations
- Only rebuild on integer download-progress changes; harden list/detail cache signatures with Object.hashAll
- Fix dead select-all branches, add mounted guards, and clone source-owned form items to avoid mutation leaks
- Expose swipe install/remove and icon long-press to screen readers; add a list progress bar and URL copy tooltip
- Move flutter_lints to dev_dependencies and apply dart format across the tree
2026-06-28 03:40:45 +01:00
Imran Remtulla
db5e380698 M3E round 2 updates 2026-06-26 02:35:40 +01:00