fixed sending message with attachments
This commit is contained in:
@@ -91,7 +91,15 @@ public class MessageAdapter extends RecyclerView.Adapter<RecyclerView.ViewHolder
|
||||
this.binding = binding;
|
||||
}
|
||||
void bind(Message m, String token, String baseUrl, OnAttachmentClickListener listener) {
|
||||
binding.tvMessageContent.setText(m.getContent());
|
||||
// Check for Text
|
||||
if (m.getContent() != null && !m.getContent().isEmpty()) {
|
||||
binding.tvMessageContent.setVisibility(View.VISIBLE);
|
||||
binding.tvMessageContent.setText(m.getContent());
|
||||
} else {
|
||||
binding.tvMessageContent.setVisibility(View.GONE);
|
||||
}
|
||||
|
||||
// Check for Attachment
|
||||
displayAttachment(m, binding.ivAttachment, binding.tvAttachmentName, token, baseUrl);
|
||||
|
||||
View.OnClickListener click = v -> {
|
||||
@@ -109,7 +117,15 @@ public class MessageAdapter extends RecyclerView.Adapter<RecyclerView.ViewHolder
|
||||
this.binding = binding;
|
||||
}
|
||||
void bind(Message m, String token, String baseUrl, OnAttachmentClickListener listener) {
|
||||
binding.tvMessageContent.setText(m.getContent());
|
||||
// Check for Text
|
||||
if (m.getContent() != null && !m.getContent().isEmpty()) {
|
||||
binding.tvMessageContent.setVisibility(View.VISIBLE);
|
||||
binding.tvMessageContent.setText(m.getContent());
|
||||
} else {
|
||||
binding.tvMessageContent.setVisibility(View.GONE);
|
||||
}
|
||||
|
||||
// Check for Attachment
|
||||
displayAttachment(m, binding.ivAttachment, binding.tvAttachmentName, token, baseUrl);
|
||||
|
||||
View.OnClickListener click = v -> {
|
||||
|
||||
@@ -5,7 +5,6 @@ import com.example.petstoremobile.dtos.SendMessageRequest;
|
||||
import java.util.List;
|
||||
|
||||
import okhttp3.MultipartBody;
|
||||
import okhttp3.RequestBody;
|
||||
import okhttp3.ResponseBody;
|
||||
import retrofit2.Call;
|
||||
import retrofit2.http.Body;
|
||||
@@ -29,8 +28,8 @@ public interface MessageApi {
|
||||
@POST("api/v1/chat/conversations/{id}/attachments")
|
||||
Call<MessageDTO> sendMessageWithAttachment(
|
||||
@Path("id") Long conversationId,
|
||||
@Part("content") RequestBody content,
|
||||
@Part MultipartBody.Part attachment
|
||||
@Part MultipartBody.Part content,
|
||||
@Part MultipartBody.Part file
|
||||
);
|
||||
|
||||
@GET("api/v1/chat/messages/{id}/attachment")
|
||||
|
||||
@@ -55,7 +55,7 @@ public class NetworkModule {
|
||||
@Singleton
|
||||
public static OkHttpClient provideOkHttpClient(TokenManager tokenManager) {
|
||||
HttpLoggingInterceptor interceptor = new HttpLoggingInterceptor();
|
||||
interceptor.setLevel(HttpLoggingInterceptor.Level.BODY);
|
||||
interceptor.setLevel(HttpLoggingInterceptor.Level.HEADERS);
|
||||
|
||||
return new OkHttpClient.Builder()
|
||||
.addInterceptor(interceptor)
|
||||
@@ -191,4 +191,4 @@ public class NetworkModule {
|
||||
public static RefundApi provideRefundApi(Retrofit retrofit) {
|
||||
return retrofit.create(RefundApi.class);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -4,13 +4,11 @@ import android.app.Activity;
|
||||
import android.app.Dialog;
|
||||
import android.content.ContentValues;
|
||||
import android.content.Intent;
|
||||
import android.database.Cursor;
|
||||
import android.net.Uri;
|
||||
import android.os.Build;
|
||||
import android.os.Bundle;
|
||||
import android.os.Environment;
|
||||
import android.provider.MediaStore;
|
||||
import android.provider.OpenableColumns;
|
||||
import android.util.Log;
|
||||
import android.view.*;
|
||||
import android.view.inputmethod.EditorInfo;
|
||||
@@ -25,8 +23,6 @@ import androidx.fragment.app.Fragment;
|
||||
import androidx.lifecycle.ViewModelProvider;
|
||||
import androidx.recyclerview.widget.LinearLayoutManager;
|
||||
import com.bumptech.glide.Glide;
|
||||
import com.bumptech.glide.load.model.GlideUrl;
|
||||
import com.bumptech.glide.load.model.LazyHeaders;
|
||||
import com.example.petstoremobile.R;
|
||||
import com.example.petstoremobile.adapters.ChatAdapter;
|
||||
import com.example.petstoremobile.adapters.MessageAdapter;
|
||||
@@ -37,8 +33,11 @@ import com.example.petstoremobile.dtos.MessageDTO;
|
||||
import com.example.petstoremobile.models.Chat;
|
||||
import com.example.petstoremobile.models.Message;
|
||||
import com.example.petstoremobile.services.ChatNotificationService;
|
||||
import com.example.petstoremobile.utils.DialogUtils;
|
||||
import com.example.petstoremobile.utils.FileUtils;
|
||||
import com.example.petstoremobile.utils.GlideUtils;
|
||||
import com.example.petstoremobile.utils.Resource;
|
||||
import com.example.petstoremobile.utils.UIUtils;
|
||||
import com.example.petstoremobile.viewmodels.ChatListViewModel;
|
||||
import com.example.petstoremobile.websocket.StompChatManager;
|
||||
|
||||
@@ -131,8 +130,8 @@ public class ChatFragment extends Fragment implements ChatAdapter.OnChatClickLis
|
||||
binding.rvChatList.setAdapter(chatAdapter);
|
||||
|
||||
messageAdapter = new MessageAdapter(messageList, null);
|
||||
messageAdapter.setBaseUrl(baseUrl);
|
||||
|
||||
messageAdapter.setBaseUrl(baseUrl);
|
||||
|
||||
messageAdapter.setOnAttachmentClickListener(message -> {
|
||||
if (message.getAttachmentMimeType() != null && message.getAttachmentMimeType().startsWith("image/")) {
|
||||
showFullScreenImage(message);
|
||||
@@ -153,22 +152,18 @@ public class ChatFragment extends Fragment implements ChatAdapter.OnChatClickLis
|
||||
|
||||
Dialog dialog = new Dialog(requireContext(), android.R.style.Theme_Black_NoTitleBar_Fullscreen);
|
||||
dialog.setContentView(R.layout.dialog_full_screen_image);
|
||||
|
||||
|
||||
ImageView imageView = dialog.findViewById(R.id.ivFullScreen);
|
||||
ImageButton closeButton = dialog.findViewById(R.id.btnClose);
|
||||
|
||||
ImageButton downloadButton = dialog.findViewById(R.id.btnDownload);
|
||||
|
||||
String cleanBase = baseUrl.endsWith("/") ? baseUrl.substring(0, baseUrl.length() - 1) : baseUrl;
|
||||
String downloadUrl = cleanBase + "/api/v1/chat/messages/" + message.getId() + "/attachment";
|
||||
|
||||
GlideUrl glideUrl = new GlideUrl(downloadUrl, new LazyHeaders.Builder()
|
||||
.addHeader("Authorization", "Bearer " + tokenManager.getToken())
|
||||
.build());
|
||||
|
||||
Glide.with(this)
|
||||
.load(glideUrl)
|
||||
.into(imageView);
|
||||
GlideUtils.loadImageWithToken(requireContext(), imageView, downloadUrl, tokenManager.getToken(), R.drawable.placeholder);
|
||||
|
||||
closeButton.setOnClickListener(v -> dialog.dismiss());
|
||||
downloadButton.setOnClickListener(v -> downloadFile(message));
|
||||
imageView.setOnClickListener(v -> dialog.dismiss());
|
||||
dialog.show();
|
||||
}
|
||||
@@ -176,14 +171,17 @@ public class ChatFragment extends Fragment implements ChatAdapter.OnChatClickLis
|
||||
private void downloadFile(Message message) {
|
||||
if (message.getId() == null) return;
|
||||
|
||||
Toast.makeText(requireContext(), "Downloading " + message.getAttachmentName() + "...", Toast.LENGTH_SHORT).show();
|
||||
DialogUtils.showConfirmDialog(requireContext(), "Download Attachment",
|
||||
"Do you want to download \"" + message.getAttachmentName() + "\"?", () -> {
|
||||
Toast.makeText(requireContext(), "Downloading " + message.getAttachmentName() + "...", Toast.LENGTH_SHORT).show();
|
||||
|
||||
viewModel.downloadAttachment(message.getId()).observe(getViewLifecycleOwner(), resource -> {
|
||||
if (resource != null && resource.status == Resource.Status.SUCCESS && resource.data != null) {
|
||||
saveFileToDownloads(resource.data, message.getAttachmentName(), message.getAttachmentMimeType());
|
||||
} else if (resource != null && resource.status == Resource.Status.ERROR) {
|
||||
Toast.makeText(requireContext(), "Download failed: " + resource.message, Toast.LENGTH_SHORT).show();
|
||||
}
|
||||
viewModel.downloadAttachment(message.getId()).observe(getViewLifecycleOwner(), resource -> {
|
||||
if (resource != null && resource.status == Resource.Status.SUCCESS && resource.data != null) {
|
||||
saveFileToDownloads(resource.data, message.getAttachmentName(), message.getAttachmentMimeType());
|
||||
} else if (resource != null && resource.status == Resource.Status.ERROR) {
|
||||
Toast.makeText(requireContext(), "Download failed: " + resource.message, Toast.LENGTH_SHORT).show();
|
||||
}
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
@@ -220,7 +218,7 @@ public class ChatFragment extends Fragment implements ChatAdapter.OnChatClickLis
|
||||
Toast.makeText(requireContext(), "File saved to Downloads: " + file.getAbsolutePath(), Toast.LENGTH_LONG).show();
|
||||
}
|
||||
}
|
||||
body.close();
|
||||
body.close();
|
||||
} catch (Exception e) {
|
||||
Log.e(TAG, "Error saving file", e);
|
||||
Toast.makeText(requireContext(), "Error saving file", Toast.LENGTH_SHORT).show();
|
||||
@@ -232,7 +230,7 @@ public class ChatFragment extends Fragment implements ChatAdapter.OnChatClickLis
|
||||
chatList.clear();
|
||||
chatList.addAll(list);
|
||||
chatAdapter.notifyDataSetChanged();
|
||||
|
||||
|
||||
if (activeConversationId != null) {
|
||||
for (Chat chat : list) {
|
||||
if (chat.getChatId().equals(String.valueOf(activeConversationId))) {
|
||||
@@ -279,7 +277,7 @@ public class ChatFragment extends Fragment implements ChatAdapter.OnChatClickLis
|
||||
}
|
||||
|
||||
viewModel.loadCustomers();
|
||||
|
||||
|
||||
if (activeConversationId != null) {
|
||||
setConversationActive(true);
|
||||
if (stompChatManager != null) stompChatManager.subscribeToConversation(activeConversationId);
|
||||
@@ -346,12 +344,15 @@ public class ChatFragment extends Fragment implements ChatAdapter.OnChatClickLis
|
||||
}
|
||||
|
||||
String text = binding.etMessage.getText().toString().trim();
|
||||
RequestBody contentPart = RequestBody.create(MediaType.parse("text/plain"), text);
|
||||
|
||||
MultipartBody.Part contentPart = text.isEmpty()
|
||||
? null
|
||||
: MultipartBody.Part.createFormData("content", text);
|
||||
|
||||
String mimeType = requireContext().getContentResolver().getType(uri);
|
||||
if (mimeType == null) mimeType = "application/octet-stream";
|
||||
|
||||
RequestBody filePartBody = RequestBody.create(MediaType.parse(mimeType), file);
|
||||
|
||||
RequestBody filePartBody = RequestBody.create(file, MediaType.parse(mimeType));
|
||||
MultipartBody.Part filePart = MultipartBody.Part.createFormData("file", file.getName(), filePartBody);
|
||||
|
||||
binding.etMessage.setText("");
|
||||
@@ -424,9 +425,7 @@ public class ChatFragment extends Fragment implements ChatAdapter.OnChatClickLis
|
||||
}
|
||||
|
||||
private void setConversationActive(boolean active) {
|
||||
binding.btnSend.setEnabled(active);
|
||||
binding.etMessage.setEnabled(active);
|
||||
binding.btnAttach.setEnabled(active);
|
||||
UIUtils.setViewsEnabled(active, binding.btnSend, binding.etMessage, binding.btnAttach);
|
||||
if (!active) {
|
||||
activeConversationId = null;
|
||||
ChatNotificationService.activeConversationIdInUi = null;
|
||||
@@ -450,4 +449,4 @@ public class ChatFragment extends Fragment implements ChatAdapter.OnChatClickLis
|
||||
ChatNotificationService.activeConversationIdInUi = null;
|
||||
if (stompChatManager != null) stompChatManager.disconnect();
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -62,7 +62,7 @@ public class ChatRepository extends BaseRepository {
|
||||
/**
|
||||
* Sends a message with an attachment.
|
||||
*/
|
||||
public LiveData<Resource<MessageDTO>> sendMessageWithAttachment(Long conversationId, RequestBody content, MultipartBody.Part attachment) {
|
||||
public LiveData<Resource<MessageDTO>> sendMessageWithAttachment(Long conversationId, MultipartBody.Part content, MultipartBody.Part attachment) {
|
||||
return executeCall(messageApi.sendMessageWithAttachment(conversationId, content, attachment));
|
||||
}
|
||||
|
||||
|
||||
@@ -91,7 +91,7 @@ public class ChatListViewModel extends ViewModel {
|
||||
return chatRepository.sendMessage(conversationId, new SendMessageRequest(text));
|
||||
}
|
||||
|
||||
public LiveData<Resource<MessageDTO>> sendMessageWithAttachment(Long conversationId, RequestBody content, MultipartBody.Part attachment) {
|
||||
public LiveData<Resource<MessageDTO>> sendMessageWithAttachment(Long conversationId, MultipartBody.Part content, MultipartBody.Part attachment) {
|
||||
return chatRepository.sendMessageWithAttachment(conversationId, content, attachment);
|
||||
}
|
||||
|
||||
|
||||
@@ -10,15 +10,31 @@
|
||||
android:layout_height="match_parent"
|
||||
android:scaleType="fitCenter" />
|
||||
|
||||
<ImageButton
|
||||
android:id="@+id/btnClose"
|
||||
android:layout_width="48dp"
|
||||
android:layout_height="48dp"
|
||||
<LinearLayout
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_gravity="top|end"
|
||||
android:layout_margin="16dp"
|
||||
android:background="?attr/selectableItemBackgroundBorderless"
|
||||
android:src="@android:drawable/ic_menu_close_clear_cancel"
|
||||
android:contentDescription="Close"
|
||||
android:tint="@android:color/white" />
|
||||
android:orientation="horizontal">
|
||||
|
||||
<ImageButton
|
||||
android:id="@+id/btnDownload"
|
||||
android:layout_width="48dp"
|
||||
android:layout_height="48dp"
|
||||
android:background="@android:color/transparent"
|
||||
android:src="@android:drawable/stat_sys_download"
|
||||
android:contentDescription="Download"
|
||||
android:tint="@android:color/white" />
|
||||
|
||||
<ImageButton
|
||||
android:id="@+id/btnClose"
|
||||
android:layout_width="48dp"
|
||||
android:layout_height="48dp"
|
||||
android:layout_marginLeft="8dp"
|
||||
android:background="@android:color/transparent"
|
||||
android:src="@android:drawable/ic_menu_close_clear_cancel"
|
||||
android:contentDescription="Close"
|
||||
android:tint="@android:color/white" />
|
||||
</LinearLayout>
|
||||
|
||||
</FrameLayout>
|
||||
@@ -95,9 +95,10 @@ public class ChatController {
|
||||
@PreAuthorize("hasAnyRole('CUSTOMER', 'STAFF', 'ADMIN')")
|
||||
public ResponseEntity<MessageResponse> sendMessageWithAttachment(
|
||||
@PathVariable Long id,
|
||||
@RequestParam("file") MultipartFile file) {
|
||||
@RequestParam("file") MultipartFile file,
|
||||
@RequestParam(value = "content", required = false) String content) {
|
||||
User user = getCurrentUser();
|
||||
MessageResponse message = chatService.sendMessageWithAttachment(id, user.getId(), user.getRole(), file);
|
||||
MessageResponse message = chatService.sendMessageWithAttachment(id, user.getId(), user.getRole(), file, content);
|
||||
chatRealtimeService.publishMessage(id, message);
|
||||
chatRealtimeService.publishConversationUpdate(id);
|
||||
return ResponseEntity.status(HttpStatus.CREATED).body(message);
|
||||
|
||||
@@ -151,7 +151,7 @@ public class ChatService {
|
||||
}
|
||||
|
||||
@Transactional
|
||||
public MessageResponse sendMessageWithAttachment(Long conversationId, Long userId, User.Role role, MultipartFile file) {
|
||||
public MessageResponse sendMessageWithAttachment(Long conversationId, Long userId, User.Role role, MultipartFile file, String content) {
|
||||
Conversation conversation = conversationRepository.findById(conversationId)
|
||||
.orElseThrow(() -> new ResourceNotFoundException("Conversation not found"));
|
||||
|
||||
@@ -173,6 +173,7 @@ public class ChatService {
|
||||
Message message = new Message();
|
||||
message.setConversationId(conversationId);
|
||||
message.setSenderId(userId);
|
||||
message.setContent(content);
|
||||
message.setAttachmentUrl(attachmentUrl);
|
||||
message.setAttachmentName(file.getOriginalFilename());
|
||||
message.setAttachmentMimeType(file.getContentType());
|
||||
|
||||
@@ -55,3 +55,9 @@ logging:
|
||||
com.petshop: ${LOG_LEVEL:INFO}
|
||||
org.springframework.security: ${LOG_LEVEL_SECURITY:WARN}
|
||||
org.springdoc.core.events.SpringDocAppInitializer: ERROR
|
||||
|
||||
jackson:
|
||||
serialization:
|
||||
write-dates-as-timestamps: false
|
||||
deserialization:
|
||||
fail-on-unknown-properties: false
|
||||
Reference in New Issue
Block a user