Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
2 changes: 1 addition & 1 deletion apps/mobile/DESIGN.md
Original file line number Diff line number Diff line change
Expand Up @@ -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`.
Expand Down
3 changes: 2 additions & 1 deletion apps/mobile/app/(tabs)/(home)/index.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -377,12 +377,13 @@ const HomeContent = memo(function HomeContent({
item.type === 'computer' ? (
conversationPeek ? (
<ConversationPeek href={conversationHref(item.conversation, saved?.hostId)}>
<ConversationRow row={item.conversation} now={new Date()} />
<ConversationRow row={item.conversation} now={new Date()} look={look} />
</ConversationPeek>
) : (
<ConversationRow
row={item.conversation}
now={new Date()}
look={look}
onPress={() => openConversation(router, item.conversation, saved?.hostId)}
actions={conversationActions(item.conversation, {
pinned: item.pinned,
Expand Down
8 changes: 7 additions & 1 deletion apps/mobile/src/ui/chat/conversation-row.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -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';
Expand Down Expand Up @@ -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;
Expand Down Expand Up @@ -64,6 +67,7 @@ export function ConversationRow({
onAction={act}
title={title}
source="computer"
look={look}
label={label}
preview={preview}
time={time}
Expand Down Expand Up @@ -123,7 +127,9 @@ export function ConversationRowFrame({
actions = [],
onAction,
testID,
look,
}: {
look?: HostLook | undefined;
actions?: readonly ConversationAction[];
onAction?: (key: ConversationActionKey) => void;
title: string;
Expand Down Expand Up @@ -189,7 +195,7 @@ export function ConversationRowFrame({
gap: theme.largeText ? space.x1 : space.x3,
}}
>
<SourceName name={title} source={source} />
<SourceName name={title} source={source} look={look} />
{time === '' ? null : (
<Text variant="meta" tone="subtle" numberOfLines={1} style={{ flexShrink: 0 }}>
{time}
Expand Down
27 changes: 25 additions & 2 deletions apps/mobile/src/ui/chat/source-name.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -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 (
<View
accessible
Expand All @@ -23,7 +34,19 @@ export function SourceName({ name, source }: { name: string; source: 'computer'
{name}
</Text>
<View accessibilityElementsHidden importantForAccessibility="no-hide-descendants">
<Icon color={theme.color.subtle} size={theme.type.meta.size * theme.fontScale} />
{source === 'computer' && look !== undefined ? (
<HostGlyph
icon={look.icon}
size={size}
color={
look.color === undefined
? theme.color.subtle
: hostColorInk(theme.scheme, theme.color, look.color)
}
/>
) : (
<Icon color={theme.color.subtle} size={size} />
)}
</View>
</View>
);
Expand Down
4 changes: 3 additions & 1 deletion apps/mobile/src/ui/share/share-screen.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -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';
Expand Down Expand Up @@ -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),
Expand Down Expand Up @@ -171,7 +173,7 @@ export default function ShareScreen() {
) : undefined
}
renderItem={({ item }) => (
<ConversationRow row={item} now={new Date()} onPress={() => void send(item)} />
<ConversationRow row={item} now={new Date()} look={look} onPress={() => void send(item)} />
)}
/>
);
Expand Down
71 changes: 71 additions & 0 deletions apps/mobile/test/source-name.test.tsx
Original file line number Diff line number Diff line change
@@ -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<typeof createElement>): Promise<HTMLElement> {
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(
<SourceName
name="Atlas"
source="computer"
look={{ icon: { kind: 'symbol', id: 'server' }, color: 'orange' }}
/>,
);
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(
<SourceName name="Atlas" source="computer" look={{ icon: { kind: 'emoji', emoji: '🦊' } }} />,
);
expect(view.textContent).toContain('🦊');
});

it('keeps the cloud mark for cloud chats', async () => {
const view = await render(
<SourceName
name="Ops"
source="cloud"
look={{ icon: { kind: 'symbol', id: 'server' }, color: 'orange' }}
/>,
);
expect(view.querySelector('i')).toBeNull();
expect(view.querySelector('[data-generic]')).not.toBeNull();
});
});
11 changes: 11 additions & 0 deletions changes/2499-mobile-chat-row-computer-icon.md
Original file line number Diff line number Diff line change
@@ -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))。
Loading