|
|
|
|
@@ -2,7 +2,9 @@ package com.hyzq.boss;
|
|
|
|
|
|
|
|
|
|
import android.content.Intent;
|
|
|
|
|
import android.os.Bundle;
|
|
|
|
|
import android.text.Editable;
|
|
|
|
|
import android.text.TextUtils;
|
|
|
|
|
import android.text.TextWatcher;
|
|
|
|
|
import android.view.View;
|
|
|
|
|
import android.widget.Button;
|
|
|
|
|
import android.widget.EditText;
|
|
|
|
|
@@ -24,6 +26,10 @@ public class ProjectDetailActivity extends BossScreenActivity {
|
|
|
|
|
private EditText composerInput;
|
|
|
|
|
private Button composerSendButton;
|
|
|
|
|
private ScrollView chatScrollView;
|
|
|
|
|
private View pendingOutgoingBubble;
|
|
|
|
|
private boolean composerSending;
|
|
|
|
|
private boolean renderNearBottom;
|
|
|
|
|
private boolean renderForcedScrollToBottom;
|
|
|
|
|
|
|
|
|
|
@Override
|
|
|
|
|
protected int getLayoutResId() {
|
|
|
|
|
@@ -43,16 +49,35 @@ public class ProjectDetailActivity extends BossScreenActivity {
|
|
|
|
|
configureScreen(initialProjectName == null ? "项目详情" : initialProjectName, "正在同步项目详情...");
|
|
|
|
|
hideHeaderAction();
|
|
|
|
|
composerSendButton.setOnClickListener(v -> sendTextMessageFromComposer());
|
|
|
|
|
reload();
|
|
|
|
|
composerInput.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) {
|
|
|
|
|
updateComposerSendButtonState();
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
@Override
|
|
|
|
|
public void afterTextChanged(Editable s) {}
|
|
|
|
|
});
|
|
|
|
|
updateComposerSendButtonState();
|
|
|
|
|
reload(true);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
@Override
|
|
|
|
|
protected void reload() {
|
|
|
|
|
reload(false);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
private void reload(boolean forcedScrollToBottom) {
|
|
|
|
|
if (projectId == null || projectId.isEmpty()) {
|
|
|
|
|
showMessage("缺少 projectId");
|
|
|
|
|
finish();
|
|
|
|
|
return;
|
|
|
|
|
}
|
|
|
|
|
renderNearBottom = isChatNearBottom();
|
|
|
|
|
renderForcedScrollToBottom = forcedScrollToBottom;
|
|
|
|
|
setRefreshing(true);
|
|
|
|
|
executor.execute(() -> {
|
|
|
|
|
try {
|
|
|
|
|
@@ -64,7 +89,13 @@ public class ProjectDetailActivity extends BossScreenActivity {
|
|
|
|
|
} catch (Exception error) {
|
|
|
|
|
runOnUiThread(() -> {
|
|
|
|
|
setRefreshing(false);
|
|
|
|
|
replaceContent(BossUi.buildEmptyCard(this, "项目详情加载失败:" + error.getMessage()));
|
|
|
|
|
composerSending = false;
|
|
|
|
|
updateComposerSendButtonState();
|
|
|
|
|
if (pendingOutgoingBubble == null) {
|
|
|
|
|
replaceContent(BossUi.buildEmptyCard(this, "项目详情加载失败:" + error.getMessage()));
|
|
|
|
|
} else {
|
|
|
|
|
showMessage("项目详情刷新失败:" + error.getMessage());
|
|
|
|
|
}
|
|
|
|
|
});
|
|
|
|
|
}
|
|
|
|
|
});
|
|
|
|
|
@@ -76,9 +107,7 @@ public class ProjectDetailActivity extends BossScreenActivity {
|
|
|
|
|
if (composerInput != null) {
|
|
|
|
|
composerInput.setEnabled(!refreshing);
|
|
|
|
|
}
|
|
|
|
|
if (composerSendButton != null) {
|
|
|
|
|
composerSendButton.setEnabled(!refreshing);
|
|
|
|
|
}
|
|
|
|
|
updateComposerSendButtonState();
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
private void renderProject(JSONObject payload) {
|
|
|
|
|
@@ -91,6 +120,7 @@ public class ProjectDetailActivity extends BossScreenActivity {
|
|
|
|
|
|
|
|
|
|
renderQuickActions();
|
|
|
|
|
replaceContent();
|
|
|
|
|
pendingOutgoingBubble = null;
|
|
|
|
|
|
|
|
|
|
JSONArray messages = project == null ? null : project.optJSONArray("messages");
|
|
|
|
|
if (messages != null && messages.length() > 0) {
|
|
|
|
|
@@ -113,7 +143,9 @@ public class ProjectDetailActivity extends BossScreenActivity {
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
setRefreshing(false);
|
|
|
|
|
scrollChatToBottom();
|
|
|
|
|
if (ProjectChatUiState.shouldAutoScroll(renderNearBottom, renderForcedScrollToBottom)) {
|
|
|
|
|
scrollChatToBottom();
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
private void renderQuickActions() {
|
|
|
|
|
@@ -155,15 +187,23 @@ public class ProjectDetailActivity extends BossScreenActivity {
|
|
|
|
|
if (composerInput == null) {
|
|
|
|
|
return;
|
|
|
|
|
}
|
|
|
|
|
sendProjectMessage("text", composerInput.getText().toString().trim());
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
private void sendProjectMessage(String kind, String body) {
|
|
|
|
|
String body = composerInput.getText() == null ? "" : composerInput.getText().toString().trim();
|
|
|
|
|
if (body.isEmpty()) {
|
|
|
|
|
showMessage("请输入消息内容");
|
|
|
|
|
return;
|
|
|
|
|
}
|
|
|
|
|
if (!ProjectChatUiState.canSend(body, isComposerBusy())) {
|
|
|
|
|
return;
|
|
|
|
|
}
|
|
|
|
|
sendProjectMessage("text", body);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
private void sendProjectMessage(String kind, String body) {
|
|
|
|
|
composerSending = true;
|
|
|
|
|
updateComposerSendButtonState();
|
|
|
|
|
setRefreshing(true);
|
|
|
|
|
appendPendingOutgoingBubble(body);
|
|
|
|
|
scrollChatToBottom();
|
|
|
|
|
executor.execute(() -> {
|
|
|
|
|
try {
|
|
|
|
|
BossApiClient.ApiResponse response = apiClient.sendProjectMessage(projectId, body, kind);
|
|
|
|
|
@@ -171,14 +211,18 @@ public class ProjectDetailActivity extends BossScreenActivity {
|
|
|
|
|
throw new IllegalStateException(response.message());
|
|
|
|
|
}
|
|
|
|
|
runOnUiThread(() -> {
|
|
|
|
|
composerSending = false;
|
|
|
|
|
composerInput.setText("");
|
|
|
|
|
showMessage("消息已发送");
|
|
|
|
|
reload();
|
|
|
|
|
reload(true);
|
|
|
|
|
});
|
|
|
|
|
} catch (Exception error) {
|
|
|
|
|
runOnUiThread(() -> {
|
|
|
|
|
composerSending = false;
|
|
|
|
|
setRefreshing(false);
|
|
|
|
|
removePendingOutgoingBubble();
|
|
|
|
|
showMessage("发送失败:" + error.getMessage());
|
|
|
|
|
updateComposerSendButtonState();
|
|
|
|
|
});
|
|
|
|
|
}
|
|
|
|
|
});
|
|
|
|
|
@@ -223,6 +267,50 @@ public class ProjectDetailActivity extends BossScreenActivity {
|
|
|
|
|
chatScrollView.post(() -> chatScrollView.fullScroll(View.FOCUS_DOWN));
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
private void appendPendingOutgoingBubble(String body) {
|
|
|
|
|
if (contentLayout == null) {
|
|
|
|
|
return;
|
|
|
|
|
}
|
|
|
|
|
removePendingOutgoingBubble();
|
|
|
|
|
if (contentLayout.getChildCount() == 1 && contentLayout.getChildAt(0) instanceof android.widget.TextView) {
|
|
|
|
|
contentLayout.removeAllViews();
|
|
|
|
|
}
|
|
|
|
|
String senderLabel = TextUtils.isEmpty(apiClient.getDisplayName()) ? "你" : apiClient.getDisplayName();
|
|
|
|
|
pendingOutgoingBubble = BossUi.buildPendingOutgoingMessageBubble(this, senderLabel, body);
|
|
|
|
|
appendContent(pendingOutgoingBubble);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
private void removePendingOutgoingBubble() {
|
|
|
|
|
if (pendingOutgoingBubble != null && pendingOutgoingBubble.getParent() != null && contentLayout != null) {
|
|
|
|
|
contentLayout.removeView(pendingOutgoingBubble);
|
|
|
|
|
}
|
|
|
|
|
pendingOutgoingBubble = null;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
private void updateComposerSendButtonState() {
|
|
|
|
|
if (composerSendButton == null || composerInput == null) {
|
|
|
|
|
return;
|
|
|
|
|
}
|
|
|
|
|
String body = composerInput.getText() == null ? "" : composerInput.getText().toString();
|
|
|
|
|
composerSendButton.setEnabled(ProjectChatUiState.canSend(body, isComposerBusy()));
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
private boolean isComposerBusy() {
|
|
|
|
|
return composerSending || (refreshLayout != null && refreshLayout.isRefreshing());
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
private boolean isChatNearBottom() {
|
|
|
|
|
if (chatScrollView == null || chatScrollView.getChildCount() == 0 || chatScrollView.getHeight() == 0) {
|
|
|
|
|
return true;
|
|
|
|
|
}
|
|
|
|
|
View child = chatScrollView.getChildAt(0);
|
|
|
|
|
if (child == null || child.getHeight() == 0) {
|
|
|
|
|
return true;
|
|
|
|
|
}
|
|
|
|
|
int remainingScroll = child.getBottom() - (chatScrollView.getScrollY() + chatScrollView.getHeight());
|
|
|
|
|
return remainingScroll <= BossUi.dp(this, 96);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
private boolean isOutgoingMessage(String senderLabel) {
|
|
|
|
|
if (TextUtils.isEmpty(senderLabel)) {
|
|
|
|
|
return false;
|
|
|
|
|
|