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;
|
this.binding = binding;
|
||||||
}
|
}
|
||||||
void bind(Message m, String token, String baseUrl, OnAttachmentClickListener listener) {
|
void bind(Message m, String token, String baseUrl, OnAttachmentClickListener listener) {
|
||||||
|
// Check for Text
|
||||||
|
if (m.getContent() != null && !m.getContent().isEmpty()) {
|
||||||
|
binding.tvMessageContent.setVisibility(View.VISIBLE);
|
||||||
binding.tvMessageContent.setText(m.getContent());
|
binding.tvMessageContent.setText(m.getContent());
|
||||||
|
} else {
|
||||||
|
binding.tvMessageContent.setVisibility(View.GONE);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Check for Attachment
|
||||||
displayAttachment(m, binding.ivAttachment, binding.tvAttachmentName, token, baseUrl);
|
displayAttachment(m, binding.ivAttachment, binding.tvAttachmentName, token, baseUrl);
|
||||||
|
|
||||||
View.OnClickListener click = v -> {
|
View.OnClickListener click = v -> {
|
||||||
@@ -109,7 +117,15 @@ public class MessageAdapter extends RecyclerView.Adapter<RecyclerView.ViewHolder
|
|||||||
this.binding = binding;
|
this.binding = binding;
|
||||||
}
|
}
|
||||||
void bind(Message m, String token, String baseUrl, OnAttachmentClickListener listener) {
|
void bind(Message m, String token, String baseUrl, OnAttachmentClickListener listener) {
|
||||||
|
// Check for Text
|
||||||
|
if (m.getContent() != null && !m.getContent().isEmpty()) {
|
||||||
|
binding.tvMessageContent.setVisibility(View.VISIBLE);
|
||||||
binding.tvMessageContent.setText(m.getContent());
|
binding.tvMessageContent.setText(m.getContent());
|
||||||
|
} else {
|
||||||
|
binding.tvMessageContent.setVisibility(View.GONE);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Check for Attachment
|
||||||
displayAttachment(m, binding.ivAttachment, binding.tvAttachmentName, token, baseUrl);
|
displayAttachment(m, binding.ivAttachment, binding.tvAttachmentName, token, baseUrl);
|
||||||
|
|
||||||
View.OnClickListener click = v -> {
|
View.OnClickListener click = v -> {
|
||||||
|
|||||||
@@ -5,7 +5,6 @@ import com.example.petstoremobile.dtos.SendMessageRequest;
|
|||||||
import java.util.List;
|
import java.util.List;
|
||||||
|
|
||||||
import okhttp3.MultipartBody;
|
import okhttp3.MultipartBody;
|
||||||
import okhttp3.RequestBody;
|
|
||||||
import okhttp3.ResponseBody;
|
import okhttp3.ResponseBody;
|
||||||
import retrofit2.Call;
|
import retrofit2.Call;
|
||||||
import retrofit2.http.Body;
|
import retrofit2.http.Body;
|
||||||
@@ -29,8 +28,8 @@ public interface MessageApi {
|
|||||||
@POST("api/v1/chat/conversations/{id}/attachments")
|
@POST("api/v1/chat/conversations/{id}/attachments")
|
||||||
Call<MessageDTO> sendMessageWithAttachment(
|
Call<MessageDTO> sendMessageWithAttachment(
|
||||||
@Path("id") Long conversationId,
|
@Path("id") Long conversationId,
|
||||||
@Part("content") RequestBody content,
|
@Part MultipartBody.Part content,
|
||||||
@Part MultipartBody.Part attachment
|
@Part MultipartBody.Part file
|
||||||
);
|
);
|
||||||
|
|
||||||
@GET("api/v1/chat/messages/{id}/attachment")
|
@GET("api/v1/chat/messages/{id}/attachment")
|
||||||
|
|||||||
@@ -55,7 +55,7 @@ public class NetworkModule {
|
|||||||
@Singleton
|
@Singleton
|
||||||
public static OkHttpClient provideOkHttpClient(TokenManager tokenManager) {
|
public static OkHttpClient provideOkHttpClient(TokenManager tokenManager) {
|
||||||
HttpLoggingInterceptor interceptor = new HttpLoggingInterceptor();
|
HttpLoggingInterceptor interceptor = new HttpLoggingInterceptor();
|
||||||
interceptor.setLevel(HttpLoggingInterceptor.Level.BODY);
|
interceptor.setLevel(HttpLoggingInterceptor.Level.HEADERS);
|
||||||
|
|
||||||
return new OkHttpClient.Builder()
|
return new OkHttpClient.Builder()
|
||||||
.addInterceptor(interceptor)
|
.addInterceptor(interceptor)
|
||||||
|
|||||||
@@ -4,13 +4,11 @@ import android.app.Activity;
|
|||||||
import android.app.Dialog;
|
import android.app.Dialog;
|
||||||
import android.content.ContentValues;
|
import android.content.ContentValues;
|
||||||
import android.content.Intent;
|
import android.content.Intent;
|
||||||
import android.database.Cursor;
|
|
||||||
import android.net.Uri;
|
import android.net.Uri;
|
||||||
import android.os.Build;
|
import android.os.Build;
|
||||||
import android.os.Bundle;
|
import android.os.Bundle;
|
||||||
import android.os.Environment;
|
import android.os.Environment;
|
||||||
import android.provider.MediaStore;
|
import android.provider.MediaStore;
|
||||||
import android.provider.OpenableColumns;
|
|
||||||
import android.util.Log;
|
import android.util.Log;
|
||||||
import android.view.*;
|
import android.view.*;
|
||||||
import android.view.inputmethod.EditorInfo;
|
import android.view.inputmethod.EditorInfo;
|
||||||
@@ -25,8 +23,6 @@ import androidx.fragment.app.Fragment;
|
|||||||
import androidx.lifecycle.ViewModelProvider;
|
import androidx.lifecycle.ViewModelProvider;
|
||||||
import androidx.recyclerview.widget.LinearLayoutManager;
|
import androidx.recyclerview.widget.LinearLayoutManager;
|
||||||
import com.bumptech.glide.Glide;
|
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.R;
|
||||||
import com.example.petstoremobile.adapters.ChatAdapter;
|
import com.example.petstoremobile.adapters.ChatAdapter;
|
||||||
import com.example.petstoremobile.adapters.MessageAdapter;
|
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.Chat;
|
||||||
import com.example.petstoremobile.models.Message;
|
import com.example.petstoremobile.models.Message;
|
||||||
import com.example.petstoremobile.services.ChatNotificationService;
|
import com.example.petstoremobile.services.ChatNotificationService;
|
||||||
|
import com.example.petstoremobile.utils.DialogUtils;
|
||||||
import com.example.petstoremobile.utils.FileUtils;
|
import com.example.petstoremobile.utils.FileUtils;
|
||||||
|
import com.example.petstoremobile.utils.GlideUtils;
|
||||||
import com.example.petstoremobile.utils.Resource;
|
import com.example.petstoremobile.utils.Resource;
|
||||||
|
import com.example.petstoremobile.utils.UIUtils;
|
||||||
import com.example.petstoremobile.viewmodels.ChatListViewModel;
|
import com.example.petstoremobile.viewmodels.ChatListViewModel;
|
||||||
import com.example.petstoremobile.websocket.StompChatManager;
|
import com.example.petstoremobile.websocket.StompChatManager;
|
||||||
|
|
||||||
@@ -156,19 +155,15 @@ public class ChatFragment extends Fragment implements ChatAdapter.OnChatClickLis
|
|||||||
|
|
||||||
ImageView imageView = dialog.findViewById(R.id.ivFullScreen);
|
ImageView imageView = dialog.findViewById(R.id.ivFullScreen);
|
||||||
ImageButton closeButton = dialog.findViewById(R.id.btnClose);
|
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 cleanBase = baseUrl.endsWith("/") ? baseUrl.substring(0, baseUrl.length() - 1) : baseUrl;
|
||||||
String downloadUrl = cleanBase + "/api/v1/chat/messages/" + message.getId() + "/attachment";
|
String downloadUrl = cleanBase + "/api/v1/chat/messages/" + message.getId() + "/attachment";
|
||||||
|
|
||||||
GlideUrl glideUrl = new GlideUrl(downloadUrl, new LazyHeaders.Builder()
|
GlideUtils.loadImageWithToken(requireContext(), imageView, downloadUrl, tokenManager.getToken(), R.drawable.placeholder);
|
||||||
.addHeader("Authorization", "Bearer " + tokenManager.getToken())
|
|
||||||
.build());
|
|
||||||
|
|
||||||
Glide.with(this)
|
|
||||||
.load(glideUrl)
|
|
||||||
.into(imageView);
|
|
||||||
|
|
||||||
closeButton.setOnClickListener(v -> dialog.dismiss());
|
closeButton.setOnClickListener(v -> dialog.dismiss());
|
||||||
|
downloadButton.setOnClickListener(v -> downloadFile(message));
|
||||||
imageView.setOnClickListener(v -> dialog.dismiss());
|
imageView.setOnClickListener(v -> dialog.dismiss());
|
||||||
dialog.show();
|
dialog.show();
|
||||||
}
|
}
|
||||||
@@ -176,6 +171,8 @@ public class ChatFragment extends Fragment implements ChatAdapter.OnChatClickLis
|
|||||||
private void downloadFile(Message message) {
|
private void downloadFile(Message message) {
|
||||||
if (message.getId() == null) return;
|
if (message.getId() == null) return;
|
||||||
|
|
||||||
|
DialogUtils.showConfirmDialog(requireContext(), "Download Attachment",
|
||||||
|
"Do you want to download \"" + message.getAttachmentName() + "\"?", () -> {
|
||||||
Toast.makeText(requireContext(), "Downloading " + message.getAttachmentName() + "...", Toast.LENGTH_SHORT).show();
|
Toast.makeText(requireContext(), "Downloading " + message.getAttachmentName() + "...", Toast.LENGTH_SHORT).show();
|
||||||
|
|
||||||
viewModel.downloadAttachment(message.getId()).observe(getViewLifecycleOwner(), resource -> {
|
viewModel.downloadAttachment(message.getId()).observe(getViewLifecycleOwner(), resource -> {
|
||||||
@@ -185,6 +182,7 @@ public class ChatFragment extends Fragment implements ChatAdapter.OnChatClickLis
|
|||||||
Toast.makeText(requireContext(), "Download failed: " + resource.message, Toast.LENGTH_SHORT).show();
|
Toast.makeText(requireContext(), "Download failed: " + resource.message, Toast.LENGTH_SHORT).show();
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
private void saveFileToDownloads(ResponseBody body, String fileName, String mimeType) {
|
private void saveFileToDownloads(ResponseBody body, String fileName, String mimeType) {
|
||||||
@@ -346,12 +344,15 @@ public class ChatFragment extends Fragment implements ChatAdapter.OnChatClickLis
|
|||||||
}
|
}
|
||||||
|
|
||||||
String text = binding.etMessage.getText().toString().trim();
|
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);
|
String mimeType = requireContext().getContentResolver().getType(uri);
|
||||||
if (mimeType == null) mimeType = "application/octet-stream";
|
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);
|
MultipartBody.Part filePart = MultipartBody.Part.createFormData("file", file.getName(), filePartBody);
|
||||||
|
|
||||||
binding.etMessage.setText("");
|
binding.etMessage.setText("");
|
||||||
@@ -424,9 +425,7 @@ public class ChatFragment extends Fragment implements ChatAdapter.OnChatClickLis
|
|||||||
}
|
}
|
||||||
|
|
||||||
private void setConversationActive(boolean active) {
|
private void setConversationActive(boolean active) {
|
||||||
binding.btnSend.setEnabled(active);
|
UIUtils.setViewsEnabled(active, binding.btnSend, binding.etMessage, binding.btnAttach);
|
||||||
binding.etMessage.setEnabled(active);
|
|
||||||
binding.btnAttach.setEnabled(active);
|
|
||||||
if (!active) {
|
if (!active) {
|
||||||
activeConversationId = null;
|
activeConversationId = null;
|
||||||
ChatNotificationService.activeConversationIdInUi = null;
|
ChatNotificationService.activeConversationIdInUi = null;
|
||||||
|
|||||||
@@ -62,7 +62,7 @@ public class ChatRepository extends BaseRepository {
|
|||||||
/**
|
/**
|
||||||
* Sends a message with an attachment.
|
* 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));
|
return executeCall(messageApi.sendMessageWithAttachment(conversationId, content, attachment));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -91,7 +91,7 @@ public class ChatListViewModel extends ViewModel {
|
|||||||
return chatRepository.sendMessage(conversationId, new SendMessageRequest(text));
|
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);
|
return chatRepository.sendMessageWithAttachment(conversationId, content, attachment);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -10,15 +10,31 @@
|
|||||||
android:layout_height="match_parent"
|
android:layout_height="match_parent"
|
||||||
android:scaleType="fitCenter" />
|
android:scaleType="fitCenter" />
|
||||||
|
|
||||||
|
<LinearLayout
|
||||||
|
android:layout_width="wrap_content"
|
||||||
|
android:layout_height="wrap_content"
|
||||||
|
android:layout_gravity="top|end"
|
||||||
|
android:layout_margin="16dp"
|
||||||
|
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
|
<ImageButton
|
||||||
android:id="@+id/btnClose"
|
android:id="@+id/btnClose"
|
||||||
android:layout_width="48dp"
|
android:layout_width="48dp"
|
||||||
android:layout_height="48dp"
|
android:layout_height="48dp"
|
||||||
android:layout_gravity="top|end"
|
android:layout_marginLeft="8dp"
|
||||||
android:layout_margin="16dp"
|
android:background="@android:color/transparent"
|
||||||
android:background="?attr/selectableItemBackgroundBorderless"
|
|
||||||
android:src="@android:drawable/ic_menu_close_clear_cancel"
|
android:src="@android:drawable/ic_menu_close_clear_cancel"
|
||||||
android:contentDescription="Close"
|
android:contentDescription="Close"
|
||||||
android:tint="@android:color/white" />
|
android:tint="@android:color/white" />
|
||||||
|
</LinearLayout>
|
||||||
|
|
||||||
</FrameLayout>
|
</FrameLayout>
|
||||||
@@ -95,9 +95,10 @@ public class ChatController {
|
|||||||
@PreAuthorize("hasAnyRole('CUSTOMER', 'STAFF', 'ADMIN')")
|
@PreAuthorize("hasAnyRole('CUSTOMER', 'STAFF', 'ADMIN')")
|
||||||
public ResponseEntity<MessageResponse> sendMessageWithAttachment(
|
public ResponseEntity<MessageResponse> sendMessageWithAttachment(
|
||||||
@PathVariable Long id,
|
@PathVariable Long id,
|
||||||
@RequestParam("file") MultipartFile file) {
|
@RequestParam("file") MultipartFile file,
|
||||||
|
@RequestParam(value = "content", required = false) String content) {
|
||||||
User user = getCurrentUser();
|
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.publishMessage(id, message);
|
||||||
chatRealtimeService.publishConversationUpdate(id);
|
chatRealtimeService.publishConversationUpdate(id);
|
||||||
return ResponseEntity.status(HttpStatus.CREATED).body(message);
|
return ResponseEntity.status(HttpStatus.CREATED).body(message);
|
||||||
|
|||||||
@@ -151,7 +151,7 @@ public class ChatService {
|
|||||||
}
|
}
|
||||||
|
|
||||||
@Transactional
|
@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)
|
Conversation conversation = conversationRepository.findById(conversationId)
|
||||||
.orElseThrow(() -> new ResourceNotFoundException("Conversation not found"));
|
.orElseThrow(() -> new ResourceNotFoundException("Conversation not found"));
|
||||||
|
|
||||||
@@ -173,6 +173,7 @@ public class ChatService {
|
|||||||
Message message = new Message();
|
Message message = new Message();
|
||||||
message.setConversationId(conversationId);
|
message.setConversationId(conversationId);
|
||||||
message.setSenderId(userId);
|
message.setSenderId(userId);
|
||||||
|
message.setContent(content);
|
||||||
message.setAttachmentUrl(attachmentUrl);
|
message.setAttachmentUrl(attachmentUrl);
|
||||||
message.setAttachmentName(file.getOriginalFilename());
|
message.setAttachmentName(file.getOriginalFilename());
|
||||||
message.setAttachmentMimeType(file.getContentType());
|
message.setAttachmentMimeType(file.getContentType());
|
||||||
|
|||||||
@@ -55,3 +55,9 @@ logging:
|
|||||||
com.petshop: ${LOG_LEVEL:INFO}
|
com.petshop: ${LOG_LEVEL:INFO}
|
||||||
org.springframework.security: ${LOG_LEVEL_SECURITY:WARN}
|
org.springframework.security: ${LOG_LEVEL_SECURITY:WARN}
|
||||||
org.springdoc.core.events.SpringDocAppInitializer: ERROR
|
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