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
2 changes: 1 addition & 1 deletion assets/l10n/de-DE.json

Large diffs are not rendered by default.

2 changes: 1 addition & 1 deletion assets/l10n/en-US.json

Large diffs are not rendered by default.

2 changes: 1 addition & 1 deletion assets/l10n/es-ES.json

Large diffs are not rendered by default.

2 changes: 1 addition & 1 deletion assets/l10n/fr-FR.json

Large diffs are not rendered by default.

2 changes: 1 addition & 1 deletion assets/l10n/ja-JP.json

Large diffs are not rendered by default.

2 changes: 1 addition & 1 deletion assets/l10n/ko-KR.json

Large diffs are not rendered by default.

2 changes: 1 addition & 1 deletion assets/l10n/zh-Hans-CN.json

Large diffs are not rendered by default.

2 changes: 1 addition & 1 deletion assets/l10n/zh-Hant-TW.json

Large diffs are not rendered by default.

15 changes: 14 additions & 1 deletion lib/chats/chat_list_preview.dart
Original file line number Diff line number Diff line change
Expand Up @@ -99,15 +99,22 @@ ChatListPreviewGeometry chatListPreviewGeometry(
/// Shows a Telegram-style, lifted conversation preview without opening the
/// chat or sending `viewMessages`. Selecting an action dismisses the preview
/// before handing control back to the chat list.
///
/// The foreground account owner is pinned when the preview opens and re-checked
/// after the delayed dismissal transition, so an account switch, session
/// replacement, slot close, or shutdown during dismissal suppresses the action
/// instead of running it against the replacement account.
Future<void> showChatListPreview(
BuildContext context, {
required ChatSummary chat,
required List<ChatListPreviewAction> actions,
String? meName,
TdFileRef? mePhoto,
ChatListPreviewLoader? loadMessages,
ChatPreviewAccounts accounts = _tdChatPreviewAccounts,
}) async {
unawaited(HapticFeedback.mediumImpact());
final owner = accounts.pinActiveOwner();
final reduceMotion = AppMotion.isReduced(context);
final route = RawDialogRoute<ChatListPreviewAction>(
barrierLabel: AppStringKeys.countryPickerCancel.l10n(context),
Expand All @@ -119,6 +126,7 @@ Future<void> showChatListPreview(
meName: meName,
mePhoto: mePhoto,
loadMessages: loadMessages,
accounts: accounts,
),
transitionBuilder: (dialogContext, animation, _, child) {
if (reduceMotion) return child;
Expand Down Expand Up @@ -148,6 +156,7 @@ Future<void> showChatListPreview(
// collide with the preview route.
await route.completed;
if (!context.mounted || selected == null) return;
if (owner != null && !owner.isCurrent) return;
selected.onSelected();
}

Expand Down Expand Up @@ -178,6 +187,9 @@ abstract interface class ChatPreviewOwner {

/// Supplies the owner a preview load runs against.
abstract interface class ChatPreviewAccounts {
static const ChatPreviewAccounts defaultPreviewAccounts =
_TdChatPreviewAccounts();

/// Null when nothing can be pinned: nothing logged in, shutdown already
/// running, or a client replaced between the reads.
ChatPreviewOwner? pinActiveOwner();
Expand Down Expand Up @@ -225,7 +237,8 @@ final class _TdChatPreviewAccounts implements ChatPreviewAccounts {
}
}

const ChatPreviewAccounts _tdChatPreviewAccounts = _TdChatPreviewAccounts();
const ChatPreviewAccounts _tdChatPreviewAccounts =
ChatPreviewAccounts.defaultPreviewAccounts;

/// The oldest message id in a `Messages` page, read from the raw payload so an
/// unparseable entry still moves the boundary instead of stalling the loop.
Expand Down
18 changes: 17 additions & 1 deletion lib/chats/chat_list_view.dart
Original file line number Diff line number Diff line change
Expand Up @@ -3168,6 +3168,7 @@ class _ChatListViewState extends State<ChatListView>
_dismissDesktopChatMenu();
}

final owner = ChatPreviewAccounts.defaultPreviewAccounts.pinActiveOwner();
entry = OverlayEntry(
builder: (_) => DesktopChatContextMenu(
anchor: anchor,
Expand All @@ -3184,6 +3185,7 @@ class _ChatListViewState extends State<ChatListView>
? null
: () => unawaited(openInSeparateWindow(chat)),
onToggleMute: () => _model.toggleMute(chat),
onArchive: () => _model.archive(chat, owner: owner),
onDeleteOrLeave: () => unawaited(_confirmDeleteChat(chat)),
),
);
Expand Down Expand Up @@ -3223,6 +3225,7 @@ class _ChatListViewState extends State<ChatListView>
final accounts = context.read<AccountStore>();
final activeAccount = accounts.activeSummary;
final avatarPath = activeAccount?.avatarPath?.trim();
final owner = ChatPreviewAccounts.defaultPreviewAccounts.pinActiveOwner();
unawaited(
showChatListPreview(
context,
Expand Down Expand Up @@ -3258,6 +3261,11 @@ class _ChatListViewState extends State<ChatListView>
icon: HeroAppIcons.thumbtack,
onSelected: () => _model.togglePin(chat),
),
ChatListPreviewAction(
label: AppStringKeys.chatListArchive,
icon: HeroAppIcons.archiveBox,
onSelected: () => _model.archive(chat, owner: owner),
),
ChatListPreviewAction(
label: chat.isMuted
? AppStringKeys.chatUnmute
Expand Down Expand Up @@ -3960,6 +3968,7 @@ class DesktopChatContextMenu extends StatelessWidget {
required this.onToggleRead,
required this.onToggleMute,
required this.onDeleteOrLeave,
required this.onArchive,
this.onOpenSeparateWindow,
});

Expand All @@ -3980,10 +3989,11 @@ class DesktopChatContextMenu extends StatelessWidget {
final VoidCallback? onOpenSeparateWindow;
final VoidCallback onToggleMute;
final VoidCallback onDeleteOrLeave;
final VoidCallback onArchive;

double get _menuHeight =>
verticalPadding * 2 +
rowHeight * (onOpenSeparateWindow == null ? 4 : 5) +
rowHeight * (onOpenSeparateWindow == null ? 5 : 6) +
dividerHeight;

void _select(VoidCallback action) {
Expand Down Expand Up @@ -4084,6 +4094,12 @@ class DesktopChatContextMenu extends StatelessWidget {
: AppStringKeys.callMute,
onTap: () => _select(onToggleMute),
),
_DesktopChatContextMenuItem(
key: const ValueKey('desktop-chat-context-archive'),
icon: HeroAppIcons.archiveBox,
label: AppStringKeys.chatListArchive,
onTap: () => _select(onArchive),
),
SizedBox(
height: dividerHeight,
child: ColoredBox(color: c.divider),
Expand Down
30 changes: 23 additions & 7 deletions lib/chats/chat_list_view_model.dart
Original file line number Diff line number Diff line change
Expand Up @@ -25,6 +25,7 @@ import '../tdlib/td_client.dart';
import '../tdlib/td_models.dart';
import '../tdlib/td_user_index.dart';
import 'chat_delete_policy.dart';
import 'chat_list_preview.dart';
import 'chat_removal_actions.dart';

class ChatFilterOption {
Expand Down Expand Up @@ -688,13 +689,28 @@ class ChatListViewModel extends ChangeNotifier {

/// Moves an archived chat back to the main list without deleting history.
void unarchive(ChatSummary chat) {
_client
.query({
'@type': 'addChatToList',
'chat_id': chat.id,
'chat_list': {'@type': 'chatListMain'},
})
.catchError((Object _) => <String, dynamic>{});
_chatListQuery({
'@type': 'addChatToList',
'chat_id': chat.id,
'chat_list': {'@type': 'chatListMain'},
}).catchError((Object _) => <String, dynamic>{});
}

/// Moves a main-list chat into the archive without deleting history.
///
/// When [owner] is supplied (e.g. pinned at preview/menu open), the request
/// is dropped if that account is no longer current and otherwise dispatched
/// through [ChatPreviewOwner.query] (bound to `owner.clientId`).
void archive(ChatSummary chat, {ChatPreviewOwner? owner}) {
if (_disposed) return;
if (owner != null && !owner.isCurrent) return;
final request = <String, dynamic>{
'@type': 'addChatToList',
'chat_id': chat.id,
'chat_list': {'@type': 'chatListArchive'},
};
final send = _queryForTesting ?? owner?.query ?? _client.query;
send(request).catchError((Object _) => <String, dynamic>{});
}

void toggleMute(ChatSummary chat) {
Expand Down
1 change: 1 addition & 0 deletions lib/components/app_icons.dart
Original file line number Diff line number Diff line change
Expand Up @@ -357,6 +357,7 @@ class HeroAppIcons {
static const idBadge = AppIconData(HeroiconsOutline.identification);
static const image = AppIconData(HeroiconsOutline.photo);
static const images = AppIconData(HeroiconsOutline.photo);
static const archiveBox = AppIconData(HeroiconsOutline.archiveBox);
static const inbox = AppIconData(HeroiconsOutline.inbox);
static const language = AppIconData(HeroiconsOutline.language);
static const key = AppIconData(HeroiconsOutline.key);
Expand Down
1 change: 1 addition & 0 deletions lib/l10n/app_localizations.dart
Original file line number Diff line number Diff line change
Expand Up @@ -923,6 +923,7 @@ abstract final class AppStringKeys {
static const desktopWindowMinimize = 'desktopWindowMinimize';
static const chatListNoChats = 'chatListNoChats';
static const chatListScanQrCode = 'chatListScanQrCode';
static const chatListArchive = 'chatListArchive';
static const chatListUnarchive = 'chatListUnarchive';
static const chatListUnpin = 'chatListUnpin';
static const chatLoadingTopics = 'chatLoadingTopics';
Expand Down
Loading
Loading