Skip to content

fix(shell): restore the native iOS glass host with Telegram's tint - #278

Closed
TiaraBasori wants to merge 2 commits into
iebb:masterfrom
TiaraBasori:fix/restore-native-ios-glass-tabs
Closed

TiaraBasori wants to merge 2 commits into
iebb:masterfrom
TiaraBasori:fix/restore-native-ios-glass-tabs

Conversation

@TiaraBasori

@TiaraBasori TiaraBasori commented Oct 25, 2026 •

Copy link
Copy Markdown
Contributor

Change

Enabling the liquid-glass bottom bar on iOS still showed no glass. The root cause had two layers:

  1. The tint was invisible by construction. The native surface tinted UIGlassEffect with the bar's own surface colour (navBar). In the light palette navBar == background == 0xFFFFFF, so that tint is white over white — any low alpha composites to no pixel, and pushing the alpha high enough to see (0.56/0.72, tried in fix(shell): make the iOS glass bottom bar visibly glass #253) turns the tint into a fill that hides the material's refraction. The bar reads as a flat translucent slab either way: "完全没有液态玻璃效果".
  2. The host had been retired. fix(shell): draw the liquid glass bottom bar in Flutter on iOS #259 replaced the native surface with the Flutter BackdropFilter bar on iOS too. That bar is a credible frosted capsule, but it is not the system Liquid Glass material — no refraction, no specular response, and its tint problem is the same white-on-white one.

What Telegram iOS actually does (read from GlassBackgroundComponent in TelegramMessenger/Telegram-iOS, first-party code): its glass surfaces tint UIGlassEffect with plain white at 0.10 in light and 0.025 in dark — never the panel's surface colour. The visible edge of iOS 26 glass comes from clamping the backdrop's sampled luminance into a narrow band (lumaMin/lumaMax = 0.8/0.801 light, 0/0.15 dark), which Telegram implements by swizzling the private _UISDFBackdropView class (backdropLayer:didChangeLuma:) behind obfuscated class-name strings. That path is not shippable here (App Store Guideline 2.5.1 binary scans; the class name embeds a UIKit build hash that breaks silently on OS updates). The public-API alternative this PR uses: keep the tint a whisper, and draw the edge — the same Flutter hairline contour + drop shadow #259 added, now wrapping the native surface.

