diff --git a/android/app/src/main/java/com/example/petstoremobile/dtos/MessageDTO.java b/android/app/src/main/java/com/example/petstoremobile/dtos/MessageDTO.java index 1080b600..fea4cf66 100644 --- a/android/app/src/main/java/com/example/petstoremobile/dtos/MessageDTO.java +++ b/android/app/src/main/java/com/example/petstoremobile/dtos/MessageDTO.java @@ -22,6 +22,15 @@ public class MessageDTO { @SerializedName("isRead") private Boolean isRead; + @SerializedName("attachmentUrl") + private String attachmentUrl; + + @SerializedName("attachmentName") + private String attachmentName; + + @SerializedName("attachmentType") + private String attachmentType; + public MessageDTO() {} public Long getId() { return id; } @@ -41,4 +50,13 @@ public class MessageDTO { public Boolean getIsRead() { return isRead; } public void setIsRead(Boolean isRead) { this.isRead = isRead; } + + public String getAttachmentUrl() { return attachmentUrl; } + public void setAttachmentUrl(String attachmentUrl) { this.attachmentUrl = attachmentUrl; } + + public String getAttachmentName() { return attachmentName; } + public void setAttachmentName(String attachmentName) { this.attachmentName = attachmentName; } + + public String getAttachmentType() { return attachmentType; } + public void setAttachmentType(String attachmentType) { this.attachmentType = attachmentType; } } \ No newline at end of file diff --git a/android/app/src/main/java/com/example/petstoremobile/fragments/ChatFragment.java b/android/app/src/main/java/com/example/petstoremobile/fragments/ChatFragment.java index 0e405c63..f52d5ea6 100644 --- a/android/app/src/main/java/com/example/petstoremobile/fragments/ChatFragment.java +++ b/android/app/src/main/java/com/example/petstoremobile/fragments/ChatFragment.java @@ -1,15 +1,23 @@ package com.example.petstoremobile.fragments; +import android.app.Activity; +import android.content.Intent; +import android.database.Cursor; +import android.net.Uri; import android.os.Bundle; +import android.provider.OpenableColumns; import android.util.Log; import android.view.*; import android.widget.*; +import androidx.activity.result.ActivityResultLauncher; +import androidx.activity.result.contract.ActivityResultContracts; import androidx.annotation.NonNull; import androidx.core.view.GravityCompat; import androidx.drawerlayout.widget.DrawerLayout; import androidx.fragment.app.Fragment; import androidx.recyclerview.widget.LinearLayoutManager; import androidx.recyclerview.widget.RecyclerView; +import com.bumptech.glide.Glide; import com.example.petstoremobile.R; import com.example.petstoremobile.adapters.ChatAdapter; import com.example.petstoremobile.adapters.MessageAdapter; @@ -41,8 +49,15 @@ public class ChatFragment extends Fragment implements ChatAdapter.OnChatClickLis private RecyclerView rvChatList, rvMessages; private EditText etMessage; private Button btnSend; + private ImageButton btnAttach; private TextView tvChatTitle; + // Preview views + private View layoutAttachmentPreview; + private ImageView ivPreview; + private TextView tvPreviewName; + private ImageButton btnRemoveAttachment; + // Adapters private ChatAdapter chatAdapter; private MessageAdapter messageAdapter; @@ -51,6 +66,7 @@ public class ChatFragment extends Fragment implements ChatAdapter.OnChatClickLis private final List chatList = new ArrayList<>(); private final List messageList = new ArrayList<>(); private final Map customerNames = new HashMap<>(); + private Uri pendingAttachmentUri; // APIs private ChatApi chatApi; @@ -61,6 +77,24 @@ public class ChatFragment extends Fragment implements ChatAdapter.OnChatClickLis private Long currentUserId; private Long activeConversationId; private StompChatManager stompChatManager; + private ActivityResultLauncher attachmentLauncher; + + + @Override + public void onCreate(Bundle savedInstanceState) { + super.onCreate(savedInstanceState); + attachmentLauncher = registerForActivityResult( + new ActivityResultContracts.StartActivityForResult(), + result -> { + if (result.getResultCode() == Activity.RESULT_OK && result.getData() != null) { + Uri uri = result.getData().getData(); + if (uri != null) { + showAttachmentPreview(uri); + } + } + } + ); + } @Override public View onCreateView(@NonNull LayoutInflater inflater, @@ -77,11 +111,28 @@ public class ChatFragment extends Fragment implements ChatAdapter.OnChatClickLis rvMessages = view.findViewById(R.id.rvMessages); etMessage = view.findViewById(R.id.etMessage); btnSend = view.findViewById(R.id.btnSend); + btnAttach = view.findViewById(R.id.btnAttach); tvChatTitle = view.findViewById(R.id.tvChatTitle); + layoutAttachmentPreview = view.findViewById(R.id.layoutAttachmentPreview); + ivPreview = view.findViewById(R.id.ivPreview); + tvPreviewName = view.findViewById(R.id.tvPreviewName); + btnRemoveAttachment = view.findViewById(R.id.btnRemoveAttachment); + ImageButton hamburger = view.findViewById(R.id.btnHamburger); hamburger.setOnClickListener(v -> drawerLayout.openDrawer(GravityCompat.START)); - btnSend.setOnClickListener(v -> sendMessage()); + //When the send button is clicked check if there is an attachment and send using the correct helper function + btnSend.setOnClickListener(v -> { + if (pendingAttachmentUri != null) { + sendWithAttachment(pendingAttachmentUri); + } else { + sendMessage(); + } + }); + + //When the attachment button is clicked open the file picker + btnAttach.setOnClickListener(v -> selectAttachment()); + btnRemoveAttachment.setOnClickListener(v -> removeAttachment()); setupRecyclerViews(); loadInitialData(); @@ -89,6 +140,7 @@ public class ChatFragment extends Fragment implements ChatAdapter.OnChatClickLis return view; } + // Helper function to setup recycler views for chat and messages private void setupRecyclerViews() { // Set up Drawer menu to select conversation chatAdapter = new ChatAdapter(chatList, this); @@ -273,6 +325,68 @@ public class ChatFragment extends Fragment implements ChatAdapter.OnChatClickLis }); } + //Helper function to open file picker when the attachment button is clicked + private void selectAttachment() { + Intent intent = new Intent(Intent.ACTION_GET_CONTENT); + intent.setType("*/*"); + attachmentLauncher.launch(intent); + } + + //Helper function to show the attachment preview + private void showAttachmentPreview(Uri uri) { + pendingAttachmentUri = uri; + layoutAttachmentPreview.setVisibility(View.VISIBLE); + + String mimeType = requireContext().getContentResolver().getType(uri); + String fileName = getFileName(uri); + tvPreviewName.setText(fileName); + + // If the file is an image, display a thumbnail of the image as well + if (mimeType != null && mimeType.startsWith("image/")) { + ivPreview.setVisibility(View.VISIBLE); + Glide.with(this).load(uri).into(ivPreview); + } else { + ivPreview.setVisibility(View.GONE); + } + } + + //Helper function to remove the attachment + private void removeAttachment() { + pendingAttachmentUri = null; + layoutAttachmentPreview.setVisibility(View.GONE); + } + + //Helper function to get the file name from the uri to display in attachment preview + private String getFileName(Uri uri) { + String result = null; + if (uri.getScheme().equals("content")) { + try (Cursor cursor = requireContext().getContentResolver().query(uri, null, null, null, null)) { + if (cursor != null && cursor.moveToFirst()) { + int index = cursor.getColumnIndex(OpenableColumns.DISPLAY_NAME); + if (index != -1) { + result = cursor.getString(index); + } + } + } + } + if (result == null) { + result = uri.getPath(); + int cut = result.lastIndexOf('/'); + if (cut != -1) { + result = result.substring(cut + 1); + } + } + return result; + } + + //Helper function to send the message with attachment + private void sendWithAttachment(Uri uri) { + if (activeConversationId == null) return; + + //TODO: send the message with attachment when backend is done + Log.d(TAG, "Send with attachment happening"); + } + // When a message is received updates the chat preview @Override public void onMessageReceived(MessageDTO dto) { @@ -370,6 +484,9 @@ public class ChatFragment extends Fragment implements ChatAdapter.OnChatClickLis m.setContent(dto.getContent()); m.setTimestamp(dto.getTimestamp()); m.setIsRead(dto.getIsRead()); + m.setAttachmentUrl(dto.getAttachmentUrl()); + m.setAttachmentName(dto.getAttachmentName()); + m.setAttachmentType(dto.getAttachmentType()); return m; } @@ -407,9 +524,11 @@ public class ChatFragment extends Fragment implements ChatAdapter.OnChatClickLis private void setConversationActive(boolean active) { btnSend.setEnabled(active); etMessage.setEnabled(active); + btnAttach.setEnabled(active); if (!active) { activeConversationId = null; ChatNotificationService.activeConversationIdInUi = null; + removeAttachment(); if (tvChatTitle != null) tvChatTitle.setText("Customer Chat"); if (stompChatManager != null) { stompChatManager.clearConversationSubscription(); diff --git a/android/app/src/main/java/com/example/petstoremobile/models/Message.java b/android/app/src/main/java/com/example/petstoremobile/models/Message.java index 18ec549a..bf76b4c4 100644 --- a/android/app/src/main/java/com/example/petstoremobile/models/Message.java +++ b/android/app/src/main/java/com/example/petstoremobile/models/Message.java @@ -7,6 +7,9 @@ public class Message { private String content; private String timestamp; private Boolean isRead; + private String attachmentUrl; + private String attachmentName; + private String attachmentType; public Message() {} @@ -33,4 +36,13 @@ public class Message { public Boolean getIsRead() { return isRead; } public void setIsRead(Boolean isRead) { this.isRead = isRead; } + + public String getAttachmentUrl() { return attachmentUrl; } + public void setAttachmentUrl(String attachmentUrl) { this.attachmentUrl = attachmentUrl; } + + public String getAttachmentName() { return attachmentName; } + public void setAttachmentName(String attachmentName) { this.attachmentName = attachmentName; } + + public String getAttachmentType() { return attachmentType; } + public void setAttachmentType(String attachmentType) { this.attachmentType = attachmentType; } } \ No newline at end of file diff --git a/android/app/src/main/res/layout/fragment_chat.xml b/android/app/src/main/res/layout/fragment_chat.xml index 875bccfd..786f53bc 100644 --- a/android/app/src/main/res/layout/fragment_chat.xml +++ b/android/app/src/main/res/layout/fragment_chat.xml @@ -49,12 +49,59 @@ android:clipToPadding="false" /> + + + + + + + + + + + + - + android:padding="8dp" + android:maxWidth="300dp"> + + + + + + + \ No newline at end of file diff --git a/android/app/src/main/res/layout/item_message_sent.xml b/android/app/src/main/res/layout/item_message_sent.xml index ab99c033..19b221b0 100644 --- a/android/app/src/main/res/layout/item_message_sent.xml +++ b/android/app/src/main/res/layout/item_message_sent.xml @@ -6,14 +6,38 @@ android:padding="8dp" android:gravity="end"> - + android:padding="8dp" + android:maxWidth="300dp"> + + + + + + + \ No newline at end of file