From 17dca04b6ff2937b5b03b90d3b8aef739bc5ef64 Mon Sep 17 00:00:00 2001 From: kris Date: Sat, 4 Apr 2026 01:50:06 +0800 Subject: [PATCH] feat: move conversation search into header mode --- .../main/java/com/hyzq/boss/MainActivity.java | 163 ++++++++++++++---- .../com/hyzq/boss/WechatSurfaceMapper.java | 2 +- .../app/src/main/res/layout/activity_main.xml | 37 +++- .../MainActivityConversationSearchTest.java | 81 ++++++--- ...MainActivityConversationSelectionTest.java | 2 +- .../WechatSurfaceMapperTopActionTest.java | 4 +- 6 files changed, 223 insertions(+), 66 deletions(-) diff --git a/android/app/src/main/java/com/hyzq/boss/MainActivity.java b/android/app/src/main/java/com/hyzq/boss/MainActivity.java index ace0bed..d07c9ed 100644 --- a/android/app/src/main/java/com/hyzq/boss/MainActivity.java +++ b/android/app/src/main/java/com/hyzq/boss/MainActivity.java @@ -61,6 +61,9 @@ public class MainActivity extends AppCompatActivity { private Button backButton; private TextView topTitle; private TextView topSubtitle; + private LinearLayout topTitleGroup; + private EditText topSearchInput; + private Button searchButton; private Button refreshButton; private Button tabConversations; private Button tabDevices; @@ -85,6 +88,7 @@ public class MainActivity extends AppCompatActivity { private String conversationSearchQuery = ""; private boolean pinnedConversationsCollapsed = false; private boolean conversationSelectionMode = false; + private boolean conversationSearchMode = false; private boolean conversationAutoRefreshArmed = false; private boolean conversationAutoRefreshEnabled = false; private final Set selectedConversationProjectIds = new LinkedHashSet<>(); @@ -128,6 +132,10 @@ public class MainActivity extends AppCompatActivity { @Override public void onBackPressed() { + if (contentPanel.getVisibility() == View.VISIBLE && conversationSearchMode) { + exitConversationSearchMode(true); + return; + } if (contentPanel.getVisibility() == View.VISIBLE && !"conversations".equals(activeTab)) { setActiveTab("conversations", false); persistLastRootTab("conversations"); @@ -179,6 +187,9 @@ public class MainActivity extends AppCompatActivity { backButton = findViewById(R.id.back_button); topTitle = findViewById(R.id.top_title); topSubtitle = findViewById(R.id.top_subtitle); + topTitleGroup = findViewById(R.id.top_title_group); + topSearchInput = findViewById(R.id.top_search_input); + searchButton = findViewById(R.id.search_button); refreshButton = findViewById(R.id.refresh_button); tabConversations = findViewById(R.id.tab_conversations); tabDevices = findViewById(R.id.tab_devices); @@ -199,6 +210,16 @@ public class MainActivity extends AppCompatActivity { private void bindActions() { loginButton.setOnClickListener(v -> performAutoLogin()); backButton.setVisibility(View.GONE); + backButton.setOnClickListener(v -> { + if (conversationSearchMode) { + exitConversationSearchMode(true); + } + }); + searchButton.setOnClickListener(v -> { + if ("conversations".equals(activeTab) && !conversationSelectionMode) { + enterConversationSearchMode(); + } + }); refreshButton.setOnClickListener(v -> handleTopAction()); tabConversations.setOnClickListener(v -> setActiveTab("conversations", true)); tabDevices.setOnClickListener(v -> setActiveTab("devices", true)); @@ -220,6 +241,26 @@ public class MainActivity extends AppCompatActivity { renderCurrentTab(); updateConversationAutoRefresh(); }); + topSearchInput.addTextChangedListener(new TextWatcher() { + @Override + public void beforeTextChanged(CharSequence s, int start, int count, int after) {} + + @Override + public void onTextChanged(CharSequence s, int start, int before, int count) {} + + @Override + public void afterTextChanged(Editable editable) { + if (!conversationSearchMode) { + return; + } + String nextQuery = editable == null ? "" : editable.toString(); + if (nextQuery.equals(conversationSearchQuery)) { + return; + } + conversationSearchQuery = nextQuery; + renderConversationsRoot(); + } + }); } private void applyInitialTab(@Nullable Intent intent) { @@ -436,6 +477,7 @@ public class MainActivity extends AppCompatActivity { private void setActiveTab(String tab, boolean fromUser) { if (!"conversations".equals(tab)) { exitConversationSelectionMode(); + exitConversationSearchMode(false); } activeTab = tab; if (fromUser) { @@ -465,18 +507,18 @@ public class MainActivity extends AppCompatActivity { switch (activeTab) { case "devices": updateHeader("设备", ""); - configureTopAction(WechatSurfaceMapper.rootTopAction(activeTab, false)); + configureRootHeaderActions(); renderDevicesRoot(); break; case "me": updateHeader("我的", ""); - configureTopAction(WechatSurfaceMapper.rootTopAction(activeTab, false)); + configureRootHeaderActions(); renderMeRoot(); break; case "conversations": default: updateHeader("会话", WechatSurfaceMapper.conversationsHeaderSubtitle()); - configureTopAction(WechatSurfaceMapper.rootTopAction(activeTab, false, conversationSelectionMode)); + configureRootHeaderActions(); renderConversationsRoot(); break; } @@ -512,13 +554,67 @@ public class MainActivity extends AppCompatActivity { ); } + private void configureRootHeaderActions() { + if ("conversations".equals(activeTab)) { + configureConversationHeaderActions(); + return; + } + topTitleGroup.setVisibility(View.VISIBLE); + topSearchInput.setVisibility(View.GONE); + backButton.setVisibility(View.GONE); + searchButton.setVisibility(View.GONE); + WechatSurfaceMapper.RootTopAction action = WechatSurfaceMapper.rootTopAction(activeTab, false); + refreshButton.setVisibility(View.VISIBLE); + configureTopAction(action); + } + + private void configureConversationHeaderActions() { + if (conversationSearchMode) { + topTitleGroup.setVisibility(View.GONE); + topSearchInput.setVisibility(View.VISIBLE); + backButton.setVisibility(View.VISIBLE); + backButton.setText("取消"); + searchButton.setVisibility(View.GONE); + refreshButton.setVisibility(View.GONE); + if (!conversationSearchQuery.equals(topSearchInput.getText().toString())) { + topSearchInput.setText(conversationSearchQuery); + topSearchInput.setSelection(topSearchInput.getText().length()); + } + return; + } + topTitleGroup.setVisibility(View.VISIBLE); + topSearchInput.setVisibility(View.GONE); + backButton.setVisibility(View.GONE); + searchButton.setVisibility(conversationSelectionMode ? View.GONE : View.VISIBLE); + refreshButton.setVisibility(View.VISIBLE); + BossUi.applyTopActionButtonStyle(this, searchButton, BossUi.TopActionButtonStyle.COMPACT_ICON); + WechatSurfaceMapper.RootTopAction action = WechatSurfaceMapper.rootTopAction(activeTab, false, conversationSelectionMode); + configureTopAction(action); + } + private void syncTopActionVisualState(boolean refreshing) { + if ("conversations".equals(activeTab)) { + configureConversationHeaderActions(); + refreshButton.setEnabled(true); + return; + } WechatSurfaceMapper.RootTopAction action = WechatSurfaceMapper.rootTopAction(activeTab, refreshing, conversationSelectionMode); configureTopAction(action); refreshButton.setEnabled(!"refresh".equals(action.actionKey) || !refreshing); } private void handleTopAction() { + if ("conversations".equals(activeTab)) { + if (conversationSearchMode) { + return; + } + if (conversationSelectionMode) { + exitConversationSelectionMode(); + return; + } + enterConversationSelectionMode(); + return; + } String actionKey = WechatSurfaceMapper.rootTopAction(activeTab, false, conversationSelectionMode).actionKey; if ("add_device".equals(actionKey)) { startActivity(new Intent(this, DeviceEnrollmentActivity.class)); @@ -540,7 +636,6 @@ public class MainActivity extends AppCompatActivity { return; } List items = new ArrayList<>(); - items.add(() -> buildConversationSearchInput()); if (conversationSelectionMode) { appendConversationSelectionControls(items); } @@ -672,6 +767,7 @@ public class MainActivity extends AppCompatActivity { } private void enterConversationSelectionMode() { + exitConversationSearchMode(false); conversationSelectionMode = true; selectedConversationProjectIds.clear(); syncTopActionVisualState(screenRefresh.isRefreshing()); @@ -690,6 +786,34 @@ public class MainActivity extends AppCompatActivity { } } + private void enterConversationSearchMode() { + if (!"conversations".equals(activeTab)) { + return; + } + conversationSearchMode = true; + syncTopActionVisualState(screenRefresh.isRefreshing()); + topSearchInput.post(() -> { + topSearchInput.requestFocus(); + topSearchInput.setSelection(topSearchInput.getText().length()); + }); + } + + private void exitConversationSearchMode(boolean clearQuery) { + if (!conversationSearchMode && (!clearQuery || conversationSearchQuery.isEmpty())) { + return; + } + boolean queryChanged = clearQuery && !conversationSearchQuery.isEmpty(); + conversationSearchMode = false; + if (clearQuery) { + conversationSearchQuery = ""; + topSearchInput.setText(""); + } + syncTopActionVisualState(screenRefresh != null && screenRefresh.isRefreshing()); + if (queryChanged && "conversations".equals(activeTab) && contentPanel.getVisibility() == View.VISIBLE) { + renderConversationsRoot(); + } + } + private void toggleConversationSelection(String projectId) { if (selectedConversationProjectIds.contains(projectId)) { selectedConversationProjectIds.remove(projectId); @@ -755,37 +879,6 @@ public class MainActivity extends AppCompatActivity { setActiveTab(tab, true); } - private EditText buildConversationSearchInput() { - EditText input = BossUi.buildInput(this, "搜索项目或线程", false); - LinearLayout.LayoutParams params = new LinearLayout.LayoutParams( - LinearLayout.LayoutParams.MATCH_PARENT, - LinearLayout.LayoutParams.WRAP_CONTENT - ); - params.leftMargin = BossUi.dp(this, 16); - params.rightMargin = BossUi.dp(this, 16); - params.bottomMargin = BossUi.dp(this, 10); - input.setLayoutParams(params); - input.setText(conversationSearchQuery); - input.addTextChangedListener(new TextWatcher() { - @Override - public void beforeTextChanged(CharSequence s, int start, int count, int after) {} - - @Override - public void onTextChanged(CharSequence s, int start, int before, int count) {} - - @Override - public void afterTextChanged(Editable editable) { - String nextQuery = editable == null ? "" : editable.toString(); - if (nextQuery.equals(conversationSearchQuery)) { - return; - } - conversationSearchQuery = nextQuery; - renderConversationsRoot(); - } - }); - return input; - } - static JSONArray filterConversationItems(@Nullable JSONArray source, @Nullable String rawQuery) { if (source == null) { return null; diff --git a/android/app/src/main/java/com/hyzq/boss/WechatSurfaceMapper.java b/android/app/src/main/java/com/hyzq/boss/WechatSurfaceMapper.java index c7000c9..72f3d94 100644 --- a/android/app/src/main/java/com/hyzq/boss/WechatSurfaceMapper.java +++ b/android/app/src/main/java/com/hyzq/boss/WechatSurfaceMapper.java @@ -235,7 +235,7 @@ public final class WechatSurfaceMapper { if (selectionMode) { return new RootTopAction("取消", false, false, "cancel_select_conversations"); } - return new RootTopAction("选择", false, false, "select_conversations"); + return new RootTopAction("+", false, true, "select_conversations"); } return new RootTopAction(refreshing ? "同步中" : "刷新", false, false, "refresh"); } diff --git a/android/app/src/main/res/layout/activity_main.xml b/android/app/src/main/res/layout/activity_main.xml index d341349..d6e732c 100644 --- a/android/app/src/main/res/layout/activity_main.xml +++ b/android/app/src/main/res/layout/activity_main.xml @@ -111,6 +111,7 @@ android:visibility="gone" /> + + +