- Add optional parameter to ToggleTile (default false)
- Add optional parameter to GeneratedForm (default false)
- GeneratedFormModal now passes to its inner GeneratedForm
- Set on direct AlertDialog + GeneratedForm in import_export.dart
- Reduce non-tile mode row spacing from 24 to 8
_generateFormState._computeItemsHash included item.value for
switches and dropdowns (only text fields were excluded). On the
add-app page, _buildSourceSpecificForm mutates item.value from
additionalSettings on every rebuild, so any value change via a
related onValueChanges callback would alter the hash, trigger
_initFormData, dispose all TextEditingControllers, and kill the
keyboard.
Hash now uses only key + runtime type for all item kinds -- form
re-initialization is driven by widget key changes (source
switch), not value fluctuations.
- 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
- 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
notifyFormChange() did not call setState(), so subform add/delete button
presses on the app detail (edit) page mutated values internally but never
triggered a rebuild of _buildSubForm(). The add_app page worked because
its parent onValueChanges callback called setState() externally.
- add_app.dart: populate form item values from additionalSettings before
passing to GeneratedForm. Without this, the items hash changes on every
rebuild (fresh clones have default values, not user data), causing
_initFormData to reset user entries. This is why add/delete buttons
on dynamic form items (Intermediate Link, Request Header, etc.)
appeared to do nothing.
- settings_widgets.dart: replace Material.shape (RenderPhysicalShape)
with ClipPath + custom _TileClipper + plain Material. RenderPhysicalShape
does not support intrinsic sizing, causing tiles in CustomScrollView to
render at zero height. ClipPath + Material uses RenderDecoratedBox which
properly measures content.
The _initDropdown method still had formItem.belowWidgets as List<Widget>
after the _buildHelpSuffixIcon refactor. The helper now takes List<dynamic>
and casts internally via .cast<Widget>().
The model stores belowWidgets as List<dynamic> but the helper parameter
was typed List<Widget>. Changed _buildHelpSuffixIcon parameter to
List<dynamic> and use .cast<Widget>() when passing to showHelpDialog.
- _fieldDecoration(): shared InputDecoration builder that handles tileMode
border/fill config, used by both text fields and dropdowns
- _buildHelpSuffixIcon(): shared help URL / help-dialog icon builder
- add_app.dart: extract _buildSourceSpecificForm() from immediately
invoked closure pattern in _getAdditionalOptsCol
Wrap the subform entries Column in Padding(vertical: 8) so panel content
has breathing room inside its SettingsTile, matching the BG check button
and add-app page button spacing.
A. Tile wrapper: Replace inline Material in tileMode form loop with
SettingsTile — eliminates duplicate shape/clip/color wiring.
B. Haptic helper: Extract hapticSwitchOnChanged() shared between
_FormSwitchRow and SettingsToggleRow.
C. Tile joining: Use shapeSettingsTiles() in tileMode form loop instead
of inline isFirst/isLast/positionalTileShape logic.
D. ConnectedCard -> SettingsTile delegation: ConnectedCard now delegates
to SettingsTile internally, unifying the tile rendering path (switches
from standard rounded corners to RoundedSuperellipseBorder).
Dependency clean-up:
- Move showHelpDialog from ui_widgets to settings_widgets to break
circular import (ui_widgets now imports settings_widgets for D).
- Remove unused theme.dart and ui_widgets imports from form renderer.
SettingsTile default padding and SettingsToggleRow contentPadding now use
16px horizontal, matching the InputDecoration contentPadding used by field
tiles (dropdowns, text fields) on the same settings page.
- SettingsToggleRow: contentPadding horizontal 8 -> 12, matching default
SettingsTile padding so toggle labels align with other settings text
- ConnectedCard: default padding EdgeInsets.all(16) ->
EdgeInsets.symmetric(horizontal: 16, vertical: 20) for more breathing room
around text fields and buttons inside cards
TileMode:
- All tiles now use surfaceContainerLow background (was mixed)
- Non-field tiles: vertical padding unified at 8px (was 4px for switches,
8px for subforms, 12px horizontal for subforms -> 16px)
- Removed redundant switch/subform type check in padding logic
Non-tileMode:
- Uniform 24px gap between all rows (was 16px after switches, 28px after
other types)
Text styles:
- Toggle label: use textTheme.bodyLarge (was default Text style)
- SubForm header: use textTheme.bodyLarge.copyWith(bold) (was bare
TextStyle with no textTheme token)
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
- SettingsToggleRow: remove full-row tap ink, unify both paths to
non-tappable ListTile + Switch in trailing. Matches _FormSwitchRow
in generated forms — only the Switch itself is interactive.
- ActionListTile: add optional borderRadius param for ListTile.shape
to match container card corners for proper ink clipping.
- LinkText: add InkWell borderRadius (circular 4) to contain splash.
- Replace raw Material in add_app source note card with ConnectedCard.
- Replace raw Card in ExportSection with ConnectedCard.
- Add ListTile.shape (connectedTileBigRadius) to color picker and
external installer tiles for matching ink clipping.
- Standardize SettingsTile paddings: remove horizontal 8px overrides
on install method, external installer, group-by, and manual BG check
tiles — use default horizontal 12px. Theme/sort order keep 16px.
- GeneratedForm labels now always call tr() at render time (7 locations)
preventing stale/raw translation keys when the item cache is built
before locale initialization or persists across locale changes.
- Remove unused SwitchListTile import (now unused in SettingsToggleRow).
Install/update was launched with the local widget context, so if it unmounted
during the flow (a list tile rebuilt on re-sort, backing out of the detail
page, later apps in a mass install) the APK picker and origin-warning dialogs
were silently skipped - installing the default APK without asking. Pass
appNavigatorKey.currentContext (the root navigator, valid app-wide) like main.
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.
notifyFormChange hardcoded isBuilding=false, so every '!isBuilding' guard
(settings sinks, add-app handlers, export filter) ran on form mount - e.g.
opening Settings wrote source-config values back to prefs. Pass isBuilding=true
on the initial/post-reinit callback and propagate it through sub-forms.
GeneratedFormModal now always setState (safe in the post-frame callback), so its
OK-button validity stays correct.
didUpdateWidget reinitialized the form (recreating controllers and focus
nodes) whenever the items hash changed, and that hash folded in each field's
live value. Since the parent mirrors the typed value back into the field's
value, every keystroke changed the hash and dropped keyboard focus. Exclude
text-field values from the hash; structural resets still reinitialize via a
changed widget key.
- Redesign the update button as an M3-expressive filled icon button
with a superellipse shape
- Remove the cancel button from list entries during download
- Widen the version/changelog/progress trailing section
- Remove all tarball size limits (500MB cap deleted)
- Remove redundant 'late' keywords from AppsFilter fields that have constructor defaults
- Add logging to previously-silent catch in download size probe
- Fix DEVELOPER_GUIDE.md: remove nonexistent AppListCategorySection, fix showGeneratedFormModal reference, correct tarball docs, add installers/ and external_install_bridge.dart to directory tree, fix double backtick fence
- Update pubspec.yaml TODO comments for all git-fork dependencies to be more specific
Critical fixes:
- Remove setTrustedCertificatesBytes which replaced the entire TLS trust store
- Fix late final async init race causing LateInitializationError crashes
- Fix infinite recursion in ObtainiumError.message for MULTI_ERROR code
High fixes:
- Fix identical() always returning false on badge icon (update count never shown)
- Clone form items before mutation to prevent cross-contamination between sources
- Log transient errors before removing apps on load failure
- Await uninstallApp calls instead of fire-and-forget
- Revert optimistic install on background workaround failure
- Log SAF errors before silently disabling export directory
Medium fixes:
- Remove dead GeneratedForm.fromDefinitions constructor and unused fields
- Replace Shizuku-specific param with generic installOptions map in Installer interface
- Add importable-keys allowlist to prevent arbitrary SharedPreferences writes
- Guard against stale Shizuku permission check callbacks with sequence counter
- Prevent ReceivePort leak in listenForDownloadCancelFromMain
- Add 500MB tarball size guard to prevent OOM on large archives
- Simplify ReDoS-vulnerable URL regex in html.dart
- Fix TOCTOU race between existsSync and length calls
- Log rename failures instead of empty catch blocks
- Use safe null-aware defaults in setFormValuesFromMap
- Replace the 'Group by category' toggle with a 'Group by' control
(None/Category/Source); sources are listed alphabetically. Migrates the
old groupByCategory setting.
- Give grouped entries small gaps so they read as distinct positional
tiles, and make the multi-select/pinned highlight follow each tile's
positional (or card) corners instead of the default radius.
- Make the multi-select 'more' FAB extended to match the Add FAB.
- Expose FAB shadow via floatingActionButtonTheme elevation in theme.dart.
- Use an extended (icon + label) FAB for the primary Add action in the
bottom layout; keep a compact FAB in the navigation rail so it fits.
- Add tactile feedback (respecting the existing setting) to interactions
that lacked it: download cancel buttons, Add/more/view FABs, list-tile
update and swipe install/remove, settings segmented buttons, and all
settings toggles (centralized in SettingsToggleRow).
- Drop manual strokeWidth overrides so inline spinners inherit the themed
Material 3 look.
Add a cancel button for in-progress downloads (in the app list tile, the
detail page, and the download notification) and surface download sizes:
the expected size before starting (best-effort probe) and live
received/total progress in the UI and notification.
- Route notification Cancel taps via the FLN background isolate to the main
isolate (IsolateNameServer/SendPort); gated off for background-isolate
downloads whose cancel token isn't reachable.
- Share per-app download state (progress + bytes) across AppInMemory copies
so UI listeners keep updating after saveApps replaces the map entry.
- Treat cancellations silently and keep the .part file for later resume.