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 bfddc71..44bf2a1 100644 --- a/android/app/src/main/java/com/hyzq/boss/MainActivity.java +++ b/android/app/src/main/java/com/hyzq/boss/MainActivity.java @@ -360,7 +360,150 @@ public class MainActivity extends AppCompatActivity { } void refreshCurrentTab() { - refreshAllData(sessionData); + if ("devices".equals(activeTab)) { + refreshDevicesData(); + return; + } + if ("me".equals(activeTab)) { + refreshMeData(); + return; + } + refreshConversationsData(); + } + + void refreshConversationsData() { + startRefreshing(true); + executor.execute(() -> { + try { + JSONObject session = ensureActiveSession(); + BossApiClient.ApiResponse conversations = null; + boolean conversationsOk = false; + try { + conversations = apiClient.getConversations(); + conversationsOk = conversations.ok(); + } catch (Exception ignored) { + conversationsOk = false; + } + if (!conversationsOk) { + try { + BossApiClient.ApiResponse fallbackConversations = apiClient.getConversationHome(); + if (fallbackConversations.ok()) { + conversations = fallbackConversations; + conversationsOk = true; + } + } catch (Exception ignored) { + conversationsOk = false; + } + } + + BossApiClient.ApiResponse finalConversations = conversations; + final boolean finalConversationsOk = conversationsOk; + runOnUiThread(() -> { + sessionData = session; + conversationsData = WechatSurfaceMapper.resolveRefreshValue( + conversationsData, + finalConversations == null ? null : finalConversations.json.optJSONArray("conversations"), + finalConversationsOk + ); + maybeApplyPreferredEntry(); + renderCurrentTab(); + startRefreshing(false); + if (!finalConversationsOk) { + showMessage("刷新失败,请稍后重试"); + } + }); + } catch (Exception error) { + runOnUiThread(() -> handleSessionRefreshFailure()); + } + }); + } + + void refreshDevicesData() { + startRefreshing(true); + executor.execute(() -> { + try { + JSONObject session = ensureActiveSession(); + BossApiClient.ApiResponse devices = null; + boolean devicesOk = false; + try { + devices = apiClient.getDevices(); + devicesOk = devices.ok(); + } catch (Exception ignored) { + devicesOk = false; + } + + BossApiClient.ApiResponse finalDevices = devices; + final boolean finalDevicesOk = devicesOk; + runOnUiThread(() -> { + sessionData = session; + devicesData = WechatSurfaceMapper.resolveRefreshValue( + devicesData, + finalDevices == null ? null : finalDevices.json.optJSONArray("devices"), + finalDevicesOk + ); + renderCurrentTab(); + startRefreshing(false); + if (!finalDevicesOk) { + showMessage("刷新失败,请稍后重试"); + } + }); + } catch (Exception error) { + runOnUiThread(() -> handleSessionRefreshFailure()); + } + }); + } + + void refreshMeData() { + startRefreshing(true); + executor.execute(() -> { + try { + JSONObject session = ensureActiveSession(); + BossApiClient.ApiResponse ota = null; + BossApiClient.ApiResponse settings = null; + boolean otaOk = false; + boolean settingsOk = false; + try { + ota = apiClient.getOtaStatus(); + otaOk = ota.ok(); + } catch (Exception ignored) { + otaOk = false; + } + try { + settings = apiClient.getSettings(); + settingsOk = settings.ok(); + } catch (Exception ignored) { + settingsOk = false; + } + + BossApiClient.ApiResponse finalOta = ota; + BossApiClient.ApiResponse finalSettings = settings; + final boolean finalOtaOk = otaOk; + final boolean finalSettingsOk = settingsOk; + runOnUiThread(() -> { + sessionData = session; + otaData = WechatSurfaceMapper.resolveRefreshValue( + otaData, + finalOta == null ? null : finalOta.json, + finalOtaOk + ); + JSONObject settingsPayload = finalSettings == null ? null : finalSettings.json.optJSONObject("settings"); + JSONObject userPayload = finalSettings == null ? null : finalSettings.json.optJSONObject("user"); + if (finalSettingsOk && settingsPayload != null) { + preferredEntryTab = settingsPayload.optString("preferredEntryPoint", "conversations"); + } + if (finalSettingsOk) { + updateBoundDeviceState(userPayload); + } + renderCurrentTab(); + startRefreshing(false); + if (!finalOtaOk && !finalSettingsOk) { + showMessage("刷新失败,请稍后重试"); + } + }); + } catch (Exception error) { + runOnUiThread(() -> handleSessionRefreshFailure()); + } + }); } void handleRealtimeEvent(BossRealtimeEvent event) { @@ -569,6 +712,40 @@ public class MainActivity extends AppCompatActivity { }); } + private JSONObject ensureActiveSession() throws IOException { + JSONObject session = sessionData; + if (session != null) { + return session; + } + try { + BossApiClient.ApiResponse sessionResponse = apiClient.getSession(); + if (!sessionResponse.ok()) { + sessionResponse = apiClient.restoreSession(); + } + if (!sessionResponse.ok()) { + throw new IOException("SESSION_UNAVAILABLE"); + } + JSONObject restored = sessionResponse.json.optJSONObject("session"); + if (restored == null) { + throw new IOException("SESSION_UNAVAILABLE"); + } + return restored; + } catch (IOException error) { + throw error; + } catch (Exception error) { + throw new IOException("SESSION_UNAVAILABLE", error); + } + } + + private void handleSessionRefreshFailure() { + startRefreshing(false); + sessionData = null; + conversationsData = null; + devicesData = null; + otaData = null; + showLogin("当前登录已失效或同步失败,请重新点击登录。"); + } + private void showLogin(String hint) { loginPanel.setVisibility(View.VISIBLE); contentPanel.setVisibility(View.GONE); diff --git a/android/app/src/test/java/com/hyzq/boss/MainActivityRealtimeTest.java b/android/app/src/test/java/com/hyzq/boss/MainActivityRealtimeTest.java index 2c58d4c..8730651 100644 --- a/android/app/src/test/java/com/hyzq/boss/MainActivityRealtimeTest.java +++ b/android/app/src/test/java/com/hyzq/boss/MainActivityRealtimeTest.java @@ -28,7 +28,9 @@ public class MainActivityRealtimeTest { ); Shadows.shadowOf(activity.getMainLooper()).idle(); - assertEquals(1, activity.refreshCount); + assertEquals(1, activity.conversationRefreshCount); + assertEquals(0, activity.deviceRefreshCount); + assertEquals(0, activity.meRefreshCount); } @Test @@ -45,7 +47,8 @@ public class MainActivityRealtimeTest { ); Shadows.shadowOf(activity.getMainLooper()).idle(); - assertEquals(0, activity.refreshCount); + assertEquals(0, activity.conversationRefreshCount); + assertEquals(0, activity.deviceRefreshCount); } @Test @@ -62,7 +65,7 @@ public class MainActivityRealtimeTest { ); Shadows.shadowOf(activity.getMainLooper()).idle(); - assertEquals(0, activity.refreshCount); + assertEquals(0, activity.conversationRefreshCount); } @Test @@ -82,7 +85,8 @@ public class MainActivityRealtimeTest { ); Shadows.shadowOf(activity.getMainLooper()).idle(); - assertEquals(1, activity.refreshCount); + assertEquals(1, activity.conversationRefreshCount); + assertEquals(0, activity.deviceRefreshCount); } @Test @@ -113,15 +117,78 @@ public class MainActivityRealtimeTest { ); Shadows.shadowOf(activity.getMainLooper()).idle(); - assertEquals(2, activity.refreshCount); + assertEquals(2, activity.conversationRefreshCount); + assertEquals(0, activity.deviceRefreshCount); + } + + @Test + public void devicesRealtimeEventRefreshesVisibleDevicesTabOnly() throws Exception { + TestMainActivity activity = Robolectric.buildActivity(TestMainActivity.class).setup().resume().get(); + ReflectionHelpers.callInstanceMethod(activity, "showContent"); + ReflectionHelpers.callInstanceMethod( + activity, + "setActiveTab", + ReflectionHelpers.ClassParameter.from(String.class, "devices"), + ReflectionHelpers.ClassParameter.from(boolean.class, false) + ); + ReflectionHelpers.callInstanceMethod( + activity, + "handleRealtimeEvent", + ReflectionHelpers.ClassParameter.from( + BossRealtimeEvent.class, + new BossRealtimeEvent("devices.updated", new JSONObject().put("deviceId", "mac-studio")) + ) + ); + Shadows.shadowOf(activity.getMainLooper()).idle(); + + assertEquals(0, activity.conversationRefreshCount); + assertEquals(1, activity.deviceRefreshCount); + assertEquals(0, activity.meRefreshCount); + } + + @Test + public void otaRealtimeEventRefreshesVisibleMeTabOnly() throws Exception { + TestMainActivity activity = Robolectric.buildActivity(TestMainActivity.class).setup().resume().get(); + ReflectionHelpers.callInstanceMethod(activity, "showContent"); + ReflectionHelpers.callInstanceMethod( + activity, + "setActiveTab", + ReflectionHelpers.ClassParameter.from(String.class, "me"), + ReflectionHelpers.ClassParameter.from(boolean.class, false) + ); + ReflectionHelpers.callInstanceMethod( + activity, + "handleRealtimeEvent", + ReflectionHelpers.ClassParameter.from( + BossRealtimeEvent.class, + new BossRealtimeEvent("ota.updated", new JSONObject()) + ) + ); + Shadows.shadowOf(activity.getMainLooper()).idle(); + + assertEquals(0, activity.conversationRefreshCount); + assertEquals(0, activity.deviceRefreshCount); + assertEquals(1, activity.meRefreshCount); } public static class TestMainActivity extends MainActivity { - int refreshCount; + int conversationRefreshCount; + int deviceRefreshCount; + int meRefreshCount; @Override - void refreshCurrentTab() { - refreshCount += 1; + void refreshConversationsData() { + conversationRefreshCount += 1; + } + + @Override + void refreshDevicesData() { + deviceRefreshCount += 1; + } + + @Override + void refreshMeData() { + meRefreshCount += 1; } } }