diff --git a/.github/pr-screenshots/2398-composer-bottom-fade/2398-empty-composer-rest-dark.png b/.github/pr-screenshots/2398-composer-bottom-fade/2398-empty-composer-rest-dark.png new file mode 100644 index 0000000000..c9745deb31 Binary files /dev/null and b/.github/pr-screenshots/2398-composer-bottom-fade/2398-empty-composer-rest-dark.png differ diff --git a/.github/pr-screenshots/2398-composer-bottom-fade/2398-empty-composer-rest-light.png b/.github/pr-screenshots/2398-composer-bottom-fade/2398-empty-composer-rest-light.png new file mode 100644 index 0000000000..39e20548f1 Binary files /dev/null and b/.github/pr-screenshots/2398-composer-bottom-fade/2398-empty-composer-rest-light.png differ diff --git a/.github/pr-screenshots/2398-composer-bottom-fade/2398-empty-composer-scrolled-r2-dark.png b/.github/pr-screenshots/2398-composer-bottom-fade/2398-empty-composer-scrolled-r2-dark.png new file mode 100644 index 0000000000..3f8c268269 Binary files /dev/null and b/.github/pr-screenshots/2398-composer-bottom-fade/2398-empty-composer-scrolled-r2-dark.png differ diff --git a/.github/pr-screenshots/2398-composer-bottom-fade/2398-empty-composer-scrolled-r2-light.png b/.github/pr-screenshots/2398-composer-bottom-fade/2398-empty-composer-scrolled-r2-light.png new file mode 100644 index 0000000000..5ad8593237 Binary files /dev/null and b/.github/pr-screenshots/2398-composer-bottom-fade/2398-empty-composer-scrolled-r2-light.png differ diff --git a/.github/pr-screenshots/2398-composer-bottom-fade/2398-empty-composer-scrolled-r3-dark.png b/.github/pr-screenshots/2398-composer-bottom-fade/2398-empty-composer-scrolled-r3-dark.png new file mode 100644 index 0000000000..4382ab2cf3 Binary files /dev/null and b/.github/pr-screenshots/2398-composer-bottom-fade/2398-empty-composer-scrolled-r3-dark.png differ diff --git a/.github/pr-screenshots/2398-composer-bottom-fade/2398-empty-composer-scrolled-r3-light.png b/.github/pr-screenshots/2398-composer-bottom-fade/2398-empty-composer-scrolled-r3-light.png new file mode 100644 index 0000000000..fc74462311 Binary files /dev/null and b/.github/pr-screenshots/2398-composer-bottom-fade/2398-empty-composer-scrolled-r3-light.png differ diff --git a/.github/pr-screenshots/2398-composer-bottom-fade/2398-empty-composer-scrolled-r4-dark.png b/.github/pr-screenshots/2398-composer-bottom-fade/2398-empty-composer-scrolled-r4-dark.png new file mode 100644 index 0000000000..4382ab2cf3 Binary files /dev/null and b/.github/pr-screenshots/2398-composer-bottom-fade/2398-empty-composer-scrolled-r4-dark.png differ diff --git a/.github/pr-screenshots/2398-composer-bottom-fade/2398-empty-composer-scrolled-r4-light.png b/.github/pr-screenshots/2398-composer-bottom-fade/2398-empty-composer-scrolled-r4-light.png new file mode 100644 index 0000000000..4dd9bb7e2f Binary files /dev/null and b/.github/pr-screenshots/2398-composer-bottom-fade/2398-empty-composer-scrolled-r4-light.png differ diff --git a/.github/pr-screenshots/2398-composer-bottom-fade/2398-empty-composer-top-edge-crop-r2.png b/.github/pr-screenshots/2398-composer-bottom-fade/2398-empty-composer-top-edge-crop-r2.png new file mode 100644 index 0000000000..6073748fe6 Binary files /dev/null and b/.github/pr-screenshots/2398-composer-bottom-fade/2398-empty-composer-top-edge-crop-r2.png differ diff --git a/.github/pr-screenshots/2398-composer-bottom-fade/2398-empty-composer-top-edge-crop-r3.png b/.github/pr-screenshots/2398-composer-bottom-fade/2398-empty-composer-top-edge-crop-r3.png new file mode 100644 index 0000000000..757e75a160 Binary files /dev/null and b/.github/pr-screenshots/2398-composer-bottom-fade/2398-empty-composer-top-edge-crop-r3.png differ diff --git a/.github/pr-screenshots/2398-composer-bottom-fade/2398-empty-composer-top-edge-crop-r4.png b/.github/pr-screenshots/2398-composer-bottom-fade/2398-empty-composer-top-edge-crop-r4.png new file mode 100644 index 0000000000..38303bb4af Binary files /dev/null and b/.github/pr-screenshots/2398-composer-bottom-fade/2398-empty-composer-top-edge-crop-r4.png differ diff --git a/.github/pr-screenshots/2398-composer-bottom-fade/2398-four-line-keyboard-closed-scrolled-dark.png b/.github/pr-screenshots/2398-composer-bottom-fade/2398-four-line-keyboard-closed-scrolled-dark.png new file mode 100644 index 0000000000..91bd1a5e39 Binary files /dev/null and b/.github/pr-screenshots/2398-composer-bottom-fade/2398-four-line-keyboard-closed-scrolled-dark.png differ diff --git a/.github/pr-screenshots/2398-composer-bottom-fade/2398-four-line-keyboard-closed-scrolled-light.png b/.github/pr-screenshots/2398-composer-bottom-fade/2398-four-line-keyboard-closed-scrolled-light.png new file mode 100644 index 0000000000..4ea0984516 Binary files /dev/null and b/.github/pr-screenshots/2398-composer-bottom-fade/2398-four-line-keyboard-closed-scrolled-light.png differ diff --git a/.github/pr-screenshots/2398-composer-bottom-fade/2398-four-line-keyboard-closed-scrolled-r2-dark.png b/.github/pr-screenshots/2398-composer-bottom-fade/2398-four-line-keyboard-closed-scrolled-r2-dark.png new file mode 100644 index 0000000000..ad9357945f Binary files /dev/null and b/.github/pr-screenshots/2398-composer-bottom-fade/2398-four-line-keyboard-closed-scrolled-r2-dark.png differ diff --git a/.github/pr-screenshots/2398-composer-bottom-fade/2398-four-line-keyboard-closed-scrolled-r2-light.png b/.github/pr-screenshots/2398-composer-bottom-fade/2398-four-line-keyboard-closed-scrolled-r2-light.png new file mode 100644 index 0000000000..bcd36d3517 Binary files /dev/null and b/.github/pr-screenshots/2398-composer-bottom-fade/2398-four-line-keyboard-closed-scrolled-r2-light.png differ diff --git a/.github/pr-screenshots/2398-composer-bottom-fade/2398-four-line-keyboard-closed-scrolled-r3-dark.png b/.github/pr-screenshots/2398-composer-bottom-fade/2398-four-line-keyboard-closed-scrolled-r3-dark.png new file mode 100644 index 0000000000..bc352e9d9b Binary files /dev/null and b/.github/pr-screenshots/2398-composer-bottom-fade/2398-four-line-keyboard-closed-scrolled-r3-dark.png differ diff --git a/.github/pr-screenshots/2398-composer-bottom-fade/2398-four-line-keyboard-closed-scrolled-r3-light.png b/.github/pr-screenshots/2398-composer-bottom-fade/2398-four-line-keyboard-closed-scrolled-r3-light.png new file mode 100644 index 0000000000..6cdec03232 Binary files /dev/null and b/.github/pr-screenshots/2398-composer-bottom-fade/2398-four-line-keyboard-closed-scrolled-r3-light.png differ diff --git a/.github/pr-screenshots/2398-composer-bottom-fade/2398-four-line-keyboard-open-dark.png b/.github/pr-screenshots/2398-composer-bottom-fade/2398-four-line-keyboard-open-dark.png new file mode 100644 index 0000000000..312e68d8db Binary files /dev/null and b/.github/pr-screenshots/2398-composer-bottom-fade/2398-four-line-keyboard-open-dark.png differ diff --git a/.github/pr-screenshots/2398-composer-bottom-fade/2398-four-line-keyboard-open-light.png b/.github/pr-screenshots/2398-composer-bottom-fade/2398-four-line-keyboard-open-light.png new file mode 100644 index 0000000000..0afa55578a Binary files /dev/null and b/.github/pr-screenshots/2398-composer-bottom-fade/2398-four-line-keyboard-open-light.png differ diff --git a/.github/pr-screenshots/2398-composer-bottom-fade/2398-four-line-keyboard-open-r2-dark.png b/.github/pr-screenshots/2398-composer-bottom-fade/2398-four-line-keyboard-open-r2-dark.png new file mode 100644 index 0000000000..0467058a0b Binary files /dev/null and b/.github/pr-screenshots/2398-composer-bottom-fade/2398-four-line-keyboard-open-r2-dark.png differ diff --git a/.github/pr-screenshots/2398-composer-bottom-fade/2398-four-line-keyboard-open-r2-light.png b/.github/pr-screenshots/2398-composer-bottom-fade/2398-four-line-keyboard-open-r2-light.png new file mode 100644 index 0000000000..069b1c197f Binary files /dev/null and b/.github/pr-screenshots/2398-composer-bottom-fade/2398-four-line-keyboard-open-r2-light.png differ diff --git a/.github/pr-screenshots/2398-composer-bottom-fade/2398-four-line-keyboard-open-r3-dark.png b/.github/pr-screenshots/2398-composer-bottom-fade/2398-four-line-keyboard-open-r3-dark.png new file mode 100644 index 0000000000..a5fefe0e2d Binary files /dev/null and b/.github/pr-screenshots/2398-composer-bottom-fade/2398-four-line-keyboard-open-r3-dark.png differ diff --git a/.github/pr-screenshots/2398-composer-bottom-fade/2398-four-line-keyboard-open-r3-light.png b/.github/pr-screenshots/2398-composer-bottom-fade/2398-four-line-keyboard-open-r3-light.png new file mode 100644 index 0000000000..7ea9d55f80 Binary files /dev/null and b/.github/pr-screenshots/2398-composer-bottom-fade/2398-four-line-keyboard-open-r3-light.png differ diff --git a/.github/pr-screenshots/2398-composer-bottom-fade/2398-four-line-reopened-scrolled-r3-dark.png b/.github/pr-screenshots/2398-composer-bottom-fade/2398-four-line-reopened-scrolled-r3-dark.png new file mode 100644 index 0000000000..49dea2ea96 Binary files /dev/null and b/.github/pr-screenshots/2398-composer-bottom-fade/2398-four-line-reopened-scrolled-r3-dark.png differ diff --git a/.github/pr-screenshots/2398-composer-bottom-fade/2398-four-line-reopened-scrolled-r3-light.png b/.github/pr-screenshots/2398-composer-bottom-fade/2398-four-line-reopened-scrolled-r3-light.png new file mode 100644 index 0000000000..33ac1c9232 Binary files /dev/null and b/.github/pr-screenshots/2398-composer-bottom-fade/2398-four-line-reopened-scrolled-r3-light.png differ diff --git a/.github/pr-screenshots/2398-composer-bottom-fade/2398-header-r3.png b/.github/pr-screenshots/2398-composer-bottom-fade/2398-header-r3.png new file mode 100644 index 0000000000..499816d9b7 Binary files /dev/null and b/.github/pr-screenshots/2398-composer-bottom-fade/2398-header-r3.png differ diff --git a/apps/mobile/DESIGN.md b/apps/mobile/DESIGN.md index 28f1071885..8710b23e00 100644 --- a/apps/mobile/DESIGN.md +++ b/apps/mobile/DESIGN.md @@ -162,7 +162,7 @@ The shell is set by [ADR-0211](../../docs/adr/0211-the-mobile-app-has-two-tabs-w - Android: the header floats over the messages like iOS's (`headerTransparent`, no bar fill, no shadow), with the same three shapes as Grok's chat header: a round Back, the title chip at the start (`headerTitleAlign: 'left'`) as its own capsule island, and a round computer button. Each shape uses the opaque-iOS fallback of the header buttons (`headerGlassFill`: frosted `raised` fill with a `raised` hairline), so the chip reads as an island, not a label in a bar. Back is always this round button (it replaces the native navigation icon; system and predictive back still work). The list pads its own top by the header height, and messages scroll under the header through the drawn top fade (see below). Android has no system scroll edge effect: Material 3 only tints the top app bar on scroll, and the View fading edge (`fadingEdgeLength`) fades content linearly at the scroll view's own bounds, so it cannot keep what passes under the floating chrome hidden. - The chip holds, in one row, the Bot's avatar (28pt, round, no state ring) and its name in `body` strong; a group shows its mark and name. It is `theme.touch` tall with padding concentric with its corners. The Bot's state is read out in its accessibility label only. Pressing shows the `raised` pressed fill. Icons in the header are `fg`, never the Bot's accent. - iOS: messages run under the transparent header and the status bar, and UIKit's scroll edge effect (left on `automatic`) softens them there. The list uses `contentInsetAdjustmentBehavior="automatic"`, so UIKit, not a manual padding, keeps the header's height free above the oldest message at rest; the edge effect needs that automatic inset. The edge effect and React Native Screens both find the content scroll view through the first-subview chain of the screen, so the message list is the first thing in that chain: a default or colour chat background is the frame's own `backgroundColor`, not a view in front of the list. An image background still needs a view behind the list, and with one the soft edge does not reach the list. UIKit also adds the home-indicator safe area to the bottom inset while the list reaches the screen bottom (keyboard closed); the list cancels it with a negative `contentInset.bottom`, because the composer's height already includes that padding. Without this, scrolling to the very end left 34pt more space below the newest message than the resting position. There is no painted top gradient. A transparent header dimmed the whole message list on iOS 26 while the list was inverted (a scaleY flip); the list now runs top to bottom, which this header depends on. - - The list runs to the bottom of the screen, and the composer floats over it with no bar of its own; messages pass around and under its **+** button and field capsule. On iOS 26 with Reduce Transparency off both shapes are Liquid Glass, like iOS Messages; older iOS and Reduce Transparency give them an opaque `surface` fill with a `borderStrong` hairline, and Android gives the field the opaque `raised` fill, so on those nothing shows through where the Human types. The bottom of the screen fades out with the same light gradient over the composer and `edgeFade.bottom` above it. The list keeps that much space below the newest message, so at rest it sits just above the composer; scrolled up, messages pass behind the composer. Dragging the list dismisses the keyboard: interactively on iOS (the keyboard follows the finger once it reaches it), on drag on Android. On iOS 26 the bottom edge is UIKit's own scroll edge effect, like the top: the composer's container carries a `UIScrollEdgeElementContainerInteraction` for the message list (the local `scroll-edge-element` module; UIKit shapes the edge once when the interaction is added, so the container re-adds it whenever the composer's height or the keyboard changes), so UIKit softens messages under the composer the way it does under a system toolbar, and adapts it to Reduce Transparency itself. No fade is drawn there. A build without that module, older iOS and Android keep the drawn bottom fade over the composer's measured height (with the soft `EdgeMaterial` blur on iOS 26 with Reduce Transparency off). On Android the fade is a mask layer between the list and the floating chrome: nearly opaque (`edgeFade.cover`) under the composer and under the header, then the light `edgeFade` curve over `edgeFade.bottom` / `edgeFade.top` beyond them, painted in the chat's own background colour (`base` with an image background). With the keyboard open the composer's pill sits `space.x3` above it: the keyboard already covers the home indicator, so the chat frame takes the composer's safe-area padding back out of the keyboard offset instead of counting it twice. + - The list runs to the bottom of the screen, and the composer floats over it with no bar of its own; messages pass around and under its **+** button and field capsule. On iOS 26 with Reduce Transparency off both shapes are Liquid Glass, like iOS Messages; older iOS and Reduce Transparency give them an opaque `surface` fill with a `borderStrong` hairline, and Android gives the field the opaque `raised` fill, so on those nothing shows through where the Human types. The bottom of the screen fades out with the same light gradient over the composer and `edgeFade.bottom` above it. The list keeps that much space below the newest message, so at rest it sits just above the composer; scrolled up, messages pass behind the composer. Dragging the list dismisses the keyboard: interactively on iOS (the keyboard follows the finger once it reaches it), on drag on Android. On iOS and Android the bottom edge is a drawn mask layer between the list and the composer, like Grok's chat: nearly opaque (`edgeFade.cover`) under the composer's measured height, then the light `edgeFade` curve over `edgeFade.bottom` above it, painted in the chat's own background colour (`base` with an image background). Android uses the same mask under its floating header. iOS 26 hides UIKit's bottom scroll edge effect for the chat (`scrollEdgeEffects.bottom: 'hidden'`) and does not use a `UIScrollEdgeElementContainerInteraction` on the composer: UIKit picked the hard style there, an opaque band in a different colour from the list with a hairline across its top, at a height that did not follow a wrapped draft, and neither re-adding the interaction nor pinning the `soft` style fixed it. The top edge stays UIKit's automatic scroll edge effect. With the keyboard open the composer's pill sits `space.x3` above it: the keyboard already covers the home indicator, so the chat frame takes the composer's safe-area padding back out of the keyboard offset instead of counting it twice. - Messages: - Activity rows show Bot DMs, posts elsewhere, memory commits, Bot creation, departures and stopped turns as one left-aligned meta line with a kind icon and tail ellipsis. They use the same `bubbleRow(true)` inset and single `space.x4` (12pt) top gap as a new message group, with no bottom padding or vertically centred minimum-height box: bubble → activity, activity → activity and activity → new message group keep the same boundary gap. Icons and avatars match the meta line height. In groups, the actor avatar occupies the existing speaker gutter, followed by the icon at the bubble's left edge and the description. In a Human DM, omit the current Bot's avatar and redundant name; Bot-DM actions show the other Bot's avatar after the icon. Missing avatars use the person icon. - Activity rows do not navigate on tap. Long-press (or the accessibility details action) opens the message sheet with the complete plain-text content in a scrollable, selectable block capped at `messageSheet.textHeightRatio` of the window height, then Copy/Share and an explicit Open conversation, Open memory or Open new Bot action when a destination exists; there are no reactions. Consecutive posts by the same Bot into the same Channel on the same day fold into a counted row whose menu opens the latest post. Memory opens the Bot's memory files, not a commit diff. Bot-to-Bot DMs remain read-only. Keep full actor/action/detail text in the accessibility label and menu even when the compact memory line uses only the icon and commit subject. diff --git a/apps/mobile/modules/scroll-edge-element/README.md b/apps/mobile/modules/scroll-edge-element/README.md deleted file mode 100644 index c548bd66f5..0000000000 --- a/apps/mobile/modules/scroll-edge-element/README.md +++ /dev/null @@ -1,15 +0,0 @@ -# iOS scroll edge element - -This local Expo module lets a floating React Native view take part in UIKit's -scroll edge effect on iOS 26, the way a system toolbar does. It wraps its -children in a view that carries a `UIScrollEdgeElementContainerInteraction` -for the nearest sibling scroll view, so UIKit draws the soft edge under it. UIKit shapes that edge when the -interaction is added, so the `revision` prop re-adds it whenever the -container's height or the keyboard changes. - -The chat composer uses it for the bottom edge; the native header already gives -the top edge. Before iOS 26 the view is a plain container. - -The JavaScript binding lives in `src/ui/primitives/scroll-edge-element.ios.tsx`. -Builds without this module keep the drawn `EdgeMaterial` fade. **Adding this -module requires a native rebuild**; an OTA update alone cannot install it. diff --git a/apps/mobile/modules/scroll-edge-element/expo-module.config.json b/apps/mobile/modules/scroll-edge-element/expo-module.config.json deleted file mode 100644 index 4acb3d0d36..0000000000 --- a/apps/mobile/modules/scroll-edge-element/expo-module.config.json +++ /dev/null @@ -1,6 +0,0 @@ -{ - "platforms": ["apple"], - "apple": { - "modules": ["ScrollEdgeElementModule"] - } -} diff --git a/apps/mobile/modules/scroll-edge-element/ios/ScrollEdgeElement.podspec b/apps/mobile/modules/scroll-edge-element/ios/ScrollEdgeElement.podspec deleted file mode 100644 index c988e80b0f..0000000000 --- a/apps/mobile/modules/scroll-edge-element/ios/ScrollEdgeElement.podspec +++ /dev/null @@ -1,21 +0,0 @@ -Pod::Spec.new do |s| - s.name = 'ScrollEdgeElement' - s.version = '1.0.0' - s.summary = 'Registers a floating React Native view as a UIKit scroll edge element.' - s.description = 'Registers a floating React Native view as a UIKit scroll edge element.' - s.license = 'MIT' - s.author = 'BotHarness' - s.homepage = 'https://github.com/BotHarness/DeepSeekBot' - s.platforms = { :ios => '16.4' } - s.swift_version = '5.9' - s.source = { git: 'https://github.com/BotHarness/DeepSeekBot.git' } - s.static_framework = true - - s.dependency 'ExpoModulesCore' - - s.source_files = '**/*.{h,m,swift}' - s.pod_target_xcconfig = { - 'DEFINES_MODULE' => 'YES', - 'SWIFT_COMPILATION_MODE' => 'wholemodule' - } -end diff --git a/apps/mobile/modules/scroll-edge-element/ios/ScrollEdgeElementModule.swift b/apps/mobile/modules/scroll-edge-element/ios/ScrollEdgeElementModule.swift deleted file mode 100644 index cb83f1f142..0000000000 --- a/apps/mobile/modules/scroll-edge-element/ios/ScrollEdgeElementModule.swift +++ /dev/null @@ -1,85 +0,0 @@ -import ExpoModulesCore -import UIKit - -public final class ScrollEdgeElementModule: Module { - public func definition() -> ModuleDefinition { - Name("ScrollEdgeElement") - - View(ScrollEdgeElementView.self) { - Prop("edge") { (view: ScrollEdgeElementView, edge: String) in - view.edge = edge == "top" ? .top : .bottom - } - Prop("revision") { (view: ScrollEdgeElementView, _: String) in - view.refresh() - } - } - } -} - -final class ScrollEdgeElementView: ExpoView { - var edge: UIRectEdge = .bottom { - didSet { attach() } - } - - private var interaction: UIInteraction? - - override func didMoveToWindow() { - super.didMoveToWindow() - attach() - } - - override func layoutSubviews() { - super.layoutSubviews() - attach() - } - - func refresh() { - if let interaction { removeInteraction(interaction) } - interaction = nil - DispatchQueue.main.async { [weak self] in self?.attach() } - DispatchQueue.main.asyncAfter(deadline: .now() + 0.4) { [weak self] in - guard let self, let interaction = self.interaction else { return } - self.removeInteraction(interaction) - self.interaction = nil - self.attach() - } - } - - private func attach() { - guard #available(iOS 26.0, *) else { return } - guard window != nil, let scrollView = nearestScrollView() else { return } - let current = interaction as? UIScrollEdgeElementContainerInteraction - if let current, current.scrollView === scrollView, current.edge == edge { return } - if let current { removeInteraction(current) } - let next = UIScrollEdgeElementContainerInteraction() - next.scrollView = scrollView - next.edge = edge - addInteraction(next) - interaction = next - } - - private func nearestScrollView() -> UIScrollView? { - var child: UIView = self - while let parent = child.superview { - for sibling in parent.subviews where sibling !== child { - if let found = largestScrollView(in: sibling) { return found } - } - child = parent - } - return nil - } - - private func largestScrollView(in root: UIView) -> UIScrollView? { - var best: UIScrollView? - var queue: [UIView] = [root] - while !queue.isEmpty { - let view = queue.removeFirst() - if let scrollView = view as? UIScrollView, !(view is UITextView) { - if best == nil || scrollView.bounds.height > best!.bounds.height { best = scrollView } - continue - } - queue.append(contentsOf: view.subviews) - } - return best - } -} diff --git a/apps/mobile/src/ui/chat/conversation-screen.tsx b/apps/mobile/src/ui/chat/conversation-screen.tsx index 087b3e3900..a8ea20171a 100644 --- a/apps/mobile/src/ui/chat/conversation-screen.tsx +++ b/apps/mobile/src/ui/chat/conversation-screen.tsx @@ -74,7 +74,6 @@ import { ChatTitle } from './chat-title'; import { Composer, type ComposerInput } from './composer'; import { ClipboardImageOffer, useClipboardImage } from './clipboard-image'; import { EdgeFade, HeaderBackButton, titleChipFill } from './header-chrome'; -import { ScrollEdgeElement, nativeScrollEdge } from '../primitives/scroll-edge-element'; import { barLabelStyle, floatingHeader, @@ -812,6 +811,9 @@ function ChatConversation({ headerShadowVisible: false, headerBackButtonDisplayMode: 'minimal', ...(ios && !glassHeader ? { headerBlurEffect: 'systemChromeMaterial' as const } : {}), + ...(glassHeader + ? { scrollEdgeEffects: { top: 'automatic' as const, bottom: 'hidden' as const } } + : {}), ...(ios ? { title: '', @@ -865,66 +867,64 @@ function ChatConversation({ } onComposerLayout={(event) => setComposerHeight(event.nativeEvent.layout.height)} composer={ - - {botDm ? ( - - - {t.activityRow.botDm} - - - ) : ( - { - setSelection(undefined); - setCaret(next); - }} - {...(candidates.length > 0 - ? { above: } - : clipboard.offered - ? { - above: ( - { - clipboard.used(); - setAttachError(false); - setFiles((current) => - [...current, fromClipboardImage(data)].slice(0, MAX_PICKED), - ); - }} - /> - ), - } - : {})} - onSend={send} - onPick={pick} - onRemove={(id) => setFiles((current) => current.filter((file) => file.id !== id))} - onDictate={() => { - setAttachError(false); - dictation.start(); - }} - onStopDictation={dictation.stop} - onCancelDictation={dictation.cancel} - inputRef={composerInput} - onFocusChange={setTyping} - /> - )} - + botDm ? ( + + + {t.activityRow.botDm} + + + ) : ( + { + setSelection(undefined); + setCaret(next); + }} + {...(candidates.length > 0 + ? { above: } + : clipboard.offered + ? { + above: ( + { + clipboard.used(); + setAttachError(false); + setFiles((current) => + [...current, fromClipboardImage(data)].slice(0, MAX_PICKED), + ); + }} + /> + ), + } + : {})} + onSend={send} + onPick={pick} + onRemove={(id) => setFiles((current) => current.filter((file) => file.id !== id))} + onDictate={() => { + setAttachError(false); + dictation.start(); + }} + onStopDictation={dictation.stop} + onCancelDictation={dictation.cancel} + inputRef={composerInput} + onFocusChange={setTyping} + /> + ) } > {notice === undefined ? null : ( @@ -1081,13 +1081,12 @@ function ChatConversation({ color={fadeColor} /> ) : null} - {nativeScrollEdge ? null : ( - - )} + {composable && Platform.OS === 'ios' ? ( - {glass ? ( + {glass && cover === 0 ? ( ) : ( - + {(top @@ -132,7 +132,7 @@ export function EdgeFade({ ))} - + )} diff --git a/apps/mobile/src/ui/primitives/scroll-edge-element.ios.tsx b/apps/mobile/src/ui/primitives/scroll-edge-element.ios.tsx deleted file mode 100644 index 65c3d5d184..0000000000 --- a/apps/mobile/src/ui/primitives/scroll-edge-element.ios.tsx +++ /dev/null @@ -1,37 +0,0 @@ -import { requireNativeView, requireOptionalNativeModule } from 'expo'; -import { useState, type ReactNode } from 'react'; -import { Platform, View, type ViewProps } from 'react-native'; -import { useKeyboardState } from 'react-native-keyboard-controller'; - -type EdgeProps = ViewProps & { edge: 'top' | 'bottom'; revision: string }; - -export const nativeScrollEdge = - Number.parseInt(String(Platform.Version), 10) >= 26 && - requireOptionalNativeModule('ScrollEdgeElement') !== null; -const NativeEdge = nativeScrollEdge ? requireNativeView('ScrollEdgeElement') : undefined; - -export function ScrollEdgeElement({ - edge, - children, -}: { - edge: 'top' | 'bottom'; - children: ReactNode; -}) { - if (NativeEdge === undefined) return {children}; - return {children}; -} - -function NativeScrollEdge({ edge, children }: { edge: 'top' | 'bottom'; children: ReactNode }) { - const keyboard = useKeyboardState((state) => state.isVisible); - const [height, setHeight] = useState(0); - if (NativeEdge === undefined) return null; - return ( - setHeight(Math.round(event.nativeEvent.layout.height))} - > - {children} - - ); -} diff --git a/apps/mobile/src/ui/primitives/scroll-edge-element.tsx b/apps/mobile/src/ui/primitives/scroll-edge-element.tsx deleted file mode 100644 index b2f0e0a36c..0000000000 --- a/apps/mobile/src/ui/primitives/scroll-edge-element.tsx +++ /dev/null @@ -1,8 +0,0 @@ -import type { ReactNode } from 'react'; -import { View } from 'react-native'; - -export const nativeScrollEdge = false; - -export function ScrollEdgeElement({ children }: { edge: 'top' | 'bottom'; children: ReactNode }) { - return {children}; -} diff --git a/changes/2355-native-composer.md b/changes/2355-native-composer.md index 5377876558..103429a33e 100644 --- a/changes/2355-native-composer.md +++ b/changes/2355-native-composer.md @@ -4,8 +4,8 @@ section: Changed ## en -- The phone's chat composer now follows each platform's own compose bar. On iOS, **+** is its own round button beside a separate field capsule with the mic or Send inside, like iOS 26 Messages; on iOS 26 both float on Liquid Glass over the messages. On Android, **+** is a Material 3 icon button next to a filled rounded field, with ripple feedback. On iOS 26, messages under the composer now soften with the system's own scroll edge effect, matching the header ([#2355](https://github.com/BotHarness/DeepSeekBot/pull/2355)). +- The phone's chat composer now follows each platform's own compose bar. On iOS, **+** is its own round button beside a separate field capsule with the mic or Send inside, like iOS 26 Messages; on iOS 26 both float on Liquid Glass over the messages. On Android, **+** is a Material 3 icon button next to a filled rounded field, with ripple feedback. Messages fade out naturally under the composer, with no hard line ([#2355](https://github.com/BotHarness/DeepSeekBot/pull/2355), [#2398](https://github.com/BotHarness/DeepSeekBot/pull/2398)). ## zh -- 手机端聊天输入框改为各平台原生的输入栏样式。iOS 上,**+** 是独立的圆形按钮,旁边是单独的输入胶囊,麦克风或发送按钮在胶囊内,与 iOS 26 信息 App 一致;在 iOS 26 上两者以液态玻璃悬浮在消息上方。Android 上,**+** 是 Material 3 图标按钮,旁边是填充圆角输入框,点按有水波纹反馈。在 iOS 26 上,输入框下方的消息改用系统自带的滚动边缘效果柔化,与顶栏一致([#2355](https://github.com/BotHarness/DeepSeekBot/pull/2355))。 +- 手机端聊天输入框改为各平台原生的输入栏样式。iOS 上,**+** 是独立的圆形按钮,旁边是单独的输入胶囊,麦克风或发送按钮在胶囊内,与 iOS 26 信息 App 一致;在 iOS 26 上两者以液态玻璃悬浮在消息上方。Android 上,**+** 是 Material 3 图标按钮,旁边是填充圆角输入框,点按有水波纹反馈。输入框下方的消息会自然淡出,没有生硬的分隔线([#2355](https://github.com/BotHarness/DeepSeekBot/pull/2355)、[#2398](https://github.com/BotHarness/DeepSeekBot/pull/2398))。