This PR restores the reviewed public-API host (#216 architecture) with Telegram's tint:

  • NativeGlassSurface returns (Swift + Dart wrapper): UIGlassEffect(style: .regular) in a non-interactive UIVisualEffectView on iOS 26+, UIBlurEffect(systemMaterial) before, overrideUserInterfaceStyle from the effective app palette, re-registered in AppDelegate and the four project.pbxproj entries.
  • Tint = nativeGlassTint(): white ARGB 0x1AFFFFFF light / 0x06FFFFFF dark, with a source-pinned policy test (red-verified against the old surface-colour tint).
  • Fixed the documented effect bug while restoring: a tint change installs a fresh UIGlassEffect (mutating the installed effect is a no-op assignment — the effect resolves its tint when the material is built).
  • _GlassCapsuleFrame draws the capsule's silhouette (0.6pt contour at 5.5%/34% black + the shared drop shadow) around the native surface, so the bar keeps a visible edge on white without any private UIKit.
  • Geometry, tabs, selection pill, badges, and every gesture stay app-owned in Flutter above the surface — one layout source for the compact phone bar and the tablet split sidebar column (the reason fix(glass-bar): keep the tablet tab indicator over its icon and label #193/fix(glass-bar): draw the app glass bar in tablet split shells #210 chased the system UITabBar away is untouched: nothing system-drawn positions against tab slots).
  • glass_bottom_bar_geometry_test.dart is now the non-iOS host suite (onAndroid); the restored native_glass_bar_geometry_test.dart pins the iOS native host: split column, home-indicator containment, narrow phone, RTL channel flag, and the host-mirror rect (native surface == Flutter capsule).

Affected platform: iOS with the liquid-glass setting on. Every other target is byte-identical (same Flutter bar, same code path).

Telegram API Terms classification

  • Compliant/unchanged: this change complies with applicable Telegram API Terms, preserves Telegram's basic functionality, and does not introduce behavior covered by section 1.4.
  • Section 1.4 exception: the entire feature is guarded by allowTelegramTosViolations (I_DONT_FUCKING_CARE_ABOUT_TOS), disabled in official builds, and available only in an explicitly enabled self-build.
  • Disallowed or unresolved: this PR must not be approved or merged until its classification and implementation are resolved.

Reason and affected Telegram behavior:

Presentation only. No message data is read, no request changes, no status Telegram shows is altered. The change swaps how the already-existing opt-in bottom bar renders its background on iOS; with the setting off the classic bar is unchanged.

Basic checks (every PR)

  • I reviewed the diff for correctness, unintended behavior, secrets, and unrelated changes.
  • Formatting passed, or I recorded why it is inapplicable: dart format --output=none --set-exit-if-changed lib test integration_test test_driver — the touched Dart files report 0 changed after formatting.
  • Analysis passed, or I recorded why it is inapplicable: flutter analyze — only the pre-existing config/secrets.dart env-file errors remain (uncommitted env file, copied from secrets_example.dart locally); zero issues in the touched files.
  • Relevant tests passed, or I recorded why they are inapplicable; name the commands and test cases below. Quality CI runs flutter test --concurrency=4.
  • I checked affected build/platform paths and applicable localization/generated files; list the evidence below.
  • I completed the Terms classification above and verified that official builds cannot enable section 1.4 exceptions.

Commands, results, platform/build/UI evidence, and reasons for inapplicable checks:

Flutter 3.44.2 (/tmp/flutter-3.44.2), serial (--no-pub -j 1):

  • test/native_glass_surface_test.dart → 2/2 (channel contract + tint policy; red-verified: injecting the old surface-colour tint fails both tests exactly).
  • test/native_glass_bar_geometry_test.dart → 7/7 (native host mount, home-indicator containment, split column, narrow phone, RTL, host-mirror rect; red-verified: disabling the iOS native branch fails 3+ tests).
  • test/glass_bottom_bar_geometry_test.dart + test/liquid_glass_bottom_bar_test.dart → 16 pass, 2 conditional skips (visual captures, captureGlassReview not set).
  • test/main_tab_motion_test.dart + test/bottom_bar_layout_test.dart + test/bottom_bar_tap_target_test.dart → 30/30.
  • test/topic_split_navigation_test.dart + test/appearance_theming_test.dart + test/music_mini_player_tablet_test.dart → 42/42 (neighbouring split-shell surfaces).
  • Mutation checks: tint revert → both tint tests fail (Expected: <452984831>); host-branch kill → native suite fails ("exactly one matching candidate"); restored → green.

Not verifiable on this machine: no Xcode/macOS host here, so the Swift file is compile-checked by inspection and source-pinned tests only. The construct set is unchanged from #216 (which compiled on a hosted macos-26/Xcode 26.6 runner in fork run 39688921371); the only Swift-side deltas are the fresh-effect-instead-of-mutate branch and no new API. No localization or generated file is touched (python3 translations/tools/check.py not re-run: no string or asset changes).

Line counts (git show --numstat HEAD): 118 0 ios/Runner/NativeGlassSurface.swift (new), 106 0 lib/app/native_glass_surface.dart (new), 92 5 lib/app/main_tab_view.dart, 69 58 test/glass_bottom_bar_geometry_test.dart (host split), 427 0 test/native_glass_bar_geometry_test.dart (restored), 188 0 test/native_glass_surface_test.dart (restored + policy), 3 0 ios/Runner/AppDelegate.swift, 4 0 ios/Runner.xcodeproj/project.pbxproj.

Device check for this change (glass on, iOS 26): the capsule under the tab row reads as system Liquid Glass — content scrolling behind it refracts/bends at the capsule edges, with a faint white brightening and the drawn hairline edge; in dark mode the material is near-clear with the heavier contour. On iOS 25 the capsule is a system material blur with the same tint and edge. Flipping the app appearance repaints through the same surface (no re-create). Tabs, badges, and long-press clear-unread stay tappable in both the compact bar and the tablet sidebar column.

Section 1.4 exception checks

Complete every item for an exception. For a compliant/unchanged PR, write Not applicable here.

Not applicable.

  • All UI, routes, commands, callbacks, background work, TDLib/native effects, and registrations use the compile-time guard; a runtime preference alone cannot enable the feature.
  • With the flag omitted, the feature is absent and ordinary Telegram behavior remains intact, including with old saved settings.
  • With --dart-define=I_DONT_FUCKING_CARE_ABOUT_TOS=false, the feature is absent and ordinary Telegram behavior remains intact.
  • With --dart-define=I_DONT_FUCKING_CARE_ABOUT_TOS=true, the feature works through the guarded entry points in a self-build; I included relevant test and build/UI evidence.
  • Official release, nightly, TestFlight, and store builds keep the flag false and have no option that enables it. All store versions remain compliant with applicable Telegram API Terms.

Guard locations and evidence for each flag variant:

Not applicable.

Agentic reviewer confirmation

Agentic code reviewer only: record this confirmation in the PR review or check these boxes before approval.

  • I independently confirmed the Terms classification and inspected all required guards and evidence.
  • Applicable checks and Quality CI pass; no unresolved classification or unguarded section 1.4 behavior remains.

Follow-up (cfed7d6)

The pressed-tab mask, ripple, and selection pill were 9px rounded rectangles inside the stadium capsule — a visible shape mismatch at both ends. #193 chose 9px to keep a stadium indicator from clipping the label row's corners; the glass row has been icon-only since #261, so the constraint is gone and the shapes are now concentric stadiums (row inset 5px, arcs on the same centres; icon block clears the arc in the narrowest tablet slot). Coverage/geometry tests updated accordingly (containment measured through the clamped RRect at the bar's real fixed row height; capsule probes pick the outermost stadium now that slot ripples share the pill radius). Red-verified: reverting the indicator to 9px fails the stadium assertion (Expected 999.0, Actual 9.0).

Re-run at this head: liquid_glass + glass geometry + native suites + motion + tap-target = 52 passed, 2 conditional skips; flutter analyze clean; dart format 0 changed.

iOS showed no liquid glass because the native surface had been retired for
a Flutter BackdropFilter bar, and before that its tint was derived from the
bar's own surface colour: in the light palette navBar equals the background
white, so any alpha either composites to nothing or, pushed high enough to
see, becomes a fill that hides the material's refraction — the exact 'still
no glass' report.

The tint now follows Telegram iOS (GlassBackgroundComponent): plain white
at 0.10 in light and 0.025 in dark, strictly a whisper so the material
stays visible. The capsule's edge, which Telegram derives by clamping
backdrop luminance through a private UIKit class, is drawn instead by the
same Flutter hairline contour and shadow the Flutter glass bar uses, so no
private API or swizzle ships. The surface host is the reviewed public-API
NativeGlassSurface (UIGlassEffect on iOS 26+, UIBlurEffect before), with
the documented no-op fixed: a tint change installs a fresh effect because
UIGlassEffect resolves its tint when the material is built. Geometry,
tabs, pill, and gestures stay app-owned in Flutter, so the compact phone
bar and the tablet sidebar column share one layout in either host; the
restored native geometry suite pins the split column, home-indicator
containment, RTL, and the host-mirror rect.
@chatgpt-codex-connector

chatgpt-codex-connector Bot commented Oct 25, 2026 •

Copy link
Copy Markdown

Codex Review Summary

This comment shows the latest Codex review activity on this pull request.

Review Status Commit Review trigger
📝 Code Review ⚠️ Failed 2026-10-25T16:24:40.833596Z
We couldn’t complete this review. Comment @codex review to try again.
45d258e PR opened
ℹ️ About Codex in GitHub

Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you

  • Open a pull request for review
  • Mark a draft as ready
  • Comment "@codex review" or "@codex security review".

Codex reacts with 👀 while any review is running, comments if it has suggestions, and reacts with 👍 once all reviews finish with no findings.

@github-actions

github-actions Bot commented Oct 25, 2026 •

Copy link
Copy Markdown
Contributor

Automated PR review

Head: cfed7d630e4a0952fbabba8f51d5da98e8b69588 · master: a64d33a5da33b9540af8e791f14682b17d2952ee
LLM recommendation: needs_human. This is not a GitHub approval.
Auto-merge: blocked. Only iebb can authorize the current head.

Review unavailable: Free OpenRouter review unavailable (HTTP 429); no paid fallback and no merge.

Remaining gates

  • GitHub has not confirmed a conflict-free merge.
  • Waiting for iebb to approve this exact current head.
  • A reviewer still requests changes; obtain a superseding review or dismiss it explicitly.
  • The latest exact-head Quality PR run must pass.
  • Quality must attest the exact live master/head merge. Update the PR branch/commit for a fresh pull_request Quality run, then approve that head.
  • All current head checks must complete successfully (no skipped/neutral substitutes).
  • master source incomplete: ios/Runner.xcodeproj/project.pbxproj
  • head source incomplete: ios/Runner.xcodeproj/project.pbxproj
  • master source incomplete: ios/Runner/AppDelegate.swift
  • head source incomplete: ios/Runner/AppDelegate.swift
  • master source incomplete: lib/app/main_tab_view.dart
  • head source incomplete: lib/app/main_tab_view.dart
  • master source incomplete: test/glass_bottom_bar_geometry_test.dart
  • Context budget reached; remaining source omitted.

No PR code runs with the model key or write token. Free-only routing; provider/rate-limit failures never authorize a merge. Existing review findings must be resolved, not hidden by a new recommendation.

The pressed-tab mask, ripple, and the selection pill were 9px rounded
rectangles inside a stadium capsule — two different shapes at the same
place, visibly mismatched at both ends. The 9px radius came from iebb#193,
where a stadium indicator clipped the label row's lower corners; the
glass row has been icon-only since iebb#261, so nothing clips anymore.

The row sits a 5px inset inside the capsule's stadium, so a stadium of
the row's own height has its end arcs on the same centres as the
capsule's — the shapes are concentric and their edges parallel
everywhere. The icon block clears the arc even in the narrowest tablet
slot (365px squared against the 31px arc disc). The coverage test now
measures containment through the clamped RRect (the real stadium
geometry) with the bar's real fixed row height, and the capsule probes
pick the outermost stadium now that slot ripples share the pill radius.

@iebb iebb left a comment

Copy link
Copy Markdown
Owner

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Reviewed exact head cfed7d630e4a0952fbabba8f51d5da98e8b69588. Changes requested before publication.

Please do not treat this PR as a substitute for the owner's separately requested historical native-glass revert. That separate work is not validation of this unchanged PR.

The recorded Quality and Apple checks both stop at formatting test/glass_bottom_bar_geometry_test.dart, before build/test execution. Fix formatting and obtain fresh actual execution results. Two [P2] runtime-behavior gaps also remain: lib/app/main_tab_view.dart:2497–2504 selects the native glass host without the explicit high-contrast opaque fallback used in lib/app/liquid_glass_bottom_bar.dart:127–134; lib/app/native_glass_surface.dart:51–58 derives dark appearance from background, not the independently configurable navigation-bar token. Preserve high-contrast behavior and navigation-token ownership, and add differing-background/nav-token and high-contrast regressions.

Real UIKit backdrop, tint and corner behavior needs native evidence; mocked platform-view captures cannot prove it. No claim is made that this PR was tested by the maintainer's separate revert/compatibility work, or that a new runtime failure was reproduced here.

No approval is granted. Current-base merged-result Quality is not yet fresh in the reviewed evidence; before publication require fresh ordinary PR Quality on this exact head merged with live master, applicable platform checks, resolved findings and genuine exact-head owner approval. No incoming PR code was executed for this review.

@TiaraBasori

Copy link
Copy Markdown
Contributor Author

Closed as superseded by the owner's own native-glass restoration that landed on master (36c0ef1d revert of the Flutter-on-iOS bar, c8ebd3a6 native Liquid Glass selection, e9ab5791 high-contrast and custom navigation themes).

This PR's two remaining review findings are both covered there with a different mechanism: master's NativeGlassSurface derives darkness from the navBar token (not the background) and forwards highContrast plus opaqueBackground/opaqueSelection into the Swift host, which renders opaque backgrounds when contrast demands it — a stricter version of this branch's Flutter-side opaque fallback. The formatting failure on test/glass_bottom_bar_geometry_test.dart is moot on a closed branch; master's own suite carries the glass geometry coverage now.

No further work is planned on this branch. Thank you for the review — the tick-tock between the two implementations is exactly why the owner-side revert was the right call.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants