diff --git a/.github/pr-screenshots/2499/ios-all-emoji-dark.png b/.github/pr-screenshots/2499/ios-all-emoji-dark.png new file mode 100644 index 0000000000..eb64b8883e Binary files /dev/null and b/.github/pr-screenshots/2499/ios-all-emoji-dark.png differ diff --git a/.github/pr-screenshots/2499/ios-all-emoji-light.png b/.github/pr-screenshots/2499/ios-all-emoji-light.png new file mode 100644 index 0000000000..218a569527 Binary files /dev/null and b/.github/pr-screenshots/2499/ios-all-emoji-light.png differ diff --git a/.github/pr-screenshots/2499/ios-all-server-dark.png b/.github/pr-screenshots/2499/ios-all-server-dark.png new file mode 100644 index 0000000000..14ac2c1a06 Binary files /dev/null and b/.github/pr-screenshots/2499/ios-all-server-dark.png differ diff --git a/.github/pr-screenshots/2499/ios-all-server-light.png b/.github/pr-screenshots/2499/ios-all-server-light.png new file mode 100644 index 0000000000..63e0db49d1 Binary files /dev/null and b/.github/pr-screenshots/2499/ios-all-server-light.png differ diff --git a/.github/pr-screenshots/2499/ios-all-server-maxtext-light.png b/.github/pr-screenshots/2499/ios-all-server-maxtext-light.png new file mode 100644 index 0000000000..7de795c51b Binary files /dev/null and b/.github/pr-screenshots/2499/ios-all-server-maxtext-light.png differ diff --git a/.github/pr-screenshots/2499/ios-computer-emoji-dark.png b/.github/pr-screenshots/2499/ios-computer-emoji-dark.png new file mode 100644 index 0000000000..cd97c5a670 Binary files /dev/null and b/.github/pr-screenshots/2499/ios-computer-emoji-dark.png differ diff --git a/.github/pr-screenshots/2499/ios-computer-emoji-light.png b/.github/pr-screenshots/2499/ios-computer-emoji-light.png new file mode 100644 index 0000000000..2763d446af Binary files /dev/null and b/.github/pr-screenshots/2499/ios-computer-emoji-light.png differ diff --git a/.github/pr-screenshots/2499/ios-computer-server-dark.png b/.github/pr-screenshots/2499/ios-computer-server-dark.png new file mode 100644 index 0000000000..70d8033c71 Binary files /dev/null and b/.github/pr-screenshots/2499/ios-computer-server-dark.png differ diff --git a/.github/pr-screenshots/2499/ios-computer-server-light.png b/.github/pr-screenshots/2499/ios-computer-server-light.png new file mode 100644 index 0000000000..cce9334c2d Binary files /dev/null and b/.github/pr-screenshots/2499/ios-computer-server-light.png differ diff --git a/.github/pr-screenshots/2499/ios-computer-server-maxtext-light.png b/.github/pr-screenshots/2499/ios-computer-server-maxtext-light.png new file mode 100644 index 0000000000..2abbe63f84 Binary files /dev/null and b/.github/pr-screenshots/2499/ios-computer-server-maxtext-light.png differ diff --git a/.github/pr-screenshots/2499/ios-computer-theme-dark.png b/.github/pr-screenshots/2499/ios-computer-theme-dark.png new file mode 100644 index 0000000000..bdaca0f942 Binary files /dev/null and b/.github/pr-screenshots/2499/ios-computer-theme-dark.png differ diff --git a/.github/pr-screenshots/2499/ios-drawer-emoji-dark.png b/.github/pr-screenshots/2499/ios-drawer-emoji-dark.png new file mode 100644 index 0000000000..7b453a70a2 Binary files /dev/null and b/.github/pr-screenshots/2499/ios-drawer-emoji-dark.png differ diff --git a/.github/pr-screenshots/2499/ios-drawer-server-dark.png b/.github/pr-screenshots/2499/ios-drawer-server-dark.png new file mode 100644 index 0000000000..22648d797b Binary files /dev/null and b/.github/pr-screenshots/2499/ios-drawer-server-dark.png differ diff --git a/.github/pr-screenshots/2499/ios-drawer-server-light.png b/.github/pr-screenshots/2499/ios-drawer-server-light.png new file mode 100644 index 0000000000..397d2d66a4 Binary files /dev/null and b/.github/pr-screenshots/2499/ios-drawer-server-light.png differ diff --git a/.github/pr-screenshots/2499/ios-reset-confirm-dark.png b/.github/pr-screenshots/2499/ios-reset-confirm-dark.png new file mode 100644 index 0000000000..1973d89767 Binary files /dev/null and b/.github/pr-screenshots/2499/ios-reset-confirm-dark.png differ diff --git a/.github/pr-screenshots/2499/ios-sheet-emoji-dark.png b/.github/pr-screenshots/2499/ios-sheet-emoji-dark.png new file mode 100644 index 0000000000..0af6a5a943 Binary files /dev/null and b/.github/pr-screenshots/2499/ios-sheet-emoji-dark.png differ diff --git a/.github/pr-screenshots/2499/ios-sheet-emoji-light.png b/.github/pr-screenshots/2499/ios-sheet-emoji-light.png new file mode 100644 index 0000000000..f7fcf62a92 Binary files /dev/null and b/.github/pr-screenshots/2499/ios-sheet-emoji-light.png differ diff --git a/.github/pr-screenshots/2499/ios-sheet-symbols-dark.png b/.github/pr-screenshots/2499/ios-sheet-symbols-dark.png new file mode 100644 index 0000000000..fc528533ac Binary files /dev/null and b/.github/pr-screenshots/2499/ios-sheet-symbols-dark.png differ diff --git a/.github/pr-screenshots/2499/ios-sheet-symbols-light.png b/.github/pr-screenshots/2499/ios-sheet-symbols-light.png new file mode 100644 index 0000000000..9c5ee1d5f5 Binary files /dev/null and b/.github/pr-screenshots/2499/ios-sheet-symbols-light.png differ diff --git a/apps/mobile/DESIGN.md b/apps/mobile/DESIGN.md index 8a16f08d17..c7f6b59b86 100644 --- a/apps/mobile/DESIGN.md +++ b/apps/mobile/DESIGN.md @@ -154,7 +154,7 @@ The shell is set by [ADR-0211](../../docs/adr/0211-the-mobile-app-has-two-tabs-w - On the Activity and Chats root lists, deliberate downward content scrolling hides the global bottom navigation; upward travel or reaching the top restores it. Clamp elastic overscroll so bounce does not toggle chrome. Activity also collapses its title/filter/search row, leaving the three tabs pinned below the safe area; no gradient covers rows beneath the tabs. Searching and filtering keep controls visible. Page changes restore chrome without discarding each page's scroll position. Scroll intent and transforms run on the UI thread; only native visibility/accessibility boundaries cross to React. List geometry stays fixed during the animation. - A 48pt avatar: the Bot's avatar as a rounded square (`radius.md`), or a rounded square with `#` for a Channel. A paused Bot's avatar is dimmed. -- First line: title, IM channel badges and time. The time follows IM lists: the clock time today, then Yesterday, the weekday within the last week, month/day this year, and the full date before that. +- First line: title, then its source mark, IM channel badges and time. The source mark is the Computer's chosen icon (in its chosen colour, `subtle` without one) for a Computer chat and the cloud glyph for a Cloud chat, at `meta` size, so chats from different places stay apart in **All**. It shows on every tab so rows keep one layout while the pager swipes. The time follows IM lists: the clock time today, then Yesterday, the weekday within the last week, month/day this year, and the full date before that. - Second line: the latest message as a one-line preview, in `fg` while the conversation has unread messages, then one status glyph at the trailing edge, the same set as the desktop sidebar: a spinning arc while the Bot thinks or works (still under Reduce Motion), a `warning` ring when it waits for the Human, a `danger` mark when blocked, a filled check when it finished and the Human has not read it, a `muted` check when it finished within the last day, and a `muted` moon when paused. Idle rows and Channels show none. - One count chip on the avatar's top-right corner, cut out of the row with a `canvas` edge: **Needs you** (how many requests wait for the Human) takes priority over **Unread** (how many messages arrived since the Human last read the conversation, from the Host's `channelUnreadCounts`). A muted conversation's unread count uses the neutral tone. - A collapsed Channel section shows one count chip for everything inside it: Needs you first, otherwise the unread sum, leaving muted conversations out. An expanded section that holds Bots waiting for the Human says how many, in `warning`. diff --git a/apps/mobile/app/(tabs)/(home)/index.tsx b/apps/mobile/app/(tabs)/(home)/index.tsx index 713c902b4f..6c93cedbf4 100644 --- a/apps/mobile/app/(tabs)/(home)/index.tsx +++ b/apps/mobile/app/(tabs)/(home)/index.tsx @@ -377,12 +377,13 @@ const HomeContent = memo(function HomeContent({ item.type === 'computer' ? ( conversationPeek ? ( - + ) : ( openConversation(router, item.conversation, saved?.hostId)} actions={conversationActions(item.conversation, { pinned: item.pinned, diff --git a/apps/mobile/src/ui/chat/conversation-row.tsx b/apps/mobile/src/ui/chat/conversation-row.tsx index 2e208cc7f0..da63a3d87b 100644 --- a/apps/mobile/src/ui/chat/conversation-row.tsx +++ b/apps/mobile/src/ui/chat/conversation-row.tsx @@ -3,6 +3,7 @@ import { SourceName } from './source-name'; import { Pressable, View } from 'react-native'; import { BotAvatar } from '../bot-avatar'; +import type { HostLook } from '../design/host-look'; import { edge, radius, space } from '../design/tokens'; import { useDesignTheme } from '../design/theme'; import { CountBadge } from '../primitives/count-badge'; @@ -34,9 +35,11 @@ export function ConversationRow({ onPress, actions = [], onAction, + look, }: { row: Conversation; now: Date; + look?: HostLook | undefined; onPress?: () => void; actions?: readonly ConversationAction[]; onAction?: (key: ConversationActionKey) => void; @@ -64,6 +67,7 @@ export function ConversationRow({ onAction={act} title={title} source="computer" + look={look} label={label} preview={preview} time={time} @@ -123,7 +127,9 @@ export function ConversationRowFrame({ actions = [], onAction, testID, + look, }: { + look?: HostLook | undefined; actions?: readonly ConversationAction[]; onAction?: (key: ConversationActionKey) => void; title: string; @@ -189,7 +195,7 @@ export function ConversationRowFrame({ gap: theme.largeText ? space.x1 : space.x3, }} > - + {time === '' ? null : ( {time} diff --git a/apps/mobile/src/ui/chat/source-name.tsx b/apps/mobile/src/ui/chat/source-name.tsx index baa02692e9..8e270533fd 100644 --- a/apps/mobile/src/ui/chat/source-name.tsx +++ b/apps/mobile/src/ui/chat/source-name.tsx @@ -2,11 +2,22 @@ import { View } from 'react-native'; import { useDesignTheme } from '../design/theme'; import { space } from '../design/tokens'; import { Text } from '../primitives/text'; +import { hostColorInk, type HostLook } from '../design/host-look'; +import { HostGlyph } from '../shell/host-tile'; import { CloudIcon, ComputerIcon } from '../shell/icons'; import { t } from '../strings'; -export function SourceName({ name, source }: { name: string; source: 'computer' | 'cloud' }) { +export function SourceName({ + name, + source, + look, +}: { + name: string; + source: 'computer' | 'cloud'; + look?: HostLook | undefined; +}) { const theme = useDesignTheme(); const Icon = source === 'cloud' ? CloudIcon : ComputerIcon; + const size = theme.type.meta.size * theme.fontScale; return ( - + {source === 'computer' && look !== undefined ? ( + + ) : ( + + )} ); diff --git a/apps/mobile/src/ui/share/share-screen.tsx b/apps/mobile/src/ui/share/share-screen.tsx index 031e6c1769..7071011a6f 100644 --- a/apps/mobile/src/ui/share/share-screen.tsx +++ b/apps/mobile/src/ui/share/share-screen.tsx @@ -13,6 +13,7 @@ import { import { useSafeAreaInsets } from 'react-native-safe-area-context'; import { useHostSession, useHostSnapshot, useQuietChannels } from '../../host/host-session'; +import { useHostLook } from '../../host/native-host-look'; import { hostDraftKey } from '../../host/source-drafts'; import { ConversationRow } from '../chat/conversation-row'; import { conversations, type Conversation } from '../chat/conversations'; @@ -40,6 +41,7 @@ export default function ShareScreen() { const { saved, client } = useHostSession(); const snapshot = useHostSnapshot(client); const quiet = useQuietChannels(saved?.hostId); + const look = useHostLook(saved?.hostId ?? undefined); const incoming = useIncomingShare(); const share = useMemo( () => incomingShare(incoming.resolvedSharedPayloads), @@ -171,7 +173,7 @@ export default function ShareScreen() { ) : undefined } renderItem={({ item }) => ( - void send(item)} /> + void send(item)} /> )} /> ); diff --git a/apps/mobile/test/source-name.test.tsx b/apps/mobile/test/source-name.test.tsx new file mode 100644 index 0000000000..f488a66592 --- /dev/null +++ b/apps/mobile/test/source-name.test.tsx @@ -0,0 +1,71 @@ +// @vitest-environment jsdom +import { act, createElement } from 'react'; +import { createRoot, type Root } from 'react-dom/client'; +import { afterEach, describe, expect, it, vi } from 'vitest'; + +vi.mock('react-native', async () => import('./react-native-shim')); +vi.mock('expo-symbols', () => ({ + SymbolView: ({ name, tintColor }: { name: { android: string }; tintColor: string }) => + createElement('i', { 'data-symbol': name.android, 'data-tint': tintColor }), +})); +vi.mock('react-native-svg', () => ({ + default: () => createElement('svg', { 'data-generic': 'true' }), + Path: () => null, + Circle: () => null, +})); + +import { hostColorInk } from '../src/ui/design/host-look'; +import { resolveTheme } from '../src/ui/design/resolve-theme'; +import { SourceName } from '../src/ui/chat/source-name'; + +Object.assign(globalThis, { IS_REACT_ACT_ENVIRONMENT: true }); + +let root: Root | undefined; +let host: HTMLElement | undefined; +afterEach(() => { + act(() => root?.unmount()); + host?.remove(); +}); + +async function render(element: ReturnType): Promise { + host = document.createElement('div'); + document.body.append(host); + root = createRoot(host); + await act(async () => root!.render(element)); + return host; +} + +describe('chat row source mark', () => { + it("shows the computer's own icon in its colour after the name", async () => { + const view = await render( + , + ); + const symbol = view.querySelector('i')!; + expect(symbol.dataset['symbol']).toBe('dns'); + const { color } = resolveTheme('light'); + expect(symbol.dataset['tint']).toBe(hostColorInk('light', color, 'orange')); + }); + + it('shows an emoji icon as text', async () => { + const view = await render( + , + ); + expect(view.textContent).toContain('🦊'); + }); + + it('keeps the cloud mark for cloud chats', async () => { + const view = await render( + , + ); + expect(view.querySelector('i')).toBeNull(); + expect(view.querySelector('[data-generic]')).not.toBeNull(); + }); +}); diff --git a/changes/2499-mobile-chat-row-computer-icon.md b/changes/2499-mobile-chat-row-computer-icon.md new file mode 100644 index 0000000000..926fbee0b6 --- /dev/null +++ b/changes/2499-mobile-chat-row-computer-icon.md @@ -0,0 +1,11 @@ +--- +section: Changed +--- + +## en + +- Mobile: chat rows in Chats and **Send to** now show the computer's chosen icon and colour after the chat name, in place of the generic computer mark, so chats from different places are easy to tell apart in **All**. Cloud chats keep the cloud mark ([#2499](https://github.com/BotHarness/DeepSeekBot/pull/2499)). + +## zh + +- 移动端:「聊天」和「发送到」里的会话行,名字后面改为显示该电脑自定义的图标和颜色,不再是通用的电脑图标,在「全部」里更容易分清会话来自哪里。云端会话仍显示云图标([#2499](https://github.com/BotHarness/DeepSeekBot/pull/2499))。