Added Helper class and commented most fragments
This commit is contained in:
@@ -38,6 +38,9 @@ public class HomeActivity extends AppCompatActivity {
|
|||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Sets up the home screen, initializes bottom navigation, and handles incoming navigation intents.
|
||||||
|
*/
|
||||||
@Override
|
@Override
|
||||||
protected void onCreate(Bundle savedInstanceState) {
|
protected void onCreate(Bundle savedInstanceState) {
|
||||||
EdgeToEdge.enable(this);
|
EdgeToEdge.enable(this);
|
||||||
@@ -71,12 +74,18 @@ public class HomeActivity extends AppCompatActivity {
|
|||||||
requestNotificationPermission();
|
requestNotificationPermission();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Handles new intents received while the activity is already running (like notifications).
|
||||||
|
*/
|
||||||
@Override
|
@Override
|
||||||
protected void onNewIntent(Intent intent) {
|
protected void onNewIntent(Intent intent) {
|
||||||
super.onNewIntent(intent);
|
super.onNewIntent(intent);
|
||||||
handleIntent(intent);
|
handleIntent(intent);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Processes the intent to determine if specific navigation (like opening a chat) is required.
|
||||||
|
*/
|
||||||
private void handleIntent(Intent intent) {
|
private void handleIntent(Intent intent) {
|
||||||
if (intent != null && "chat".equals(intent.getStringExtra("navigate_to"))) {
|
if (intent != null && "chat".equals(intent.getStringExtra("navigate_to"))) {
|
||||||
Bundle args = new Bundle();
|
Bundle args = new Bundle();
|
||||||
@@ -90,14 +99,17 @@ public class HomeActivity extends AppCompatActivity {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// Function to start the notification service in the background
|
/**
|
||||||
// to receive notifications when a new conversation is created
|
* Starts the background service responsible for monitoring chat notifications.
|
||||||
|
*/
|
||||||
private void startNotificationService() {
|
private void startNotificationService() {
|
||||||
Intent serviceIntent = new Intent(this, ChatNotificationService.class);
|
Intent serviceIntent = new Intent(this, ChatNotificationService.class);
|
||||||
startService(serviceIntent);
|
startService(serviceIntent);
|
||||||
}
|
}
|
||||||
|
|
||||||
//Function to request for notification permission
|
/**
|
||||||
|
* Requests POST_NOTIFICATIONS permission from the user if running on Android 13 and above.
|
||||||
|
*/
|
||||||
private void requestNotificationPermission() {
|
private void requestNotificationPermission() {
|
||||||
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.TIRAMISU) {
|
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.TIRAMISU) {
|
||||||
if (ContextCompat.checkSelfPermission(this, Manifest.permission.POST_NOTIFICATIONS) != PackageManager.PERMISSION_GRANTED) {
|
if (ContextCompat.checkSelfPermission(this, Manifest.permission.POST_NOTIFICATIONS) != PackageManager.PERMISSION_GRANTED) {
|
||||||
|
|||||||
@@ -39,6 +39,9 @@ public class MainActivity extends AppCompatActivity {
|
|||||||
@Inject TokenManager tokenManager;
|
@Inject TokenManager tokenManager;
|
||||||
@Inject @Named("baseUrl") String baseUrl;
|
@Inject @Named("baseUrl") String baseUrl;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Initializes the activity, sets up the UI, and checks for an existing login session.
|
||||||
|
*/
|
||||||
@Override
|
@Override
|
||||||
protected void onCreate(Bundle savedInstanceState) {
|
protected void onCreate(Bundle savedInstanceState) {
|
||||||
EdgeToEdge.enable(this);
|
EdgeToEdge.enable(this);
|
||||||
@@ -98,6 +101,9 @@ public class MainActivity extends AppCompatActivity {
|
|||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Executes the login process using the AuthViewModel and handles the authentication response.
|
||||||
|
*/
|
||||||
private void performLogin(String username, String password) {
|
private void performLogin(String username, String password) {
|
||||||
viewModel.login(username, password).observe(this, resource -> {
|
viewModel.login(username, password).observe(this, resource -> {
|
||||||
if (resource == null) return;
|
if (resource == null) return;
|
||||||
@@ -129,6 +135,9 @@ public class MainActivity extends AppCompatActivity {
|
|||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Retrieves the logged-in user's profile information to save their ID before navigating to the home screen.
|
||||||
|
*/
|
||||||
private void fetchUserIdAndNavigate() {
|
private void fetchUserIdAndNavigate() {
|
||||||
viewModel.getMe().observe(this, resource -> {
|
viewModel.getMe().observe(this, resource -> {
|
||||||
if (resource != null && resource.status != Resource.Status.LOADING) {
|
if (resource != null && resource.status != Resource.Status.LOADING) {
|
||||||
|
|||||||
@@ -9,13 +9,10 @@ import android.widget.TextView;
|
|||||||
import androidx.annotation.NonNull;
|
import androidx.annotation.NonNull;
|
||||||
import androidx.recyclerview.widget.RecyclerView;
|
import androidx.recyclerview.widget.RecyclerView;
|
||||||
|
|
||||||
import com.bumptech.glide.Glide;
|
|
||||||
import com.bumptech.glide.load.engine.DiskCacheStrategy;
|
|
||||||
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.api.PetApi;
|
import com.example.petstoremobile.api.PetApi;
|
||||||
import com.example.petstoremobile.dtos.PetDTO;
|
import com.example.petstoremobile.dtos.PetDTO;
|
||||||
|
import com.example.petstoremobile.utils.GlideUtils;
|
||||||
import java.util.List;
|
import java.util.List;
|
||||||
|
|
||||||
public class PetAdapter extends RecyclerView.Adapter<PetAdapter.PetViewHolder> {
|
public class PetAdapter extends RecyclerView.Adapter<PetAdapter.PetViewHolder> {
|
||||||
@@ -93,25 +90,10 @@ public class PetAdapter extends RecyclerView.Adapter<PetAdapter.PetViewHolder> {
|
|||||||
holder.tvPetStatus.setBackgroundColor(Color.parseColor("#F44336"));
|
holder.tvPetStatus.setBackgroundColor(Color.parseColor("#F44336"));
|
||||||
}
|
}
|
||||||
|
|
||||||
// Load pet image using Glide with circle crop
|
// Load pet image using Glide
|
||||||
if (baseUrl != null) {
|
if (baseUrl != null) {
|
||||||
String imageUrl = baseUrl + String.format(PetApi.PET_IMAGE_PATH, pet.getPetId());
|
String imageUrl = baseUrl + String.format(PetApi.PET_IMAGE_PATH, pet.getPetId());
|
||||||
|
GlideUtils.loadImageWithTokenCircle(holder.itemView.getContext(), holder.ivPetProfile, imageUrl, token, R.drawable.placeholder);
|
||||||
Object loadTarget = imageUrl;
|
|
||||||
if (token != null) {
|
|
||||||
loadTarget = new GlideUrl(imageUrl, new LazyHeaders.Builder()
|
|
||||||
.addHeader("Authorization", "Bearer " + token)
|
|
||||||
.build());
|
|
||||||
}
|
|
||||||
|
|
||||||
Glide.with(holder.itemView.getContext())
|
|
||||||
.load(loadTarget)
|
|
||||||
.circleCrop()
|
|
||||||
.skipMemoryCache(true)
|
|
||||||
.diskCacheStrategy(DiskCacheStrategy.NONE)
|
|
||||||
.placeholder(R.drawable.placeholder)
|
|
||||||
.error(R.drawable.placeholder)
|
|
||||||
.into(holder.ivPetProfile);
|
|
||||||
} else {
|
} else {
|
||||||
holder.ivPetProfile.setImageResource(R.drawable.placeholder);
|
holder.ivPetProfile.setImageResource(R.drawable.placeholder);
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -6,13 +6,10 @@ import android.widget.TextView;
|
|||||||
import androidx.annotation.NonNull;
|
import androidx.annotation.NonNull;
|
||||||
import androidx.recyclerview.widget.RecyclerView;
|
import androidx.recyclerview.widget.RecyclerView;
|
||||||
|
|
||||||
import com.bumptech.glide.Glide;
|
|
||||||
import com.bumptech.glide.load.engine.DiskCacheStrategy;
|
|
||||||
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.api.ProductApi;
|
import com.example.petstoremobile.api.ProductApi;
|
||||||
import com.example.petstoremobile.dtos.ProductDTO;
|
import com.example.petstoremobile.dtos.ProductDTO;
|
||||||
|
import com.example.petstoremobile.utils.GlideUtils;
|
||||||
import java.util.List;
|
import java.util.List;
|
||||||
|
|
||||||
public class ProductAdapter extends RecyclerView.Adapter<ProductAdapter.ProductViewHolder> {
|
public class ProductAdapter extends RecyclerView.Adapter<ProductAdapter.ProductViewHolder> {
|
||||||
@@ -72,22 +69,7 @@ public class ProductAdapter extends RecyclerView.Adapter<ProductAdapter.ProductV
|
|||||||
// Load product image using Glide
|
// Load product image using Glide
|
||||||
if (baseUrl != null) {
|
if (baseUrl != null) {
|
||||||
String imageUrl = baseUrl + String.format(ProductApi.PRODUCT_IMAGE_PATH, p.getProdId());
|
String imageUrl = baseUrl + String.format(ProductApi.PRODUCT_IMAGE_PATH, p.getProdId());
|
||||||
|
GlideUtils.loadImageWithTokenCircle(holder.itemView.getContext(), holder.ivProductImage, imageUrl, token, R.drawable.placeholder);
|
||||||
Object loadTarget = imageUrl;
|
|
||||||
if (token != null) {
|
|
||||||
loadTarget = new GlideUrl(imageUrl, new LazyHeaders.Builder()
|
|
||||||
.addHeader("Authorization", "Bearer " + token)
|
|
||||||
.build());
|
|
||||||
}
|
|
||||||
|
|
||||||
Glide.with(holder.itemView.getContext())
|
|
||||||
.load(loadTarget)
|
|
||||||
.circleCrop()
|
|
||||||
.skipMemoryCache(true)
|
|
||||||
.diskCacheStrategy(DiskCacheStrategy.NONE)
|
|
||||||
.placeholder(R.drawable.placeholder)
|
|
||||||
.error(R.drawable.placeholder)
|
|
||||||
.into(holder.ivProductImage);
|
|
||||||
} else {
|
} else {
|
||||||
holder.ivProductImage.setImageResource(R.drawable.placeholder);
|
holder.ivProductImage.setImageResource(R.drawable.placeholder);
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -88,7 +88,9 @@ public class ChatFragment extends Fragment implements ChatAdapter.OnChatClickLis
|
|||||||
private StompChatManager stompChatManager;
|
private StompChatManager stompChatManager;
|
||||||
private ActivityResultLauncher<Intent> attachmentLauncher;
|
private ActivityResultLauncher<Intent> attachmentLauncher;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Initializes the attachment launcher to handle file selection from the gallery.
|
||||||
|
*/
|
||||||
@Override
|
@Override
|
||||||
public void onCreate(Bundle savedInstanceState) {
|
public void onCreate(Bundle savedInstanceState) {
|
||||||
super.onCreate(savedInstanceState);
|
super.onCreate(savedInstanceState);
|
||||||
@@ -105,6 +107,9 @@ public class ChatFragment extends Fragment implements ChatAdapter.OnChatClickLis
|
|||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Inflates the layout, initializes UI components, and sets up click listeners for messaging.
|
||||||
|
*/
|
||||||
@Override
|
@Override
|
||||||
public View onCreateView(@NonNull LayoutInflater inflater,
|
public View onCreateView(@NonNull LayoutInflater inflater,
|
||||||
ViewGroup container, Bundle savedInstanceState) {
|
ViewGroup container, Bundle savedInstanceState) {
|
||||||
@@ -155,7 +160,9 @@ public class ChatFragment extends Fragment implements ChatAdapter.OnChatClickLis
|
|||||||
return view;
|
return view;
|
||||||
}
|
}
|
||||||
|
|
||||||
// Helper function to setup recycler views for chat and messages
|
/**
|
||||||
|
* Configures the RecyclerViews for the conversation list and the message history.
|
||||||
|
*/
|
||||||
private void setupRecyclerViews() {
|
private void setupRecyclerViews() {
|
||||||
// Set up Drawer menu to select conversation
|
// Set up Drawer menu to select conversation
|
||||||
chatAdapter = new ChatAdapter(chatList, this);
|
chatAdapter = new ChatAdapter(chatList, this);
|
||||||
@@ -171,7 +178,9 @@ public class ChatFragment extends Fragment implements ChatAdapter.OnChatClickLis
|
|||||||
setConversationActive(false);
|
setConversationActive(false);
|
||||||
}
|
}
|
||||||
|
|
||||||
//Helper function to load token and user id then connect to websocket
|
/**
|
||||||
|
* Loads authentication tokens and user info, then initializes the Stomp WebSocket connection.
|
||||||
|
*/
|
||||||
private void loadInitialData() {
|
private void loadInitialData() {
|
||||||
String token = tokenManager.getToken();
|
String token = tokenManager.getToken();
|
||||||
currentUserId = tokenManager.getUserId();
|
currentUserId = tokenManager.getUserId();
|
||||||
@@ -198,7 +207,9 @@ public class ChatFragment extends Fragment implements ChatAdapter.OnChatClickLis
|
|||||||
loadCustomers();
|
loadCustomers();
|
||||||
}
|
}
|
||||||
|
|
||||||
//Helper function to load customer names for it to be displayed on drawer menu
|
/**
|
||||||
|
* Fetches a list of customers from the API to display customer names for the chat list.
|
||||||
|
*/
|
||||||
private void loadCustomers() {
|
private void loadCustomers() {
|
||||||
customerApi.getAllCustomers(0, 100).enqueue(new Callback<PageResponse<CustomerDTO>>() {
|
customerApi.getAllCustomers(0, 100).enqueue(new Callback<PageResponse<CustomerDTO>>() {
|
||||||
@Override
|
@Override
|
||||||
@@ -220,7 +231,9 @@ public class ChatFragment extends Fragment implements ChatAdapter.OnChatClickLis
|
|||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
//helper function to load conversations entities to display with customer names in drawer menu
|
/**
|
||||||
|
* Retrieves all conversations for the current user and populates the chat drawer.
|
||||||
|
*/
|
||||||
private void loadConversations() {
|
private void loadConversations() {
|
||||||
chatApi.getAllConversations().enqueue(new Callback<List<ConversationDTO>>() {
|
chatApi.getAllConversations().enqueue(new Callback<List<ConversationDTO>>() {
|
||||||
@Override
|
@Override
|
||||||
@@ -268,8 +281,9 @@ public class ChatFragment extends Fragment implements ChatAdapter.OnChatClickLis
|
|||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
// Called when user taps a chat in the drawer
|
/**
|
||||||
// Loads messages for that chat selected
|
* Handles selection of a chat from the drawer, updating the UI and subscribing to the WebSocket.
|
||||||
|
*/
|
||||||
@Override
|
@Override
|
||||||
public void onChatClick(Chat chat) {
|
public void onChatClick(Chat chat) {
|
||||||
activeConversationId = Long.parseLong(chat.getChatId());
|
activeConversationId = Long.parseLong(chat.getChatId());
|
||||||
@@ -284,7 +298,9 @@ public class ChatFragment extends Fragment implements ChatAdapter.OnChatClickLis
|
|||||||
loadMessageHistory(activeConversationId);
|
loadMessageHistory(activeConversationId);
|
||||||
}
|
}
|
||||||
|
|
||||||
//helper function to load messages for selected chat
|
/**
|
||||||
|
* Fetches the full message history for a specific conversation from the API.
|
||||||
|
*/
|
||||||
private void loadMessageHistory(Long conversationId) {
|
private void loadMessageHistory(Long conversationId) {
|
||||||
messageApi.getMessages(conversationId).enqueue(new Callback<List<MessageDTO>>() {
|
messageApi.getMessages(conversationId).enqueue(new Callback<List<MessageDTO>>() {
|
||||||
@Override
|
@Override
|
||||||
@@ -307,7 +323,9 @@ public class ChatFragment extends Fragment implements ChatAdapter.OnChatClickLis
|
|||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
//Helper function to send a message to the chat
|
/**
|
||||||
|
* Sends a plain text message to the currently active conversation.
|
||||||
|
*/
|
||||||
private void sendMessage() {
|
private void sendMessage() {
|
||||||
//check if a chat is selected
|
//check if a chat is selected
|
||||||
if (activeConversationId == null) return;
|
if (activeConversationId == null) return;
|
||||||
@@ -340,14 +358,18 @@ public class ChatFragment extends Fragment implements ChatAdapter.OnChatClickLis
|
|||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
//Helper function to open file picker when the attachment button is clicked
|
/**
|
||||||
|
* Launches a file picker intent to select an attachment for the message.
|
||||||
|
*/
|
||||||
private void selectAttachment() {
|
private void selectAttachment() {
|
||||||
Intent intent = new Intent(Intent.ACTION_GET_CONTENT);
|
Intent intent = new Intent(Intent.ACTION_GET_CONTENT);
|
||||||
intent.setType("*/*");
|
intent.setType("*/*");
|
||||||
attachmentLauncher.launch(intent);
|
attachmentLauncher.launch(intent);
|
||||||
}
|
}
|
||||||
|
|
||||||
//Helper function to show the attachment preview
|
/**
|
||||||
|
* Displays a preview of the selected attachment in the UI.
|
||||||
|
*/
|
||||||
private void showAttachmentPreview(Uri uri) {
|
private void showAttachmentPreview(Uri uri) {
|
||||||
pendingAttachmentUri = uri;
|
pendingAttachmentUri = uri;
|
||||||
layoutAttachmentPreview.setVisibility(View.VISIBLE);
|
layoutAttachmentPreview.setVisibility(View.VISIBLE);
|
||||||
@@ -365,13 +387,17 @@ public class ChatFragment extends Fragment implements ChatAdapter.OnChatClickLis
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
//Helper function to remove the attachment
|
/**
|
||||||
|
* Clears the current attachment selection and hides the preview UI.
|
||||||
|
*/
|
||||||
private void removeAttachment() {
|
private void removeAttachment() {
|
||||||
pendingAttachmentUri = null;
|
pendingAttachmentUri = null;
|
||||||
layoutAttachmentPreview.setVisibility(View.GONE);
|
layoutAttachmentPreview.setVisibility(View.GONE);
|
||||||
}
|
}
|
||||||
|
|
||||||
//Helper function to get the file name from the uri to display in attachment preview
|
/**
|
||||||
|
* Show the display name of the file from its Uri.
|
||||||
|
*/
|
||||||
private String getFileName(Uri uri) {
|
private String getFileName(Uri uri) {
|
||||||
String result = null;
|
String result = null;
|
||||||
if (uri.getScheme().equals("content")) {
|
if (uri.getScheme().equals("content")) {
|
||||||
@@ -394,7 +420,9 @@ public class ChatFragment extends Fragment implements ChatAdapter.OnChatClickLis
|
|||||||
return result;
|
return result;
|
||||||
}
|
}
|
||||||
|
|
||||||
//Helper function to send the message with attachment
|
/**
|
||||||
|
* Handles sending a message that includes a file attachment.
|
||||||
|
*/
|
||||||
private void sendWithAttachment(Uri uri) {
|
private void sendWithAttachment(Uri uri) {
|
||||||
if (activeConversationId == null) return;
|
if (activeConversationId == null) return;
|
||||||
|
|
||||||
@@ -402,7 +430,9 @@ public class ChatFragment extends Fragment implements ChatAdapter.OnChatClickLis
|
|||||||
Log.d(TAG, "Send with attachment happening");
|
Log.d(TAG, "Send with attachment happening");
|
||||||
}
|
}
|
||||||
|
|
||||||
// When a message is received updates the chat preview
|
/**
|
||||||
|
* Callback triggered when a new message is received via the WebSocket.
|
||||||
|
*/
|
||||||
@Override
|
@Override
|
||||||
public void onMessageReceived(MessageDTO dto) {
|
public void onMessageReceived(MessageDTO dto) {
|
||||||
//if there is no active selected conversation or the message received is for another chat, then just update the preview of last message
|
//if there is no active selected conversation or the message received is for another chat, then just update the preview of last message
|
||||||
@@ -420,7 +450,9 @@ public class ChatFragment extends Fragment implements ChatAdapter.OnChatClickLis
|
|||||||
scrollToBottom();
|
scrollToBottom();
|
||||||
}
|
}
|
||||||
|
|
||||||
// When a new conversation is added, updates the chat preview
|
/**
|
||||||
|
* Callback triggered when a conversation is created or updated via the WebSocket.
|
||||||
|
*/
|
||||||
@Override
|
@Override
|
||||||
public void onConversationUpdated(ConversationDTO dto) {
|
public void onConversationUpdated(ConversationDTO dto) {
|
||||||
boolean updated = false;
|
boolean updated = false;
|
||||||
@@ -460,6 +492,9 @@ public class ChatFragment extends Fragment implements ChatAdapter.OnChatClickLis
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Callback triggered when the WebSocket connection is successfully opened.
|
||||||
|
*/
|
||||||
@Override
|
@Override
|
||||||
public void onSocketOpened() {
|
public void onSocketOpened() {
|
||||||
if (!isAdded()) {
|
if (!isAdded()) {
|
||||||
@@ -471,6 +506,9 @@ public class ChatFragment extends Fragment implements ChatAdapter.OnChatClickLis
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Callback triggered when the WebSocket connection is closed.
|
||||||
|
*/
|
||||||
@Override
|
@Override
|
||||||
public void onSocketClosed() {
|
public void onSocketClosed() {
|
||||||
if (!isAdded()) {
|
if (!isAdded()) {
|
||||||
@@ -479,6 +517,9 @@ public class ChatFragment extends Fragment implements ChatAdapter.OnChatClickLis
|
|||||||
loadConversations();
|
loadConversations();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Callback triggered when a WebSocket connection error occurs.
|
||||||
|
*/
|
||||||
@Override
|
@Override
|
||||||
public void onSocketError() {
|
public void onSocketError() {
|
||||||
if (!isAdded()) {
|
if (!isAdded()) {
|
||||||
@@ -490,7 +531,9 @@ public class ChatFragment extends Fragment implements ChatAdapter.OnChatClickLis
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// Helper function to convert DTO to message
|
/**
|
||||||
|
* Converts a MessageDTO into a Message object.
|
||||||
|
*/
|
||||||
private Message dtoToModel(MessageDTO dto) {
|
private Message dtoToModel(MessageDTO dto) {
|
||||||
Message m = new Message();
|
Message m = new Message();
|
||||||
m.setId(dto.getId());
|
m.setId(dto.getId());
|
||||||
@@ -505,7 +548,9 @@ public class ChatFragment extends Fragment implements ChatAdapter.OnChatClickLis
|
|||||||
return m;
|
return m;
|
||||||
}
|
}
|
||||||
|
|
||||||
//Helper function to scroll to bottom of the chat
|
/**
|
||||||
|
* Scrolls the message history RecyclerView to the most recent message.
|
||||||
|
*/
|
||||||
private void scrollToBottom() {
|
private void scrollToBottom() {
|
||||||
if (!messageList.isEmpty()) {
|
if (!messageList.isEmpty()) {
|
||||||
rvMessages.post(() ->
|
rvMessages.post(() ->
|
||||||
@@ -513,7 +558,9 @@ public class ChatFragment extends Fragment implements ChatAdapter.OnChatClickLis
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// Helper function to update the chat preview last message
|
/**
|
||||||
|
* Updates the preview snippet of the last message for a specific conversation in the drawer.
|
||||||
|
*/
|
||||||
private void updateConversationPreview(Long conversationId, String lastMessage) {
|
private void updateConversationPreview(Long conversationId, String lastMessage) {
|
||||||
if (conversationId == null) {
|
if (conversationId == null) {
|
||||||
return;
|
return;
|
||||||
@@ -535,7 +582,9 @@ public class ChatFragment extends Fragment implements ChatAdapter.OnChatClickLis
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
//Helper function to enable or disable the send button when there is no active chat
|
/**
|
||||||
|
* Toggles the UI state based on whether a conversation is currently selected.
|
||||||
|
*/
|
||||||
private void setConversationActive(boolean active) {
|
private void setConversationActive(boolean active) {
|
||||||
btnSend.setEnabled(active);
|
btnSend.setEnabled(active);
|
||||||
etMessage.setEnabled(active);
|
etMessage.setEnabled(active);
|
||||||
@@ -558,7 +607,9 @@ public class ChatFragment extends Fragment implements ChatAdapter.OnChatClickLis
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// When fragment is destroyed, disconnect from websocket
|
/**
|
||||||
|
* Disconnects the WebSocket manager when the fragment view is destroyed.
|
||||||
|
*/
|
||||||
@Override
|
@Override
|
||||||
public void onDestroyView() {
|
public void onDestroyView() {
|
||||||
super.onDestroyView();
|
super.onDestroyView();
|
||||||
|
|||||||
@@ -38,6 +38,9 @@ public class ListFragment extends Fragment {
|
|||||||
|
|
||||||
@Inject TokenManager tokenManager;
|
@Inject TokenManager tokenManager;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Inflates the fragment layout, initializes navigation drawers, and applies role-based access control.
|
||||||
|
*/
|
||||||
@Override
|
@Override
|
||||||
public View onCreateView(LayoutInflater inflater, ViewGroup container,
|
public View onCreateView(LayoutInflater inflater, ViewGroup container,
|
||||||
Bundle savedInstanceState) {
|
Bundle savedInstanceState) {
|
||||||
@@ -106,6 +109,9 @@ public class ListFragment extends Fragment {
|
|||||||
return view;
|
return view;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Initializes the NavController for the internal fragment container.
|
||||||
|
*/
|
||||||
@Override
|
@Override
|
||||||
public void onViewCreated(@NonNull View view, @Nullable Bundle savedInstanceState) {
|
public void onViewCreated(@NonNull View view, @Nullable Bundle savedInstanceState) {
|
||||||
super.onViewCreated(view, savedInstanceState);
|
super.onViewCreated(view, savedInstanceState);
|
||||||
@@ -116,6 +122,9 @@ public class ListFragment extends Fragment {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Navigates to a specific inner destination and closes all drawers.
|
||||||
|
*/
|
||||||
private void navigateTo(int destinationId) {
|
private void navigateTo(int destinationId) {
|
||||||
if (innerNavController != null) {
|
if (innerNavController != null) {
|
||||||
innerNavController.navigate(destinationId);
|
innerNavController.navigate(destinationId);
|
||||||
@@ -123,7 +132,9 @@ public class ListFragment extends Fragment {
|
|||||||
drawerLayout.closeDrawers();
|
drawerLayout.closeDrawers();
|
||||||
}
|
}
|
||||||
|
|
||||||
//helper function to open the drawer
|
/**
|
||||||
|
* Programmatically opens the navigation drawer.
|
||||||
|
*/
|
||||||
public void openDrawer() {
|
public void openDrawer() {
|
||||||
drawerLayout.openDrawer(GravityCompat.START);
|
drawerLayout.openDrawer(GravityCompat.START);
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,21 +1,11 @@
|
|||||||
package com.example.petstoremobile.fragments;
|
package com.example.petstoremobile.fragments;
|
||||||
|
|
||||||
import android.Manifest;
|
|
||||||
import android.app.Activity;
|
|
||||||
import android.content.Intent;
|
|
||||||
import android.content.pm.PackageManager;
|
|
||||||
import android.net.Uri;
|
import android.net.Uri;
|
||||||
import android.os.Bundle;
|
import android.os.Bundle;
|
||||||
|
|
||||||
import androidx.activity.result.ActivityResultLauncher;
|
|
||||||
import androidx.activity.result.contract.ActivityResultContracts;
|
|
||||||
import androidx.appcompat.app.AlertDialog;
|
import androidx.appcompat.app.AlertDialog;
|
||||||
import androidx.core.content.ContextCompat;
|
|
||||||
import androidx.core.content.FileProvider;
|
|
||||||
import androidx.fragment.app.Fragment;
|
import androidx.fragment.app.Fragment;
|
||||||
|
|
||||||
import android.provider.MediaStore;
|
|
||||||
import android.text.InputType;
|
|
||||||
import android.util.Log;
|
import android.util.Log;
|
||||||
import android.view.LayoutInflater;
|
import android.view.LayoutInflater;
|
||||||
import android.view.View;
|
import android.view.View;
|
||||||
@@ -26,26 +16,21 @@ import android.widget.ImageView;
|
|||||||
import android.widget.TextView;
|
import android.widget.TextView;
|
||||||
import android.widget.Toast;
|
import android.widget.Toast;
|
||||||
|
|
||||||
import com.bumptech.glide.Glide;
|
|
||||||
import com.bumptech.glide.load.engine.DiskCacheStrategy;
|
|
||||||
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.activities.MainActivity;
|
import com.example.petstoremobile.activities.MainActivity;
|
||||||
import com.example.petstoremobile.api.auth.AuthApi;
|
import com.example.petstoremobile.api.auth.AuthApi;
|
||||||
import com.example.petstoremobile.api.auth.TokenManager;
|
import com.example.petstoremobile.api.auth.TokenManager;
|
||||||
import com.example.petstoremobile.dtos.ErrorResponse;
|
|
||||||
import com.example.petstoremobile.dtos.UserDTO;
|
import com.example.petstoremobile.dtos.UserDTO;
|
||||||
import com.example.petstoremobile.services.ChatNotificationService;
|
import com.example.petstoremobile.services.ChatNotificationService;
|
||||||
|
import com.example.petstoremobile.utils.ErrorUtils;
|
||||||
|
import com.example.petstoremobile.utils.FileUtils;
|
||||||
|
import com.example.petstoremobile.utils.GlideUtils;
|
||||||
|
import com.example.petstoremobile.utils.ImagePickerHelper;
|
||||||
import com.example.petstoremobile.utils.InputValidator;
|
import com.example.petstoremobile.utils.InputValidator;
|
||||||
import com.google.gson.Gson;
|
import com.example.petstoremobile.utils.UIUtils;
|
||||||
|
|
||||||
import java.io.File;
|
import java.io.File;
|
||||||
import java.io.FileOutputStream;
|
|
||||||
import java.io.InputStream;
|
|
||||||
import java.util.ArrayList;
|
|
||||||
import java.util.HashMap;
|
import java.util.HashMap;
|
||||||
import java.util.List;
|
|
||||||
import java.util.Map;
|
import java.util.Map;
|
||||||
|
|
||||||
import javax.inject.Inject;
|
import javax.inject.Inject;
|
||||||
@@ -59,13 +44,15 @@ import retrofit2.Call;
|
|||||||
import retrofit2.Callback;
|
import retrofit2.Callback;
|
||||||
import retrofit2.Response;
|
import retrofit2.Response;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Fragment that displays and allows editing of the user's profile information.
|
||||||
|
*/
|
||||||
@AndroidEntryPoint
|
@AndroidEntryPoint
|
||||||
public class ProfileFragment extends Fragment {
|
public class ProfileFragment extends Fragment {
|
||||||
|
|
||||||
//initialize the view/controls
|
//initialize the view/controls
|
||||||
private ImageView imgProfile;
|
private ImageView imgProfile;
|
||||||
private TextView tvProfileName, tvProfileEmail, tvProfilePhone, tvProfileRole;
|
private TextView tvProfileName, tvProfileEmail, tvProfilePhone, tvProfileRole;
|
||||||
private Uri photoUri;
|
|
||||||
private UserDTO currentUser;
|
private UserDTO currentUser;
|
||||||
private boolean hasImage = false;
|
private boolean hasImage = false;
|
||||||
|
|
||||||
@@ -73,71 +60,31 @@ public class ProfileFragment extends Fragment {
|
|||||||
@Inject TokenManager tokenManager;
|
@Inject TokenManager tokenManager;
|
||||||
@Inject @Named("baseUrl") String baseUrl;
|
@Inject @Named("baseUrl") String baseUrl;
|
||||||
|
|
||||||
//Initialize the launchers for camera and gallery
|
private ImagePickerHelper imagePickerHelper;
|
||||||
private ActivityResultLauncher<Intent> galleryLauncher;
|
|
||||||
private ActivityResultLauncher<Uri> cameraLauncher;
|
|
||||||
private ActivityResultLauncher<String> permissionLauncher;
|
|
||||||
|
|
||||||
//Called when the fragment is created, sets up the launchers is set profile image
|
/**
|
||||||
|
* Initializes activity launchers and the ImagePickerHelper for camera and gallary.
|
||||||
|
*/
|
||||||
@Override
|
@Override
|
||||||
public void onCreate(Bundle savedInstanceState) {
|
public void onCreate(Bundle savedInstanceState) {
|
||||||
super.onCreate(savedInstanceState);
|
super.onCreate(savedInstanceState);
|
||||||
|
|
||||||
// Launcher to open gallery to select profile image
|
imagePickerHelper = new ImagePickerHelper(this, "profile_photo.jpg", new ImagePickerHelper.ImagePickerListener() {
|
||||||
galleryLauncher = registerForActivityResult(
|
@Override
|
||||||
//open gallery
|
public void onImagePicked(Uri uri) {
|
||||||
new ActivityResultContracts.StartActivityForResult(),
|
uploadAvatar(uri);
|
||||||
result -> {
|
|
||||||
//if the user selects an image and its not null
|
|
||||||
if (result.getResultCode() == Activity.RESULT_OK
|
|
||||||
&& result.getData() != null) {
|
|
||||||
//get the selected image and set the image to the profile
|
|
||||||
Uri selectedImage = result.getData().getData();
|
|
||||||
uploadAvatar(selectedImage);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
);
|
|
||||||
|
|
||||||
// Launcher for camera to open and capture profile image
|
|
||||||
cameraLauncher = registerForActivityResult(
|
|
||||||
//open camera
|
|
||||||
new ActivityResultContracts.TakePicture(),
|
|
||||||
success -> {
|
|
||||||
//if a photo is taken set the image profile to it otherwise do nothing
|
|
||||||
if (success) {
|
|
||||||
uploadAvatar(photoUri);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
);
|
|
||||||
|
|
||||||
// Launcher to request camera permission
|
|
||||||
permissionLauncher = registerForActivityResult(
|
|
||||||
//ask user for camera permission
|
|
||||||
new ActivityResultContracts.RequestPermission(),
|
|
||||||
granted -> {
|
|
||||||
//if the permission is granted launch the camera
|
|
||||||
if (granted) {
|
|
||||||
launchCamera();
|
|
||||||
}
|
|
||||||
else {
|
|
||||||
//if the permission is denied then tell the user to grant it
|
|
||||||
new AlertDialog.Builder(requireContext())
|
|
||||||
.setTitle("Permission Permission Required")
|
|
||||||
.setMessage("Please grant camera permission to use this feature")
|
|
||||||
.setPositiveButton("Open Settings", (dialog, which) ->{
|
|
||||||
//open the settings page to grant the permission when they click open settings
|
|
||||||
Intent intent = new Intent(android.provider.Settings.ACTION_APPLICATION_DETAILS_SETTINGS);
|
|
||||||
intent.setData(Uri.fromParts("package", requireContext().getPackageName(), null));
|
|
||||||
startActivity(intent);
|
|
||||||
})
|
|
||||||
//close the dialog when the user clicks cancel
|
|
||||||
.setNegativeButton("Cancel", null)
|
|
||||||
.show();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void onImageRemoved() {
|
||||||
|
deleteAvatar();
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Inflates the fragment layout and sets up listeners for profile.
|
||||||
|
*/
|
||||||
@Override
|
@Override
|
||||||
public View onCreateView(LayoutInflater inflater, ViewGroup container,
|
public View onCreateView(LayoutInflater inflater, ViewGroup container,
|
||||||
Bundle savedInstanceState) {
|
Bundle savedInstanceState) {
|
||||||
@@ -160,39 +107,7 @@ public class ProfileFragment extends Fragment {
|
|||||||
//Set up listeners for the buttons
|
//Set up listeners for the buttons
|
||||||
//Change photo button
|
//Change photo button
|
||||||
btnChangePhoto.setOnClickListener(v -> {
|
btnChangePhoto.setOnClickListener(v -> {
|
||||||
List<String> options = new ArrayList<>();
|
imagePickerHelper.showImagePickerDialog("Change Profile Photo", hasImage);
|
||||||
options.add("Take Photo");
|
|
||||||
options.add("Choose from Gallery");
|
|
||||||
if (hasImage) {
|
|
||||||
options.add("Remove Photo");
|
|
||||||
}
|
|
||||||
|
|
||||||
//Show alert dialog to user to select from gallery or camera
|
|
||||||
new AlertDialog.Builder(requireContext())
|
|
||||||
.setTitle("Change Profile Photo")
|
|
||||||
//set the options for the alert dialog
|
|
||||||
.setItems(options.toArray(new String[0]), (dialog, which) -> {
|
|
||||||
String selected = options.get(which);
|
|
||||||
if (selected.equals("Take Photo")) {
|
|
||||||
// Choose Camera
|
|
||||||
//Checks if the user has granted the camera permission already
|
|
||||||
if (ContextCompat.checkSelfPermission(requireContext(), Manifest.permission.CAMERA) == PackageManager.PERMISSION_GRANTED) {
|
|
||||||
//if the permission is already granted then launch the camera
|
|
||||||
launchCamera();
|
|
||||||
} else {
|
|
||||||
//otherwise request the permission
|
|
||||||
permissionLauncher.launch(Manifest.permission.CAMERA);
|
|
||||||
}
|
|
||||||
} else if (selected.equals("Choose from Gallery")) {
|
|
||||||
// Choose Gallery
|
|
||||||
Intent intent = new Intent(Intent.ACTION_PICK,
|
|
||||||
MediaStore.Images.Media.EXTERNAL_CONTENT_URI);
|
|
||||||
galleryLauncher.launch(intent);
|
|
||||||
} else if (selected.equals("Remove Photo")) {
|
|
||||||
deleteAvatar();
|
|
||||||
}
|
|
||||||
})
|
|
||||||
.show();
|
|
||||||
});
|
});
|
||||||
|
|
||||||
//Edit email button
|
//Edit email button
|
||||||
@@ -231,11 +146,10 @@ public class ProfileFragment extends Fragment {
|
|||||||
input.setText(tvProfilePhone.getText().toString());
|
input.setText(tvProfilePhone.getText().toString());
|
||||||
|
|
||||||
//set input type to phone number
|
//set input type to phone number
|
||||||
input.setInputType(InputType.TYPE_CLASS_PHONE);
|
input.setInputType(android.view.inputmethod.EditorInfo.TYPE_CLASS_PHONE);
|
||||||
|
|
||||||
//add canada phone number formatting to input (XXX) XXX-XXXX
|
//add canada phone number formatting to input
|
||||||
input.addTextChangedListener(new android.telephony.PhoneNumberFormattingTextWatcher("CA"));
|
UIUtils.formatPhoneInput(input);
|
||||||
input.setFilters(new android.text.InputFilter[]{new android.text.InputFilter.LengthFilter(14)});
|
|
||||||
|
|
||||||
|
|
||||||
//Show alert dialog to user to enter new phone
|
//Show alert dialog to user to enter new phone
|
||||||
@@ -256,13 +170,13 @@ public class ProfileFragment extends Fragment {
|
|||||||
//Logout button
|
//Logout button
|
||||||
btnLogout.setOnClickListener(v -> {
|
btnLogout.setOnClickListener(v -> {
|
||||||
// Stop notification service before logging out so notifications stop
|
// Stop notification service before logging out so notifications stop
|
||||||
Intent serviceIntent = new Intent(requireContext(), ChatNotificationService.class);
|
android.content.Intent serviceIntent = new android.content.Intent(requireContext(), ChatNotificationService.class);
|
||||||
requireContext().stopService(serviceIntent);
|
requireContext().stopService(serviceIntent);
|
||||||
|
|
||||||
tokenManager.clearLoginData(); // clear the token for next login
|
tokenManager.clearLoginData(); // clear the token for next login
|
||||||
//get the intent to the main activity and clear the back stack so the back button won't allow the user to go back to the previous screen
|
//get the intent to the main activity and clear the back stack so the back button won't allow the user to go back to the previous screen
|
||||||
Intent intent = new Intent(getActivity(), MainActivity.class);
|
android.content.Intent intent = new android.content.Intent(getActivity(), MainActivity.class);
|
||||||
intent.addFlags(Intent.FLAG_ACTIVITY_CLEAR_TOP | Intent.FLAG_ACTIVITY_NEW_TASK);
|
intent.addFlags(android.content.Intent.FLAG_ACTIVITY_CLEAR_TOP | android.content.Intent.FLAG_ACTIVITY_NEW_TASK);
|
||||||
//start the activity to go to login page and finish the current activity
|
//start the activity to go to login page and finish the current activity
|
||||||
startActivity(intent);
|
startActivity(intent);
|
||||||
requireActivity().finish();
|
requireActivity().finish();
|
||||||
@@ -271,17 +185,9 @@ public class ProfileFragment extends Fragment {
|
|||||||
return view;
|
return view;
|
||||||
}
|
}
|
||||||
|
|
||||||
//Helper function create a file in the cache directory to store the photo in then launch the camera to capture the photo
|
/**
|
||||||
private void launchCamera() {
|
* Fetches current user profile data from the API and then updates the UI.
|
||||||
//create a file in the cache directory to store the photo in
|
*/
|
||||||
File photoFile = new File(requireContext().getCacheDir(), "profile_photo.jpg");
|
|
||||||
//get the uri for the file made
|
|
||||||
photoUri = FileProvider.getUriForFile(requireContext(), requireContext().getPackageName() + ".fileprovider", photoFile);
|
|
||||||
//launch the camera to capture the photo and save the photo to photoUri
|
|
||||||
cameraLauncher.launch(photoUri);
|
|
||||||
}
|
|
||||||
|
|
||||||
//Helper function to call the backend to get profile data and load it to the view
|
|
||||||
private void loadProfileData() {
|
private void loadProfileData() {
|
||||||
authApi.getMe().enqueue(new Callback<UserDTO>() {
|
authApi.getMe().enqueue(new Callback<UserDTO>() {
|
||||||
@Override
|
@Override
|
||||||
@@ -300,44 +206,21 @@ public class ProfileFragment extends Fragment {
|
|||||||
String avatarUrl = baseUrl + AuthApi.AVATAR_FILE_PATH;
|
String avatarUrl = baseUrl + AuthApi.AVATAR_FILE_PATH;
|
||||||
String token = tokenManager.getToken();
|
String token = tokenManager.getToken();
|
||||||
|
|
||||||
if (token != null) {
|
GlideUtils.loadImageWithToken(requireContext(), imgProfile, avatarUrl, token, R.drawable.placeholder, new GlideUtils.ImageLoadListener() {
|
||||||
// Create GlideUrl with token to fetch the image
|
|
||||||
GlideUrl glideUrl = new GlideUrl(avatarUrl, new LazyHeaders.Builder()
|
|
||||||
.addHeader("Authorization", "Bearer " + token)
|
|
||||||
.build());
|
|
||||||
|
|
||||||
// Load image using Glide
|
|
||||||
Glide.with(ProfileFragment.this)
|
|
||||||
.load(glideUrl)
|
|
||||||
.diskCacheStrategy(DiskCacheStrategy.NONE)
|
|
||||||
.skipMemoryCache(true)
|
|
||||||
.placeholder(R.drawable.placeholder)
|
|
||||||
.error(R.drawable.placeholder)
|
|
||||||
.listener(new com.bumptech.glide.request.RequestListener<android.graphics.drawable.Drawable>() {
|
|
||||||
@Override
|
@Override
|
||||||
public boolean onLoadFailed(@androidx.annotation.Nullable com.bumptech.glide.load.engine.GlideException e, Object model, com.bumptech.glide.request.target.Target<android.graphics.drawable.Drawable> target, boolean isFirstResource) {
|
public void onResourceReady() {
|
||||||
hasImage = false;
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public boolean onResourceReady(android.graphics.drawable.Drawable resource, Object model, com.bumptech.glide.request.target.Target<android.graphics.drawable.Drawable> target, com.bumptech.glide.load.DataSource dataSource, boolean isFirstResource) {
|
|
||||||
hasImage = true;
|
hasImage = true;
|
||||||
return false;
|
|
||||||
}
|
}
|
||||||
})
|
|
||||||
.into(imgProfile);
|
@Override
|
||||||
} else {
|
public void onLoadFailed() {
|
||||||
// load placeholder image if token is null
|
|
||||||
hasImage = false;
|
hasImage = false;
|
||||||
Glide.with(ProfileFragment.this)
|
|
||||||
.load(R.drawable.placeholder)
|
|
||||||
.into(imgProfile);
|
|
||||||
}
|
}
|
||||||
|
});
|
||||||
}
|
}
|
||||||
else {
|
else {
|
||||||
Log.e("onResponse: ", response.message());
|
Log.e("onResponse: ", response.message());
|
||||||
Toast.makeText(getContext(), "Failed to load profile: ", Toast.LENGTH_SHORT).show();
|
ErrorUtils.showErrorMessage(getContext(), response, "Failed to load profile");
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -349,10 +232,12 @@ public class ProfileFragment extends Fragment {
|
|||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
//Helper function to call the backend to upload a profile image
|
/**
|
||||||
|
* Uploads the selected or captured image as the user's new avatar.
|
||||||
|
*/
|
||||||
private void uploadAvatar(Uri uri) {
|
private void uploadAvatar(Uri uri) {
|
||||||
try {
|
try {
|
||||||
File file = getFileFromUri(uri);
|
File file = FileUtils.getFileFromUri(requireContext(), uri);
|
||||||
if (file == null) return;
|
if (file == null) return;
|
||||||
|
|
||||||
// Create RequestBody for file upload
|
// Create RequestBody for file upload
|
||||||
@@ -369,7 +254,7 @@ public class ProfileFragment extends Fragment {
|
|||||||
// Reload image after successful upload
|
// Reload image after successful upload
|
||||||
loadProfileData();
|
loadProfileData();
|
||||||
} else {
|
} else {
|
||||||
Toast.makeText(requireContext(), "Failed to upload avatar", Toast.LENGTH_SHORT).show();
|
ErrorUtils.showErrorMessage(getContext(), response, "Failed to upload avatar");
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -384,6 +269,9 @@ public class ProfileFragment extends Fragment {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Sends a request to the API to delete the current user's avatar image.
|
||||||
|
*/
|
||||||
private void deleteAvatar() {
|
private void deleteAvatar() {
|
||||||
authApi.deleteAvatar().enqueue(new Callback<Void>() {
|
authApi.deleteAvatar().enqueue(new Callback<Void>() {
|
||||||
@Override
|
@Override
|
||||||
@@ -393,7 +281,7 @@ public class ProfileFragment extends Fragment {
|
|||||||
hasImage = false;
|
hasImage = false;
|
||||||
imgProfile.setImageResource(R.drawable.placeholder);
|
imgProfile.setImageResource(R.drawable.placeholder);
|
||||||
} else {
|
} else {
|
||||||
Toast.makeText(requireContext(), "Failed to remove avatar", Toast.LENGTH_SHORT).show();
|
ErrorUtils.showErrorMessage(getContext(), response, "Failed to remove avatar");
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -405,27 +293,9 @@ public class ProfileFragment extends Fragment {
|
|||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
// Helper function to create a temporary File object from a Uri for uploading the avatar
|
/**
|
||||||
private File getFileFromUri(Uri uri) {
|
* Updates a specific profile field (like email or phone) by sending a request to the API.
|
||||||
try {
|
*/
|
||||||
InputStream inputStream = requireContext().getContentResolver().openInputStream(uri);
|
|
||||||
File tempFile = new File(requireContext().getCacheDir(), "upload_avatar.jpg");
|
|
||||||
FileOutputStream outputStream = new FileOutputStream(tempFile);
|
|
||||||
byte[] buffer = new byte[1024];
|
|
||||||
int length;
|
|
||||||
while ((length = inputStream.read(buffer)) > 0) {
|
|
||||||
outputStream.write(buffer, 0, length);
|
|
||||||
}
|
|
||||||
outputStream.close();
|
|
||||||
inputStream.close();
|
|
||||||
return tempFile;
|
|
||||||
} catch (Exception e) {
|
|
||||||
Log.e("FILE_UTILS", "Error creating temp file", e);
|
|
||||||
return null;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
//Helper function to update a profile field in the backend
|
|
||||||
private void updateProfileField(String fieldName, String value) {
|
private void updateProfileField(String fieldName, String value) {
|
||||||
Map<String, String> updates = new HashMap<>();
|
Map<String, String> updates = new HashMap<>();
|
||||||
updates.put(fieldName, value);
|
updates.put(fieldName, value);
|
||||||
@@ -440,15 +310,7 @@ public class ProfileFragment extends Fragment {
|
|||||||
tvProfilePhone.setText(currentUser.getPhone());
|
tvProfilePhone.setText(currentUser.getPhone());
|
||||||
Toast.makeText(requireContext(), "Profile updated successfully", Toast.LENGTH_SHORT).show();
|
Toast.makeText(requireContext(), "Profile updated successfully", Toast.LENGTH_SHORT).show();
|
||||||
} else {
|
} else {
|
||||||
try {
|
ErrorUtils.showErrorMessage(getContext(), response, "Failed to update profile");
|
||||||
String errorJson = response.errorBody().string();
|
|
||||||
ErrorResponse errorResponse = new Gson().fromJson(errorJson, ErrorResponse.class);
|
|
||||||
String errorMessage = errorResponse.getMessage();
|
|
||||||
Toast.makeText(requireContext(), errorMessage, Toast.LENGTH_LONG).show();
|
|
||||||
} catch (Exception e) {
|
|
||||||
Log.e("UPDATE_PROFILE", "Error parsing error body", e);
|
|
||||||
Toast.makeText(requireContext(), "Failed to update profile", Toast.LENGTH_SHORT).show();
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -48,12 +48,18 @@ public class AdoptionFragment extends Fragment implements AdoptionAdapter.OnAdop
|
|||||||
private boolean isMonthMode = false;
|
private boolean isMonthMode = false;
|
||||||
private final SimpleDateFormat dateFormat = new SimpleDateFormat("yyyy-MM-dd", Locale.getDefault());
|
private final SimpleDateFormat dateFormat = new SimpleDateFormat("yyyy-MM-dd", Locale.getDefault());
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Initializes the fragment and its ViewModel.
|
||||||
|
*/
|
||||||
@Override
|
@Override
|
||||||
public void onCreate(@Nullable Bundle savedInstanceState) {
|
public void onCreate(@Nullable Bundle savedInstanceState) {
|
||||||
super.onCreate(savedInstanceState);
|
super.onCreate(savedInstanceState);
|
||||||
viewModel = new ViewModelProvider(this).get(AdoptionViewModel.class);
|
viewModel = new ViewModelProvider(this).get(AdoptionViewModel.class);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Sets up the fragment's UI components, including RecyclerView, Search, SwipeRefresh, and Calendar.
|
||||||
|
*/
|
||||||
@Override
|
@Override
|
||||||
public View onCreateView(@NonNull LayoutInflater inflater, ViewGroup container,
|
public View onCreateView(@NonNull LayoutInflater inflater, ViewGroup container,
|
||||||
Bundle savedInstanceState) {
|
Bundle savedInstanceState) {
|
||||||
@@ -87,6 +93,9 @@ public class AdoptionFragment extends Fragment implements AdoptionAdapter.OnAdop
|
|||||||
return view;
|
return view;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Toggles the calendar display between week and month modes.
|
||||||
|
*/
|
||||||
private void toggleCalendarMode() {
|
private void toggleCalendarMode() {
|
||||||
isMonthMode = !isMonthMode;
|
isMonthMode = !isMonthMode;
|
||||||
calendarView.state().edit()
|
calendarView.state().edit()
|
||||||
@@ -94,6 +103,9 @@ public class AdoptionFragment extends Fragment implements AdoptionAdapter.OnAdop
|
|||||||
.commit();
|
.commit();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Sets up the date selection listener for the calendar.
|
||||||
|
*/
|
||||||
private void setupCalendar() {
|
private void setupCalendar() {
|
||||||
calendarView.setOnDateChangedListener(new OnDateSelectedListener() {
|
calendarView.setOnDateChangedListener(new OnDateSelectedListener() {
|
||||||
@Override
|
@Override
|
||||||
@@ -113,6 +125,9 @@ public class AdoptionFragment extends Fragment implements AdoptionAdapter.OnAdop
|
|||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Updates the calendar decorators to highlight days with adoptions.
|
||||||
|
*/
|
||||||
private void updateCalendarDecorators() {
|
private void updateCalendarDecorators() {
|
||||||
HashSet<CalendarDay> datesWithAdoptions = new HashSet<>();
|
HashSet<CalendarDay> datesWithAdoptions = new HashSet<>();
|
||||||
for (AdoptionDTO adoption : adoptionList) {
|
for (AdoptionDTO adoption : adoptionList) {
|
||||||
@@ -133,6 +148,9 @@ public class AdoptionFragment extends Fragment implements AdoptionAdapter.OnAdop
|
|||||||
calendarView.addDecorator(new EventDecorator(Color.RED, datesWithAdoptions));
|
calendarView.addDecorator(new EventDecorator(Color.RED, datesWithAdoptions));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Initializes the RecyclerView for displaying adoptions.
|
||||||
|
*/
|
||||||
private void setupRecyclerView(View view) {
|
private void setupRecyclerView(View view) {
|
||||||
RecyclerView rv = view.findViewById(R.id.recyclerViewAdoptions);
|
RecyclerView rv = view.findViewById(R.id.recyclerViewAdoptions);
|
||||||
adapter = new AdoptionAdapter(filteredList, this);
|
adapter = new AdoptionAdapter(filteredList, this);
|
||||||
@@ -140,6 +158,9 @@ public class AdoptionFragment extends Fragment implements AdoptionAdapter.OnAdop
|
|||||||
rv.setAdapter(adapter);
|
rv.setAdapter(adapter);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Sets up the search bar for filtering
|
||||||
|
*/
|
||||||
private void setupSearch(View view) {
|
private void setupSearch(View view) {
|
||||||
etSearch = view.findViewById(R.id.etSearchAdoption);
|
etSearch = view.findViewById(R.id.etSearchAdoption);
|
||||||
etSearch.addTextChangedListener(new TextWatcher() {
|
etSearch.addTextChangedListener(new TextWatcher() {
|
||||||
@@ -151,11 +172,17 @@ public class AdoptionFragment extends Fragment implements AdoptionAdapter.OnAdop
|
|||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Sets up the SwipeRefreshLayout to reload adoption data.
|
||||||
|
*/
|
||||||
private void setupSwipeRefresh(View view) {
|
private void setupSwipeRefresh(View view) {
|
||||||
swipeRefresh = view.findViewById(R.id.swipeRefreshAdoption);
|
swipeRefresh = view.findViewById(R.id.swipeRefreshAdoption);
|
||||||
swipeRefresh.setOnRefreshListener(this::loadAdoptions);
|
swipeRefresh.setOnRefreshListener(this::loadAdoptions);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Filters the adoption list based on search query and selected calendar date.
|
||||||
|
*/
|
||||||
private void filter(String query) {
|
private void filter(String query) {
|
||||||
filteredList.clear();
|
filteredList.clear();
|
||||||
String lowerQuery = query.toLowerCase();
|
String lowerQuery = query.toLowerCase();
|
||||||
@@ -182,7 +209,9 @@ public class AdoptionFragment extends Fragment implements AdoptionAdapter.OnAdop
|
|||||||
adapter.notifyDataSetChanged();
|
adapter.notifyDataSetChanged();
|
||||||
}
|
}
|
||||||
|
|
||||||
// Helper function to get a list of all adoptions from the backend
|
/**
|
||||||
|
* Fetches the adoption list from the server through the ViewModel.
|
||||||
|
*/
|
||||||
private void loadAdoptions() {
|
private void loadAdoptions() {
|
||||||
//Load all adoptions from the backend using viewModel
|
//Load all adoptions from the backend using viewModel
|
||||||
viewModel.getAllAdoptions(0, 500).observe(getViewLifecycleOwner(), resource -> {
|
viewModel.getAllAdoptions(0, 500).observe(getViewLifecycleOwner(), resource -> {
|
||||||
@@ -214,6 +243,9 @@ public class AdoptionFragment extends Fragment implements AdoptionAdapter.OnAdop
|
|||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Navigates to the adoption detail screen for a specific adoption or to create a new one.
|
||||||
|
*/
|
||||||
private void openDetail(int position) {
|
private void openDetail(int position) {
|
||||||
Bundle args = new Bundle();
|
Bundle args = new Bundle();
|
||||||
|
|
||||||
@@ -229,6 +261,9 @@ public class AdoptionFragment extends Fragment implements AdoptionAdapter.OnAdop
|
|||||||
NavHostFragment.findNavController(this).navigate(R.id.nav_adoption_detail, args);
|
NavHostFragment.findNavController(this).navigate(R.id.nav_adoption_detail, args);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Handles item click in the adoption list.
|
||||||
|
*/
|
||||||
@Override
|
@Override
|
||||||
public void onAdoptionClick(int position) { openDetail(position); }
|
public void onAdoptionClick(int position) { openDetail(position); }
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -71,6 +71,9 @@ public class AppointmentFragment extends Fragment implements AppointmentAdapter.
|
|||||||
private boolean isMonthMode = false;
|
private boolean isMonthMode = false;
|
||||||
private final SimpleDateFormat dateFormat = new SimpleDateFormat("yyyy-MM-dd", Locale.getDefault());
|
private final SimpleDateFormat dateFormat = new SimpleDateFormat("yyyy-MM-dd", Locale.getDefault());
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Initializes the fragment and its associated ViewModels.
|
||||||
|
*/
|
||||||
@Override
|
@Override
|
||||||
public void onCreate(@Nullable Bundle savedInstanceState) {
|
public void onCreate(@Nullable Bundle savedInstanceState) {
|
||||||
super.onCreate(savedInstanceState);
|
super.onCreate(savedInstanceState);
|
||||||
@@ -79,6 +82,9 @@ public class AppointmentFragment extends Fragment implements AppointmentAdapter.
|
|||||||
serviceViewModel = new ViewModelProvider(this).get(ServiceViewModel.class);
|
serviceViewModel = new ViewModelProvider(this).get(ServiceViewModel.class);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Sets up the fragment's UI, including RecyclerView, search, swipe-to-refresh, and calendar.
|
||||||
|
*/
|
||||||
@Override
|
@Override
|
||||||
public View onCreateView(@NonNull LayoutInflater inflater, ViewGroup container,
|
public View onCreateView(@NonNull LayoutInflater inflater, ViewGroup container,
|
||||||
Bundle savedInstanceState) {
|
Bundle savedInstanceState) {
|
||||||
@@ -114,7 +120,9 @@ public class AppointmentFragment extends Fragment implements AppointmentAdapter.
|
|||||||
return view;
|
return view;
|
||||||
}
|
}
|
||||||
|
|
||||||
// Toggle Calendar Mode from week to month and other way around
|
/**
|
||||||
|
* Toggles the calendar between week and month display modes.
|
||||||
|
*/
|
||||||
private void toggleCalendarMode() {
|
private void toggleCalendarMode() {
|
||||||
isMonthMode = !isMonthMode;
|
isMonthMode = !isMonthMode;
|
||||||
calendarView.state().edit()
|
calendarView.state().edit()
|
||||||
@@ -122,6 +130,9 @@ public class AppointmentFragment extends Fragment implements AppointmentAdapter.
|
|||||||
.commit();
|
.commit();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Sets up the date selection listener for the calendar.
|
||||||
|
*/
|
||||||
private void setupCalendar() {
|
private void setupCalendar() {
|
||||||
calendarView.setOnDateChangedListener(new OnDateSelectedListener() {
|
calendarView.setOnDateChangedListener(new OnDateSelectedListener() {
|
||||||
@Override
|
@Override
|
||||||
@@ -141,7 +152,9 @@ public class AppointmentFragment extends Fragment implements AppointmentAdapter.
|
|||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
//Set indicators for dates with appointments on the calendar
|
/**
|
||||||
|
* Updates calendar indicators to highlight dates that have scheduled appointments.
|
||||||
|
*/
|
||||||
private void updateCalendarDecorators() {
|
private void updateCalendarDecorators() {
|
||||||
HashSet<CalendarDay> datesWithAppointments = new HashSet<>();
|
HashSet<CalendarDay> datesWithAppointments = new HashSet<>();
|
||||||
for (AppointmentDTO appointment : appointmentList) {
|
for (AppointmentDTO appointment : appointmentList) {
|
||||||
@@ -163,6 +176,9 @@ public class AppointmentFragment extends Fragment implements AppointmentAdapter.
|
|||||||
calendarView.addDecorator(new EventDecorator(Color.RED, datesWithAppointments));
|
calendarView.addDecorator(new EventDecorator(Color.RED, datesWithAppointments));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Configures the search bar for filtering.
|
||||||
|
*/
|
||||||
private void setupSearch(View view) {
|
private void setupSearch(View view) {
|
||||||
etSearch = view.findViewById(R.id.etSearchAppointment);
|
etSearch = view.findViewById(R.id.etSearchAppointment);
|
||||||
etSearch.addTextChangedListener(new TextWatcher() {
|
etSearch.addTextChangedListener(new TextWatcher() {
|
||||||
@@ -181,6 +197,9 @@ public class AppointmentFragment extends Fragment implements AppointmentAdapter.
|
|||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Filters the appointment list based on the search query and selected calendar date.
|
||||||
|
*/
|
||||||
private void filterAppointments(String query) {
|
private void filterAppointments(String query) {
|
||||||
filteredList.clear();
|
filteredList.clear();
|
||||||
String lowerQuery = query.toLowerCase();
|
String lowerQuery = query.toLowerCase();
|
||||||
@@ -207,11 +226,17 @@ public class AppointmentFragment extends Fragment implements AppointmentAdapter.
|
|||||||
adapter.notifyDataSetChanged();
|
adapter.notifyDataSetChanged();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Initializes the SwipeRefreshLayout to allow manual data refreshing.
|
||||||
|
*/
|
||||||
private void setupSwipeRefresh(View view) {
|
private void setupSwipeRefresh(View view) {
|
||||||
swipeRefreshLayout = view.findViewById(R.id.swipeRefreshAppointment);
|
swipeRefreshLayout = view.findViewById(R.id.swipeRefreshAppointment);
|
||||||
swipeRefreshLayout.setOnRefreshListener(this::loadAppointmentData);
|
swipeRefreshLayout.setOnRefreshListener(this::loadAppointmentData);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Navigates to the appointment detail screen for editing or creating an appointment.
|
||||||
|
*/
|
||||||
private void openAppointmentDetails(int position) {
|
private void openAppointmentDetails(int position) {
|
||||||
Bundle args = new Bundle();
|
Bundle args = new Bundle();
|
||||||
|
|
||||||
@@ -230,20 +255,32 @@ public class AppointmentFragment extends Fragment implements AppointmentAdapter.
|
|||||||
|
|
||||||
NavHostFragment.findNavController(this).navigate(R.id.nav_appointment_detail, args);
|
NavHostFragment.findNavController(this).navigate(R.id.nav_appointment_detail, args);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Reloads data when an appointment is saved.
|
||||||
|
*/
|
||||||
public void onAppointmentSaved(int position, AppointmentDTO appointment) {
|
public void onAppointmentSaved(int position, AppointmentDTO appointment) {
|
||||||
loadAppointmentData();
|
loadAppointmentData();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Reloads data when an appointment is deleted.
|
||||||
|
*/
|
||||||
public void onAppointmentDeleted(int position) {
|
public void onAppointmentDeleted(int position) {
|
||||||
loadAppointmentData();
|
loadAppointmentData();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Handles item click in the appointment list.
|
||||||
|
*/
|
||||||
@Override
|
@Override
|
||||||
public void onAppointmentClick(int position) {
|
public void onAppointmentClick(int position) {
|
||||||
openAppointmentDetails(position);
|
openAppointmentDetails(position);
|
||||||
}
|
}
|
||||||
|
|
||||||
// Helper function to get a list of all appointments from the backend
|
/**
|
||||||
|
* Fetches all appointment data from the server.
|
||||||
|
*/
|
||||||
private void loadAppointmentData() {
|
private void loadAppointmentData() {
|
||||||
//Load all appointments from the backend using viewModel
|
//Load all appointments from the backend using viewModel
|
||||||
appointmentViewModel.getAllAppointments(0, 500).observe(getViewLifecycleOwner(), resource -> {
|
appointmentViewModel.getAllAppointments(0, 500).observe(getViewLifecycleOwner(), resource -> {
|
||||||
@@ -275,7 +312,9 @@ public class AppointmentFragment extends Fragment implements AppointmentAdapter.
|
|||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
// Load Pets
|
/**
|
||||||
|
* Fetches the full list of pets from the server.
|
||||||
|
*/
|
||||||
private void loadPets() {
|
private void loadPets() {
|
||||||
petViewModel.getAllPets(0, 100).observe(getViewLifecycleOwner(), resource -> {
|
petViewModel.getAllPets(0, 100).observe(getViewLifecycleOwner(), resource -> {
|
||||||
if (resource != null && resource.status == Resource.Status.SUCCESS && resource.data != null) {
|
if (resource != null && resource.status == Resource.Status.SUCCESS && resource.data != null) {
|
||||||
@@ -285,7 +324,9 @@ public class AppointmentFragment extends Fragment implements AppointmentAdapter.
|
|||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
// Load Services
|
/**
|
||||||
|
* Fetches the full list of services from the server.
|
||||||
|
*/
|
||||||
private void loadServices() {
|
private void loadServices() {
|
||||||
serviceViewModel.getAllServices(0, 100).observe(getViewLifecycleOwner(), resource -> {
|
serviceViewModel.getAllServices(0, 100).observe(getViewLifecycleOwner(), resource -> {
|
||||||
if (resource != null && resource.status == Resource.Status.SUCCESS && resource.data != null) {
|
if (resource != null && resource.status == Resource.Status.SUCCESS && resource.data != null) {
|
||||||
@@ -295,6 +336,9 @@ public class AppointmentFragment extends Fragment implements AppointmentAdapter.
|
|||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Get a pet's name based on its ID.
|
||||||
|
*/
|
||||||
private String getPetName(Long id) {
|
private String getPetName(Long id) {
|
||||||
for (PetDTO p : petList) {
|
for (PetDTO p : petList) {
|
||||||
if (p.getPetId().equals(id)) return p.getPetName();
|
if (p.getPetId().equals(id)) return p.getPetName();
|
||||||
@@ -303,6 +347,9 @@ public class AppointmentFragment extends Fragment implements AppointmentAdapter.
|
|||||||
return "";
|
return "";
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Get a service's name based on its ID.
|
||||||
|
*/
|
||||||
private String getServiceName(Long id) {
|
private String getServiceName(Long id) {
|
||||||
for (ServiceDTO s : serviceList) {
|
for (ServiceDTO s : serviceList) {
|
||||||
if (s.getServiceId().equals(id))return s.getServiceName();
|
if (s.getServiceId().equals(id))return s.getServiceName();
|
||||||
@@ -310,6 +357,9 @@ public class AppointmentFragment extends Fragment implements AppointmentAdapter.
|
|||||||
return "";
|
return "";
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Initializes the RecyclerView for displaying appointments.
|
||||||
|
*/
|
||||||
private void setupRecyclerView(View view) {
|
private void setupRecyclerView(View view) {
|
||||||
RecyclerView recyclerView = view.findViewById(R.id.recyclerViewAppointments);
|
RecyclerView recyclerView = view.findViewById(R.id.recyclerViewAppointments);
|
||||||
adapter = new AppointmentAdapter(filteredList, this);
|
adapter = new AppointmentAdapter(filteredList, this);
|
||||||
|
|||||||
@@ -74,12 +74,18 @@ public class InventoryFragment extends Fragment implements InventoryAdapter.OnIn
|
|||||||
// Prevent spinner from firing on initial load
|
// Prevent spinner from firing on initial load
|
||||||
private boolean spinnerReady = false;
|
private boolean spinnerReady = false;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Initializes the fragment and its ViewModel.
|
||||||
|
*/
|
||||||
@Override
|
@Override
|
||||||
public void onCreate(@Nullable Bundle savedInstanceState) {
|
public void onCreate(@Nullable Bundle savedInstanceState) {
|
||||||
super.onCreate(savedInstanceState);
|
super.onCreate(savedInstanceState);
|
||||||
viewModel = new ViewModelProvider(this).get(InventoryViewModel.class);
|
viewModel = new ViewModelProvider(this).get(InventoryViewModel.class);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Sets up the fragment's UI components, including the inventory list, search, and category filter.
|
||||||
|
*/
|
||||||
@Override
|
@Override
|
||||||
public View onCreateView(@NonNull LayoutInflater inflater, ViewGroup container,
|
public View onCreateView(@NonNull LayoutInflater inflater, ViewGroup container,
|
||||||
Bundle savedInstanceState) {
|
Bundle savedInstanceState) {
|
||||||
@@ -114,7 +120,9 @@ public class InventoryFragment extends Fragment implements InventoryAdapter.OnIn
|
|||||||
return view;
|
return view;
|
||||||
}
|
}
|
||||||
|
|
||||||
// Categories
|
/**
|
||||||
|
* Fetches all product categories to populate the filter spinner.
|
||||||
|
*/
|
||||||
private void loadCategories() {
|
private void loadCategories() {
|
||||||
viewModel.getAllCategories(0, 100).observe(getViewLifecycleOwner(), resource -> {
|
viewModel.getAllCategories(0, 100).observe(getViewLifecycleOwner(), resource -> {
|
||||||
if (resource != null && resource.status == Resource.Status.SUCCESS && resource.data != null) {
|
if (resource != null && resource.status == Resource.Status.SUCCESS && resource.data != null) {
|
||||||
@@ -128,6 +136,9 @@ public class InventoryFragment extends Fragment implements InventoryAdapter.OnIn
|
|||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Setup the category filter spinner.
|
||||||
|
*/
|
||||||
private void setupCategorySpinner() {
|
private void setupCategorySpinner() {
|
||||||
// First item is always "All Categories"
|
// First item is always "All Categories"
|
||||||
List<String> categoryNames = new ArrayList<>();
|
List<String> categoryNames = new ArrayList<>();
|
||||||
@@ -167,8 +178,9 @@ public class InventoryFragment extends Fragment implements InventoryAdapter.OnIn
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// Search
|
/**
|
||||||
|
* Sets up the search bar for filtering.
|
||||||
|
*/
|
||||||
private void setupSearch(View view) {
|
private void setupSearch(View view) {
|
||||||
etSearch = view.findViewById(R.id.etSearchInventory);
|
etSearch = view.findViewById(R.id.etSearchInventory);
|
||||||
etSearch.addTextChangedListener(new TextWatcher() {
|
etSearch.addTextChangedListener(new TextWatcher() {
|
||||||
@@ -193,7 +205,9 @@ public class InventoryFragment extends Fragment implements InventoryAdapter.OnIn
|
|||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
// RecyclerView + infinite scroll
|
/**
|
||||||
|
* Initializes the RecyclerView with a layout manager, and adapter.
|
||||||
|
*/
|
||||||
private void setupRecyclerView(View view) {
|
private void setupRecyclerView(View view) {
|
||||||
RecyclerView rv = view.findViewById(R.id.recyclerViewInventory);
|
RecyclerView rv = view.findViewById(R.id.recyclerViewInventory);
|
||||||
adapter = new InventoryAdapter(inventoryList, this);
|
adapter = new InventoryAdapter(inventoryList, this);
|
||||||
@@ -218,12 +232,17 @@ public class InventoryFragment extends Fragment implements InventoryAdapter.OnIn
|
|||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Sets up the SwipeRefreshLayout to reload the first page of inventory items.
|
||||||
|
*/
|
||||||
private void setupSwipeRefresh(View view) {
|
private void setupSwipeRefresh(View view) {
|
||||||
swipeRefreshLayout = view.findViewById(R.id.swipeRefreshInventory);
|
swipeRefreshLayout = view.findViewById(R.id.swipeRefreshInventory);
|
||||||
swipeRefreshLayout.setOnRefreshListener(() -> loadInventory(true));
|
swipeRefreshLayout.setOnRefreshListener(() -> loadInventory(true));
|
||||||
}
|
}
|
||||||
|
|
||||||
// Helper function to get a list of all inventory items from the backend
|
/**
|
||||||
|
* Fetches a page of inventory items from the API.
|
||||||
|
*/
|
||||||
private void loadInventory(boolean reset) {
|
private void loadInventory(boolean reset) {
|
||||||
if (isLoading)
|
if (isLoading)
|
||||||
return;
|
return;
|
||||||
@@ -270,7 +289,9 @@ public class InventoryFragment extends Fragment implements InventoryAdapter.OnIn
|
|||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
// Combines search text and category into one query string for ?q=
|
/**
|
||||||
|
* Constructs a query string based on the current search text and selected category.
|
||||||
|
*/
|
||||||
private String buildQuery() {
|
private String buildQuery() {
|
||||||
String q = null;
|
String q = null;
|
||||||
if (!currentQuery.isEmpty() && selectedCategory != null) {
|
if (!currentQuery.isEmpty() && selectedCategory != null) {
|
||||||
@@ -284,7 +305,9 @@ public class InventoryFragment extends Fragment implements InventoryAdapter.OnIn
|
|||||||
return q;
|
return q;
|
||||||
}
|
}
|
||||||
|
|
||||||
// Bulk delete
|
/**
|
||||||
|
* Displays a confirmation dialog before performing a bulk deletion of selected items.
|
||||||
|
*/
|
||||||
private void confirmBulkDelete() {
|
private void confirmBulkDelete() {
|
||||||
List<Long> ids = adapter.getSelectedIds();
|
List<Long> ids = adapter.getSelectedIds();
|
||||||
if (ids.isEmpty())
|
if (ids.isEmpty())
|
||||||
@@ -298,6 +321,9 @@ public class InventoryFragment extends Fragment implements InventoryAdapter.OnIn
|
|||||||
.show();
|
.show();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Executes the bulk deletion of inventory items through the ViewModel.
|
||||||
|
*/
|
||||||
private void bulkDelete(List<Long> ids) {
|
private void bulkDelete(List<Long> ids) {
|
||||||
viewModel.bulkDeleteInventory(ids).observe(getViewLifecycleOwner(), resource -> {
|
viewModel.bulkDeleteInventory(ids).observe(getViewLifecycleOwner(), resource -> {
|
||||||
if (resource != null && resource.status != Resource.Status.LOADING) {
|
if (resource != null && resource.status != Resource.Status.LOADING) {
|
||||||
@@ -313,6 +339,9 @@ public class InventoryFragment extends Fragment implements InventoryAdapter.OnIn
|
|||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Hides the bulk deletion UI bar.
|
||||||
|
*/
|
||||||
private void hideBulkDeleteBar() {
|
private void hideBulkDeleteBar() {
|
||||||
if (btnBulkDelete != null)
|
if (btnBulkDelete != null)
|
||||||
btnBulkDelete.setVisibility(View.GONE);
|
btnBulkDelete.setVisibility(View.GONE);
|
||||||
@@ -320,7 +349,9 @@ public class InventoryFragment extends Fragment implements InventoryAdapter.OnIn
|
|||||||
tvSelectionCount.setVisibility(View.GONE);
|
tvSelectionCount.setVisibility(View.GONE);
|
||||||
}
|
}
|
||||||
|
|
||||||
// Navigation
|
/**
|
||||||
|
* Navigates to the inventory detail screen for a specific item or to add a new one.
|
||||||
|
*/
|
||||||
private void openDetail(InventoryDTO inv) {
|
private void openDetail(InventoryDTO inv) {
|
||||||
Bundle args = new Bundle();
|
Bundle args = new Bundle();
|
||||||
|
|
||||||
@@ -335,12 +366,16 @@ public class InventoryFragment extends Fragment implements InventoryAdapter.OnIn
|
|||||||
NavHostFragment.findNavController(this).navigate(R.id.nav_inventory_detail, args);
|
NavHostFragment.findNavController(this).navigate(R.id.nav_inventory_detail, args);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Reloads inventory data when changes occur.
|
||||||
|
*/
|
||||||
public void onInventoryChanged() {
|
public void onInventoryChanged() {
|
||||||
loadInventory(true);
|
loadInventory(true);
|
||||||
}
|
}
|
||||||
|
|
||||||
// Adapter callbacks
|
/**
|
||||||
|
* Handles item click in the inventory list.
|
||||||
|
*/
|
||||||
@Override
|
@Override
|
||||||
public void onInventoryClick(int position) {
|
public void onInventoryClick(int position) {
|
||||||
if (position >= 0 && position < inventoryList.size()) {
|
if (position >= 0 && position < inventoryList.size()) {
|
||||||
@@ -348,6 +383,9 @@ public class InventoryFragment extends Fragment implements InventoryAdapter.OnIn
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Updates the bulk deletion UI visibility and count when items are selected or deselected.
|
||||||
|
*/
|
||||||
@Override
|
@Override
|
||||||
public void onSelectionChanged(int selectedCount) {
|
public void onSelectionChanged(int selectedCount) {
|
||||||
if (selectedCount > 0) {
|
if (selectedCount > 0) {
|
||||||
|
|||||||
@@ -2,7 +2,6 @@ package com.example.petstoremobile.fragments.listfragments;
|
|||||||
|
|
||||||
import android.os.Bundle;
|
import android.os.Bundle;
|
||||||
|
|
||||||
import androidx.annotation.NonNull;
|
|
||||||
import androidx.annotation.Nullable;
|
import androidx.annotation.Nullable;
|
||||||
import androidx.fragment.app.Fragment;
|
import androidx.fragment.app.Fragment;
|
||||||
import androidx.lifecycle.ViewModelProvider;
|
import androidx.lifecycle.ViewModelProvider;
|
||||||
@@ -29,7 +28,6 @@ import com.example.petstoremobile.adapters.PetAdapter;
|
|||||||
import com.example.petstoremobile.dtos.PetDTO;
|
import com.example.petstoremobile.dtos.PetDTO;
|
||||||
import com.example.petstoremobile.fragments.ListFragment;
|
import com.example.petstoremobile.fragments.ListFragment;
|
||||||
import com.example.petstoremobile.viewmodels.PetViewModel;
|
import com.example.petstoremobile.viewmodels.PetViewModel;
|
||||||
import com.example.petstoremobile.utils.Resource;
|
|
||||||
import com.google.android.material.floatingactionbutton.FloatingActionButton;
|
import com.google.android.material.floatingactionbutton.FloatingActionButton;
|
||||||
|
|
||||||
import java.util.ArrayList;
|
import java.util.ArrayList;
|
||||||
@@ -53,12 +51,18 @@ public class PetFragment extends Fragment implements PetAdapter.OnPetClickListen
|
|||||||
private EditText etSearch;
|
private EditText etSearch;
|
||||||
private Spinner spinnerStatus;
|
private Spinner spinnerStatus;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Initializes the fragment and its associated PetViewModel.
|
||||||
|
*/
|
||||||
@Override
|
@Override
|
||||||
public void onCreate(@Nullable Bundle savedInstanceState) {
|
public void onCreate(@Nullable Bundle savedInstanceState) {
|
||||||
super.onCreate(savedInstanceState);
|
super.onCreate(savedInstanceState);
|
||||||
viewModel = new ViewModelProvider(this).get(PetViewModel.class);
|
viewModel = new ViewModelProvider(this).get(PetViewModel.class);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Sets up the fragment's UI components, including RecyclerView, search, status filter, and swipe-to-refresh.
|
||||||
|
*/
|
||||||
@Override
|
@Override
|
||||||
public View onCreateView(LayoutInflater inflater, ViewGroup container,
|
public View onCreateView(LayoutInflater inflater, ViewGroup container,
|
||||||
Bundle savedInstanceState) {
|
Bundle savedInstanceState) {
|
||||||
@@ -72,7 +76,7 @@ public class PetFragment extends Fragment implements PetAdapter.OnPetClickListen
|
|||||||
setupSwipeRefresh(view);
|
setupSwipeRefresh(view);
|
||||||
|
|
||||||
FloatingActionButton fabAddPet = view.findViewById(R.id.fabAddPet);
|
FloatingActionButton fabAddPet = view.findViewById(R.id.fabAddPet);
|
||||||
fabAddPet.setOnClickListener(v -> openPetDetails(-1));
|
fabAddPet.setOnClickListener(v -> openPetDetails());
|
||||||
|
|
||||||
hamburger.setOnClickListener(v -> {
|
hamburger.setOnClickListener(v -> {
|
||||||
Fragment parent = getParentFragment();
|
Fragment parent = getParentFragment();
|
||||||
@@ -87,12 +91,18 @@ public class PetFragment extends Fragment implements PetAdapter.OnPetClickListen
|
|||||||
return view;
|
return view;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Reloads pet data every time the fragment becomes visible.
|
||||||
|
*/
|
||||||
@Override
|
@Override
|
||||||
public void onResume() {
|
public void onResume() {
|
||||||
super.onResume();
|
super.onResume();
|
||||||
loadPetData();
|
loadPetData();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Configures the search bar with a for filtering.
|
||||||
|
*/
|
||||||
private void setupSearch(View view) {
|
private void setupSearch(View view) {
|
||||||
etSearch = view.findViewById(R.id.etSearchPet);
|
etSearch = view.findViewById(R.id.etSearchPet);
|
||||||
etSearch.addTextChangedListener(new TextWatcher() {
|
etSearch.addTextChangedListener(new TextWatcher() {
|
||||||
@@ -104,6 +114,9 @@ public class PetFragment extends Fragment implements PetAdapter.OnPetClickListen
|
|||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Configures the status filter spinner.
|
||||||
|
*/
|
||||||
private void setupStatusFilter(View view) {
|
private void setupStatusFilter(View view) {
|
||||||
spinnerStatus = view.findViewById(R.id.spinnerStatus);
|
spinnerStatus = view.findViewById(R.id.spinnerStatus);
|
||||||
String[] statuses = {"All Statuses", "Available", "Adopted"};
|
String[] statuses = {"All Statuses", "Available", "Adopted"};
|
||||||
@@ -122,6 +135,9 @@ public class PetFragment extends Fragment implements PetAdapter.OnPetClickListen
|
|||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Filters the pet list based on both the search query and the selected status.
|
||||||
|
*/
|
||||||
private void filterPets() {
|
private void filterPets() {
|
||||||
String query = etSearch.getText().toString().toLowerCase();
|
String query = etSearch.getText().toString().toLowerCase();
|
||||||
String selectedStatus = spinnerStatus.getSelectedItem().toString();
|
String selectedStatus = spinnerStatus.getSelectedItem().toString();
|
||||||
@@ -143,11 +159,17 @@ public class PetFragment extends Fragment implements PetAdapter.OnPetClickListen
|
|||||||
adapter.notifyDataSetChanged();
|
adapter.notifyDataSetChanged();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Sets up the SwipeRefreshLayout to allow manual re-fetching of pet data.
|
||||||
|
*/
|
||||||
private void setupSwipeRefresh(View view) {
|
private void setupSwipeRefresh(View view) {
|
||||||
swipeRefreshLayout = view.findViewById(R.id.swipeRefreshPet);
|
swipeRefreshLayout = view.findViewById(R.id.swipeRefreshPet);
|
||||||
swipeRefreshLayout.setOnRefreshListener(this::loadPetData);
|
swipeRefreshLayout.setOnRefreshListener(this::loadPetData);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Navigates to the pet profile screen for a specific pet.
|
||||||
|
*/
|
||||||
private void openPetProfile(int position) {
|
private void openPetProfile(int position) {
|
||||||
Bundle args = new Bundle();
|
Bundle args = new Bundle();
|
||||||
PetDTO pet = filteredList.get(position);
|
PetDTO pet = filteredList.get(position);
|
||||||
@@ -167,16 +189,24 @@ public class PetFragment extends Fragment implements PetAdapter.OnPetClickListen
|
|||||||
NavHostFragment.findNavController(this).navigate(R.id.nav_pet_profile, args);
|
NavHostFragment.findNavController(this).navigate(R.id.nav_pet_profile, args);
|
||||||
}
|
}
|
||||||
|
|
||||||
private void openPetDetails(int position) {
|
/**
|
||||||
|
* Navigates to the pet detail screen. (Only used for adding a new pet on this screen)
|
||||||
|
*/
|
||||||
|
private void openPetDetails() {
|
||||||
NavHostFragment.findNavController(this).navigate(R.id.nav_pet_detail);
|
NavHostFragment.findNavController(this).navigate(R.id.nav_pet_detail);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Handles clicks on individual pet items in the list.
|
||||||
|
*/
|
||||||
@Override
|
@Override
|
||||||
public void onPetClick(int position) {
|
public void onPetClick(int position) {
|
||||||
openPetProfile(position);
|
openPetProfile(position);
|
||||||
}
|
}
|
||||||
|
|
||||||
// Helper function to get a list of all pets from the backend
|
/**
|
||||||
|
* Fetches all pet data from the server via the ViewModel and updates the UI.
|
||||||
|
*/
|
||||||
private void loadPetData() {
|
private void loadPetData() {
|
||||||
//Load all pets from the backend using viewModel
|
//Load all pets from the backend using viewModel
|
||||||
viewModel.getAllPets(0, 100).observe(getViewLifecycleOwner(), resource -> {
|
viewModel.getAllPets(0, 100).observe(getViewLifecycleOwner(), resource -> {
|
||||||
@@ -207,6 +237,9 @@ public class PetFragment extends Fragment implements PetAdapter.OnPetClickListen
|
|||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Initializes the RecyclerView with a layout manager and adapter for displaying pets.
|
||||||
|
*/
|
||||||
private void setupRecyclerView(View view) {
|
private void setupRecyclerView(View view) {
|
||||||
RecyclerView recyclerView = view.findViewById(R.id.recyclerViewPets);
|
RecyclerView recyclerView = view.findViewById(R.id.recyclerViewPets);
|
||||||
adapter = new PetAdapter(filteredList, this);
|
adapter = new PetAdapter(filteredList, this);
|
||||||
|
|||||||
@@ -41,12 +41,18 @@ public class ProductFragment extends Fragment implements ProductAdapter.OnProduc
|
|||||||
@Inject @Named("baseUrl") String baseUrl;
|
@Inject @Named("baseUrl") String baseUrl;
|
||||||
@Inject TokenManager tokenManager;
|
@Inject TokenManager tokenManager;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Initializes the fragment and its associated ProductViewModel.
|
||||||
|
*/
|
||||||
@Override
|
@Override
|
||||||
public void onCreate(@Nullable Bundle savedInstanceState) {
|
public void onCreate(@Nullable Bundle savedInstanceState) {
|
||||||
super.onCreate(savedInstanceState);
|
super.onCreate(savedInstanceState);
|
||||||
viewModel = new ViewModelProvider(this).get(ProductViewModel.class);
|
viewModel = new ViewModelProvider(this).get(ProductViewModel.class);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Sets up the fragment's UI components, including the product list, search, and swipe-to-refresh.
|
||||||
|
*/
|
||||||
@Override
|
@Override
|
||||||
public View onCreateView(@NonNull LayoutInflater inflater, ViewGroup container,
|
public View onCreateView(@NonNull LayoutInflater inflater, ViewGroup container,
|
||||||
Bundle savedInstanceState) {
|
Bundle savedInstanceState) {
|
||||||
@@ -75,6 +81,9 @@ public class ProductFragment extends Fragment implements ProductAdapter.OnProduc
|
|||||||
return view;
|
return view;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Initializes the RecyclerView with a layout manager and adapter for displaying products.
|
||||||
|
*/
|
||||||
private void setupRecyclerView(View view) {
|
private void setupRecyclerView(View view) {
|
||||||
RecyclerView rv = view.findViewById(R.id.recyclerViewProducts);
|
RecyclerView rv = view.findViewById(R.id.recyclerViewProducts);
|
||||||
adapter = new ProductAdapter(filteredList, this);
|
adapter = new ProductAdapter(filteredList, this);
|
||||||
@@ -84,6 +93,9 @@ public class ProductFragment extends Fragment implements ProductAdapter.OnProduc
|
|||||||
rv.setAdapter(adapter);
|
rv.setAdapter(adapter);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Configures the search bar for filtering.
|
||||||
|
*/
|
||||||
private void setupSearch(View view) {
|
private void setupSearch(View view) {
|
||||||
etSearch = view.findViewById(R.id.etSearchProduct);
|
etSearch = view.findViewById(R.id.etSearchProduct);
|
||||||
etSearch.addTextChangedListener(new TextWatcher() {
|
etSearch.addTextChangedListener(new TextWatcher() {
|
||||||
@@ -95,11 +107,17 @@ public class ProductFragment extends Fragment implements ProductAdapter.OnProduc
|
|||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Sets up the SwipeRefreshLayout to allow manual re-fetching of product data.
|
||||||
|
*/
|
||||||
private void setupSwipeRefresh(View view) {
|
private void setupSwipeRefresh(View view) {
|
||||||
swipeRefresh = view.findViewById(R.id.swipeRefreshProduct);
|
swipeRefresh = view.findViewById(R.id.swipeRefreshProduct);
|
||||||
swipeRefresh.setOnRefreshListener(this::loadProducts);
|
swipeRefresh.setOnRefreshListener(this::loadProducts);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Filters the product list based on the search query across name, category, and description.
|
||||||
|
*/
|
||||||
private void filter() {
|
private void filter() {
|
||||||
String query = etSearch.getText().toString().toLowerCase();
|
String query = etSearch.getText().toString().toLowerCase();
|
||||||
|
|
||||||
@@ -117,7 +135,9 @@ public class ProductFragment extends Fragment implements ProductAdapter.OnProduc
|
|||||||
adapter.notifyDataSetChanged();
|
adapter.notifyDataSetChanged();
|
||||||
}
|
}
|
||||||
|
|
||||||
// Helper function to get a list of all products from the backend
|
/**
|
||||||
|
* Fetches all product data from the server through the ViewModel and updates the UI.
|
||||||
|
*/
|
||||||
private void loadProducts() {
|
private void loadProducts() {
|
||||||
//Load all products from the backend using viewModel
|
//Load all products from the backend using viewModel
|
||||||
viewModel.getAllProducts(null, 0, 100).observe(getViewLifecycleOwner(), resource -> {
|
viewModel.getAllProducts(null, 0, 100).observe(getViewLifecycleOwner(), resource -> {
|
||||||
@@ -148,6 +168,9 @@ public class ProductFragment extends Fragment implements ProductAdapter.OnProduc
|
|||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Navigates to the product detail screen for a specific product or to add a new one.
|
||||||
|
*/
|
||||||
private void openDetail(int position) {
|
private void openDetail(int position) {
|
||||||
Bundle args = new Bundle();
|
Bundle args = new Bundle();
|
||||||
if (position != -1) {
|
if (position != -1) {
|
||||||
@@ -161,6 +184,9 @@ public class ProductFragment extends Fragment implements ProductAdapter.OnProduc
|
|||||||
NavHostFragment.findNavController(this).navigate(R.id.nav_product_detail, args);
|
NavHostFragment.findNavController(this).navigate(R.id.nav_product_detail, args);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Handles item click in the product list.
|
||||||
|
*/
|
||||||
@Override
|
@Override
|
||||||
public void onProductClick(int position) { openDetail(position); }
|
public void onProductClick(int position) { openDetail(position); }
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -35,12 +35,18 @@ public class ProductSupplierFragment extends Fragment
|
|||||||
private EditText etSearch;
|
private EditText etSearch;
|
||||||
private ProductSupplierViewModel viewModel;
|
private ProductSupplierViewModel viewModel;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Initializes the fragment and its associated ProductSupplierViewModel.
|
||||||
|
*/
|
||||||
@Override
|
@Override
|
||||||
public void onCreate(@Nullable Bundle savedInstanceState) {
|
public void onCreate(@Nullable Bundle savedInstanceState) {
|
||||||
super.onCreate(savedInstanceState);
|
super.onCreate(savedInstanceState);
|
||||||
viewModel = new ViewModelProvider(this).get(ProductSupplierViewModel.class);
|
viewModel = new ViewModelProvider(this).get(ProductSupplierViewModel.class);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Sets up the fragment's UI components, including the RecyclerView, search, and swipe-to-refresh.
|
||||||
|
*/
|
||||||
@Override
|
@Override
|
||||||
public View onCreateView(@NonNull LayoutInflater inflater, ViewGroup container,
|
public View onCreateView(@NonNull LayoutInflater inflater, ViewGroup container,
|
||||||
Bundle savedInstanceState) {
|
Bundle savedInstanceState) {
|
||||||
@@ -68,6 +74,9 @@ public class ProductSupplierFragment extends Fragment
|
|||||||
return view;
|
return view;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Initializes the RecyclerView with a layout manager and adapter for product-supplier data.
|
||||||
|
*/
|
||||||
private void setupRecyclerView(View view) {
|
private void setupRecyclerView(View view) {
|
||||||
RecyclerView rv = view.findViewById(R.id.recyclerViewPS);
|
RecyclerView rv = view.findViewById(R.id.recyclerViewPS);
|
||||||
adapter = new ProductSupplierAdapter(filteredList, this);
|
adapter = new ProductSupplierAdapter(filteredList, this);
|
||||||
@@ -75,6 +84,9 @@ public class ProductSupplierFragment extends Fragment
|
|||||||
rv.setAdapter(adapter);
|
rv.setAdapter(adapter);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Configures the search bar for filtering.
|
||||||
|
*/
|
||||||
private void setupSearch(View view) {
|
private void setupSearch(View view) {
|
||||||
etSearch = view.findViewById(R.id.etSearchPS);
|
etSearch = view.findViewById(R.id.etSearchPS);
|
||||||
etSearch.addTextChangedListener(new TextWatcher() {
|
etSearch.addTextChangedListener(new TextWatcher() {
|
||||||
@@ -86,11 +98,17 @@ public class ProductSupplierFragment extends Fragment
|
|||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Sets up the SwipeRefreshLayout to allow manual reloading of product-supplier data.
|
||||||
|
*/
|
||||||
private void setupSwipeRefresh(View view) {
|
private void setupSwipeRefresh(View view) {
|
||||||
swipeRefresh = view.findViewById(R.id.swipeRefreshPS);
|
swipeRefresh = view.findViewById(R.id.swipeRefreshPS);
|
||||||
swipeRefresh.setOnRefreshListener(this::loadData);
|
swipeRefresh.setOnRefreshListener(this::loadData);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Filters the product-supplier list based on the search query.
|
||||||
|
*/
|
||||||
private void filter(String query) {
|
private void filter(String query) {
|
||||||
filteredList.clear();
|
filteredList.clear();
|
||||||
if (query.isEmpty()) {
|
if (query.isEmpty()) {
|
||||||
@@ -107,7 +125,9 @@ public class ProductSupplierFragment extends Fragment
|
|||||||
adapter.notifyDataSetChanged();
|
adapter.notifyDataSetChanged();
|
||||||
}
|
}
|
||||||
|
|
||||||
// Helper function to get a list of all product suppliers from the backend
|
/**
|
||||||
|
* Fetches all product-supplier data from the server through the ViewModel.
|
||||||
|
*/
|
||||||
private void loadData() {
|
private void loadData() {
|
||||||
//Load all product suppliers from the backend using viewModel
|
//Load all product suppliers from the backend using viewModel
|
||||||
viewModel.getAllProductSuppliers(0, 100).observe(getViewLifecycleOwner(), resource -> {
|
viewModel.getAllProductSuppliers(0, 100).observe(getViewLifecycleOwner(), resource -> {
|
||||||
@@ -138,6 +158,9 @@ public class ProductSupplierFragment extends Fragment
|
|||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Navigates to the product-supplier detail screen for a specific item or to add a new record.
|
||||||
|
*/
|
||||||
private void openDetail(int position) {
|
private void openDetail(int position) {
|
||||||
Bundle args = new Bundle();
|
Bundle args = new Bundle();
|
||||||
if (position != -1) {
|
if (position != -1) {
|
||||||
@@ -151,6 +174,9 @@ public class ProductSupplierFragment extends Fragment
|
|||||||
NavHostFragment.findNavController(this).navigate(R.id.nav_product_supplier_detail, args);
|
NavHostFragment.findNavController(this).navigate(R.id.nav_product_supplier_detail, args);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Handles item click in the product-supplier list.
|
||||||
|
*/
|
||||||
@Override
|
@Override
|
||||||
public void onProductSupplierClick(int position) { openDetail(position); }
|
public void onProductSupplierClick(int position) { openDetail(position); }
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -34,12 +34,18 @@ public class PurchaseOrderFragment extends Fragment
|
|||||||
private EditText etSearch;
|
private EditText etSearch;
|
||||||
private PurchaseOrderViewModel viewModel;
|
private PurchaseOrderViewModel viewModel;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Initializes the fragment and its associated PurchaseOrderViewModel.
|
||||||
|
*/
|
||||||
@Override
|
@Override
|
||||||
public void onCreate(@Nullable Bundle savedInstanceState) {
|
public void onCreate(@Nullable Bundle savedInstanceState) {
|
||||||
super.onCreate(savedInstanceState);
|
super.onCreate(savedInstanceState);
|
||||||
viewModel = new ViewModelProvider(this).get(PurchaseOrderViewModel.class);
|
viewModel = new ViewModelProvider(this).get(PurchaseOrderViewModel.class);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Sets up the fragment's UI components, including RecyclerView, search, and swipe-to-refresh.
|
||||||
|
*/
|
||||||
@Override
|
@Override
|
||||||
public View onCreateView(LayoutInflater inflater, ViewGroup container,
|
public View onCreateView(LayoutInflater inflater, ViewGroup container,
|
||||||
Bundle savedInstanceState) {
|
Bundle savedInstanceState) {
|
||||||
@@ -64,6 +70,9 @@ public class PurchaseOrderFragment extends Fragment
|
|||||||
return view;
|
return view;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Initializes the RecyclerView with a layout manager and adapter for purchase orders.
|
||||||
|
*/
|
||||||
private void setupRecyclerView(View view) {
|
private void setupRecyclerView(View view) {
|
||||||
RecyclerView rv = view.findViewById(R.id.recyclerViewPO);
|
RecyclerView rv = view.findViewById(R.id.recyclerViewPO);
|
||||||
adapter = new PurchaseOrderAdapter(filteredList, this);
|
adapter = new PurchaseOrderAdapter(filteredList, this);
|
||||||
@@ -71,6 +80,9 @@ public class PurchaseOrderFragment extends Fragment
|
|||||||
rv.setAdapter(adapter);
|
rv.setAdapter(adapter);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Configures the search bar for filtering.
|
||||||
|
*/
|
||||||
private void setupSearch(View view) {
|
private void setupSearch(View view) {
|
||||||
etSearch = view.findViewById(R.id.etSearchPO);
|
etSearch = view.findViewById(R.id.etSearchPO);
|
||||||
etSearch.addTextChangedListener(new TextWatcher() {
|
etSearch.addTextChangedListener(new TextWatcher() {
|
||||||
@@ -86,11 +98,17 @@ public class PurchaseOrderFragment extends Fragment
|
|||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Sets up the SwipeRefreshLayout to allow manual reloading of purchase order data.
|
||||||
|
*/
|
||||||
private void setupSwipeRefresh(View view) {
|
private void setupSwipeRefresh(View view) {
|
||||||
swipeRefresh = view.findViewById(R.id.swipeRefreshPO);
|
swipeRefresh = view.findViewById(R.id.swipeRefreshPO);
|
||||||
swipeRefresh.setOnRefreshListener(this::loadData);
|
swipeRefresh.setOnRefreshListener(this::loadData);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Filters the purchase order list based on the search query.
|
||||||
|
*/
|
||||||
private void filter(String query) {
|
private void filter(String query) {
|
||||||
filteredList.clear();
|
filteredList.clear();
|
||||||
if (query.isEmpty()) {
|
if (query.isEmpty()) {
|
||||||
@@ -107,7 +125,9 @@ public class PurchaseOrderFragment extends Fragment
|
|||||||
adapter.notifyDataSetChanged();
|
adapter.notifyDataSetChanged();
|
||||||
}
|
}
|
||||||
|
|
||||||
// Helper function to get a list of all purchase orders from the backend
|
/**
|
||||||
|
* Fetches all purchase order data from the server through the ViewModel and updates the UI.
|
||||||
|
*/
|
||||||
private void loadData() {
|
private void loadData() {
|
||||||
//Load all purchase orders from the backend using viewModel
|
//Load all purchase orders from the backend using viewModel
|
||||||
viewModel.getAllPurchaseOrders(0, 100).observe(getViewLifecycleOwner(), resource -> {
|
viewModel.getAllPurchaseOrders(0, 100).observe(getViewLifecycleOwner(), resource -> {
|
||||||
@@ -138,6 +158,9 @@ public class PurchaseOrderFragment extends Fragment
|
|||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Navigates to the purchase order detail screen for a specific record.
|
||||||
|
*/
|
||||||
private void openDetail(int position) {
|
private void openDetail(int position) {
|
||||||
Bundle args = new Bundle();
|
Bundle args = new Bundle();
|
||||||
PurchaseOrderDTO po = filteredList.get(position);
|
PurchaseOrderDTO po = filteredList.get(position);
|
||||||
@@ -148,6 +171,9 @@ public class PurchaseOrderFragment extends Fragment
|
|||||||
NavHostFragment.findNavController(this).navigate(R.id.nav_purchase_order_detail, args);
|
NavHostFragment.findNavController(this).navigate(R.id.nav_purchase_order_detail, args);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Handles item click in the purchase order list.
|
||||||
|
*/
|
||||||
@Override
|
@Override
|
||||||
public void onPurchaseOrderClick(int position) {
|
public void onPurchaseOrderClick(int position) {
|
||||||
openDetail(position);
|
openDetail(position);
|
||||||
|
|||||||
@@ -45,13 +45,18 @@ public class ServiceFragment extends Fragment implements ServiceAdapter.OnServic
|
|||||||
private SwipeRefreshLayout swipeRefreshLayout;
|
private SwipeRefreshLayout swipeRefreshLayout;
|
||||||
private EditText etSearch;
|
private EditText etSearch;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Initializes the fragment and its associated ServiceViewModel.
|
||||||
|
*/
|
||||||
@Override
|
@Override
|
||||||
public void onCreate(@Nullable Bundle savedInstanceState) {
|
public void onCreate(@Nullable Bundle savedInstanceState) {
|
||||||
super.onCreate(savedInstanceState);
|
super.onCreate(savedInstanceState);
|
||||||
viewModel = new ViewModelProvider(this).get(ServiceViewModel.class);
|
viewModel = new ViewModelProvider(this).get(ServiceViewModel.class);
|
||||||
}
|
}
|
||||||
|
|
||||||
//load service view
|
/**
|
||||||
|
* Sets up the fragment's UI components, including RecyclerView, search, and swipe-to-refresh.
|
||||||
|
*/
|
||||||
@Override
|
@Override
|
||||||
public View onCreateView(@NonNull LayoutInflater inflater, ViewGroup container,
|
public View onCreateView(@NonNull LayoutInflater inflater, ViewGroup container,
|
||||||
Bundle savedInstanceState) {
|
Bundle savedInstanceState) {
|
||||||
@@ -82,6 +87,9 @@ public class ServiceFragment extends Fragment implements ServiceAdapter.OnServic
|
|||||||
return view;
|
return view;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Configures the search bar for filtering.
|
||||||
|
*/
|
||||||
private void setupSearch(View view) {
|
private void setupSearch(View view) {
|
||||||
etSearch = view.findViewById(R.id.etSearchService);
|
etSearch = view.findViewById(R.id.etSearchService);
|
||||||
etSearch.addTextChangedListener(new TextWatcher() {
|
etSearch.addTextChangedListener(new TextWatcher() {
|
||||||
@@ -93,6 +101,9 @@ public class ServiceFragment extends Fragment implements ServiceAdapter.OnServic
|
|||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Filters the service list based on the search query across name and description fields.
|
||||||
|
*/
|
||||||
private void filterServices(String query) {
|
private void filterServices(String query) {
|
||||||
filteredList.clear();
|
filteredList.clear();
|
||||||
if (query.isEmpty()) {
|
if (query.isEmpty()) {
|
||||||
@@ -109,12 +120,17 @@ public class ServiceFragment extends Fragment implements ServiceAdapter.OnServic
|
|||||||
adapter.notifyDataSetChanged();
|
adapter.notifyDataSetChanged();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Sets up the SwipeRefreshLayout to allow manual reloading of service data.
|
||||||
|
*/
|
||||||
private void setupSwipeRefresh(View view) {
|
private void setupSwipeRefresh(View view) {
|
||||||
swipeRefreshLayout = view.findViewById(R.id.swipeRefreshService);
|
swipeRefreshLayout = view.findViewById(R.id.swipeRefreshService);
|
||||||
swipeRefreshLayout.setOnRefreshListener(this::loadServiceData);
|
swipeRefreshLayout.setOnRefreshListener(this::loadServiceData);
|
||||||
}
|
}
|
||||||
|
|
||||||
//Open the service detail view depending on the mode
|
/**
|
||||||
|
* Navigates to the service detail screen for editing an existing service or adding a new one.
|
||||||
|
*/
|
||||||
private void openServiceDetails(int position) {
|
private void openServiceDetails(int position) {
|
||||||
//Make a bundle to pass data to the detail fragment
|
//Make a bundle to pass data to the detail fragment
|
||||||
Bundle args = new Bundle();
|
Bundle args = new Bundle();
|
||||||
@@ -133,13 +149,17 @@ public class ServiceFragment extends Fragment implements ServiceAdapter.OnServic
|
|||||||
NavHostFragment.findNavController(this).navigate(R.id.nav_service_detail, args);
|
NavHostFragment.findNavController(this).navigate(R.id.nav_service_detail, args);
|
||||||
}
|
}
|
||||||
|
|
||||||
// Called by ServiceAdapter when a row is clicked to open the details view
|
/**
|
||||||
|
* Handles item click in the service list.
|
||||||
|
*/
|
||||||
@Override
|
@Override
|
||||||
public void onServiceClick(int position) {
|
public void onServiceClick(int position) {
|
||||||
openServiceDetails(position);
|
openServiceDetails(position);
|
||||||
}
|
}
|
||||||
|
|
||||||
// Helper function to get a list of all services from the backend
|
/**
|
||||||
|
* Fetches all service data from the server through the ViewModel and updates the UI.
|
||||||
|
*/
|
||||||
private void loadServiceData() {
|
private void loadServiceData() {
|
||||||
//Load all services from the backend using viewModel
|
//Load all services from the backend using viewModel
|
||||||
viewModel.getAllServices(0, 100).observe(getViewLifecycleOwner(), resource -> {
|
viewModel.getAllServices(0, 100).observe(getViewLifecycleOwner(), resource -> {
|
||||||
@@ -172,7 +192,9 @@ public class ServiceFragment extends Fragment implements ServiceAdapter.OnServic
|
|||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
//set up the recyclerview and adapter
|
/**
|
||||||
|
* Initializes the RecyclerView with a layout manager and adapter for services.
|
||||||
|
*/
|
||||||
private void setupRecyclerView(View view) {
|
private void setupRecyclerView(View view) {
|
||||||
RecyclerView recyclerView = view.findViewById(R.id.recyclerViewServices);
|
RecyclerView recyclerView = view.findViewById(R.id.recyclerViewServices);
|
||||||
adapter = new ServiceAdapter(filteredList, this);
|
adapter = new ServiceAdapter(filteredList, this);
|
||||||
|
|||||||
@@ -45,13 +45,18 @@ public class SupplierFragment extends Fragment implements SupplierAdapter.OnSupp
|
|||||||
private SwipeRefreshLayout swipeRefreshLayout;
|
private SwipeRefreshLayout swipeRefreshLayout;
|
||||||
private EditText etSearch;
|
private EditText etSearch;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Initializes the fragment and its associated SupplierViewModel.
|
||||||
|
*/
|
||||||
@Override
|
@Override
|
||||||
public void onCreate(@Nullable Bundle savedInstanceState) {
|
public void onCreate(@Nullable Bundle savedInstanceState) {
|
||||||
super.onCreate(savedInstanceState);
|
super.onCreate(savedInstanceState);
|
||||||
viewModel = new ViewModelProvider(this).get(SupplierViewModel.class);
|
viewModel = new ViewModelProvider(this).get(SupplierViewModel.class);
|
||||||
}
|
}
|
||||||
|
|
||||||
//load supplier view
|
/**
|
||||||
|
* Sets up the fragment's UI components, including RecyclerView, search, and swipe-to-refresh.
|
||||||
|
*/
|
||||||
@Override
|
@Override
|
||||||
public View onCreateView(@NonNull LayoutInflater inflater, ViewGroup container,
|
public View onCreateView(@NonNull LayoutInflater inflater, ViewGroup container,
|
||||||
Bundle savedInstanceState) {
|
Bundle savedInstanceState) {
|
||||||
@@ -82,6 +87,9 @@ public class SupplierFragment extends Fragment implements SupplierAdapter.OnSupp
|
|||||||
return view;
|
return view;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Configures the search bar for filtering.
|
||||||
|
*/
|
||||||
private void setupSearch(View view) {
|
private void setupSearch(View view) {
|
||||||
etSearch = view.findViewById(R.id.etSearchSupplier);
|
etSearch = view.findViewById(R.id.etSearchSupplier);
|
||||||
etSearch.addTextChangedListener(new TextWatcher() {
|
etSearch.addTextChangedListener(new TextWatcher() {
|
||||||
@@ -93,6 +101,9 @@ public class SupplierFragment extends Fragment implements SupplierAdapter.OnSupp
|
|||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Filters the supplier list based on the search query across company name and contact person.
|
||||||
|
*/
|
||||||
private void filterSuppliers(String query) {
|
private void filterSuppliers(String query) {
|
||||||
filteredList.clear();
|
filteredList.clear();
|
||||||
if (query.isEmpty()) {
|
if (query.isEmpty()) {
|
||||||
@@ -110,12 +121,17 @@ public class SupplierFragment extends Fragment implements SupplierAdapter.OnSupp
|
|||||||
adapter.notifyDataSetChanged();
|
adapter.notifyDataSetChanged();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Sets up the SwipeRefreshLayout to allow manual reloading of supplier data.
|
||||||
|
*/
|
||||||
private void setupSwipeRefresh(View view) {
|
private void setupSwipeRefresh(View view) {
|
||||||
swipeRefreshLayout = view.findViewById(R.id.swipeRefreshSupplier);
|
swipeRefreshLayout = view.findViewById(R.id.swipeRefreshSupplier);
|
||||||
swipeRefreshLayout.setOnRefreshListener(this::loadSupplierData);
|
swipeRefreshLayout.setOnRefreshListener(this::loadSupplierData);
|
||||||
}
|
}
|
||||||
|
|
||||||
//Open the supplier detail view depending on the mode
|
/**
|
||||||
|
* Navigates to the supplier detail screen for editing an existing record or adding a new one.
|
||||||
|
*/
|
||||||
private void openSupplierDetails(int position) {
|
private void openSupplierDetails(int position) {
|
||||||
//Make a bundle to pass data to the detail fragment
|
//Make a bundle to pass data to the detail fragment
|
||||||
Bundle args = new Bundle();
|
Bundle args = new Bundle();
|
||||||
@@ -136,13 +152,17 @@ public class SupplierFragment extends Fragment implements SupplierAdapter.OnSupp
|
|||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
// Called by SupplierAdapter when a row is clicked to open the details view
|
/**
|
||||||
|
* Handles item click in the supplier list.
|
||||||
|
*/
|
||||||
@Override
|
@Override
|
||||||
public void onSupplierClick(int position) {
|
public void onSupplierClick(int position) {
|
||||||
openSupplierDetails(position);
|
openSupplierDetails(position);
|
||||||
}
|
}
|
||||||
|
|
||||||
// Helper function to get a list of all suppliers from the backend
|
/**
|
||||||
|
* Fetches all supplier data from the server through the ViewModel and updates the UI.
|
||||||
|
*/
|
||||||
private void loadSupplierData() {
|
private void loadSupplierData() {
|
||||||
//Load all suppliers from the backend using viewModel
|
//Load all suppliers from the backend using viewModel
|
||||||
viewModel.getAllSuppliers(0, 100).observe(getViewLifecycleOwner(), resource -> {
|
viewModel.getAllSuppliers(0, 100).observe(getViewLifecycleOwner(), resource -> {
|
||||||
@@ -175,7 +195,9 @@ public class SupplierFragment extends Fragment implements SupplierAdapter.OnSupp
|
|||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
//set up the recyclerview and adapter
|
/**
|
||||||
|
* Initializes the RecyclerView with a layout manager and adapter for displaying suppliers.
|
||||||
|
*/
|
||||||
private void setupRecyclerView(View view) {
|
private void setupRecyclerView(View view) {
|
||||||
RecyclerView recyclerView = view.findViewById(R.id.recyclerViewSuppliers);
|
RecyclerView recyclerView = view.findViewById(R.id.recyclerViewSuppliers);
|
||||||
adapter = new SupplierAdapter(filteredList, this);
|
adapter = new SupplierAdapter(filteredList, this);
|
||||||
|
|||||||
@@ -14,7 +14,8 @@ import com.example.petstoremobile.R;
|
|||||||
import com.example.petstoremobile.adapters.BlackTextArrayAdapter;
|
import com.example.petstoremobile.adapters.BlackTextArrayAdapter;
|
||||||
import com.example.petstoremobile.api.*;
|
import com.example.petstoremobile.api.*;
|
||||||
import com.example.petstoremobile.dtos.*;
|
import com.example.petstoremobile.dtos.*;
|
||||||
import com.example.petstoremobile.fragments.ListFragment;
|
import com.example.petstoremobile.utils.ErrorUtils;
|
||||||
|
|
||||||
import java.util.*;
|
import java.util.*;
|
||||||
|
|
||||||
import javax.inject.Inject;
|
import javax.inject.Inject;
|
||||||
@@ -22,6 +23,9 @@ import javax.inject.Inject;
|
|||||||
import dagger.hilt.android.AndroidEntryPoint;
|
import dagger.hilt.android.AndroidEntryPoint;
|
||||||
import retrofit2.*;
|
import retrofit2.*;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Fragment for displaying and editing adoption request details.
|
||||||
|
*/
|
||||||
@AndroidEntryPoint
|
@AndroidEntryPoint
|
||||||
public class AdoptionDetailFragment extends Fragment {
|
public class AdoptionDetailFragment extends Fragment {
|
||||||
|
|
||||||
@@ -60,6 +64,9 @@ public class AdoptionDetailFragment extends Fragment {
|
|||||||
return view;
|
return view;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Initializes UI components from the layout.
|
||||||
|
*/
|
||||||
private void initViews(View v) {
|
private void initViews(View v) {
|
||||||
tvMode = v.findViewById(R.id.tvAdoptionMode);
|
tvMode = v.findViewById(R.id.tvAdoptionMode);
|
||||||
tvAdoptionId = v.findViewById(R.id.tvAdoptionId);
|
tvAdoptionId = v.findViewById(R.id.tvAdoptionId);
|
||||||
@@ -72,11 +79,17 @@ public class AdoptionDetailFragment extends Fragment {
|
|||||||
btnBack = v.findViewById(R.id.btnAdoptionBack);
|
btnBack = v.findViewById(R.id.btnAdoptionBack);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Configures the spinner for adoption status.
|
||||||
|
*/
|
||||||
private void setupSpinners() {
|
private void setupSpinners() {
|
||||||
spinnerStatus.setAdapter(new BlackTextArrayAdapter<>(requireContext(),
|
spinnerStatus.setAdapter(new BlackTextArrayAdapter<>(requireContext(),
|
||||||
android.R.layout.simple_spinner_item, STATUSES));
|
android.R.layout.simple_spinner_item, STATUSES));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Configures the date picker dialog for the adoption date field.
|
||||||
|
*/
|
||||||
private void setupDatePicker() {
|
private void setupDatePicker() {
|
||||||
etAdoptionDate.setOnClickListener(v -> {
|
etAdoptionDate.setOnClickListener(v -> {
|
||||||
Calendar c = Calendar.getInstance();
|
Calendar c = Calendar.getInstance();
|
||||||
@@ -89,11 +102,17 @@ public class AdoptionDetailFragment extends Fragment {
|
|||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Fetches required data (pets and customers) from the backend.
|
||||||
|
*/
|
||||||
private void loadData() {
|
private void loadData() {
|
||||||
loadPets();
|
loadPets();
|
||||||
loadCustomers();
|
loadCustomers();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Loads the list of pets from the API.
|
||||||
|
*/
|
||||||
private void loadPets() {
|
private void loadPets() {
|
||||||
petApi.getAllPets(0, 200)
|
petApi.getAllPets(0, 200)
|
||||||
.enqueue(new Callback<PageResponse<PetDTO>>() {
|
.enqueue(new Callback<PageResponse<PetDTO>>() {
|
||||||
@@ -110,6 +129,9 @@ public class AdoptionDetailFragment extends Fragment {
|
|||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Populates the pet selection spinner.
|
||||||
|
*/
|
||||||
private void populatePetSpinner() {
|
private void populatePetSpinner() {
|
||||||
List<String> names = new ArrayList<>();
|
List<String> names = new ArrayList<>();
|
||||||
names.add("-- Select Pet --");
|
names.add("-- Select Pet --");
|
||||||
@@ -125,6 +147,9 @@ public class AdoptionDetailFragment extends Fragment {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Loads the list of customers from the API.
|
||||||
|
*/
|
||||||
private void loadCustomers() {
|
private void loadCustomers() {
|
||||||
customerApi.getAllCustomers(0, 200)
|
customerApi.getAllCustomers(0, 200)
|
||||||
.enqueue(new Callback<PageResponse<CustomerDTO>>() {
|
.enqueue(new Callback<PageResponse<CustomerDTO>>() {
|
||||||
@@ -141,6 +166,9 @@ public class AdoptionDetailFragment extends Fragment {
|
|||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Populates the customer selection spinner.
|
||||||
|
*/
|
||||||
private void populateCustomerSpinner() {
|
private void populateCustomerSpinner() {
|
||||||
List<String> names = new ArrayList<>();
|
List<String> names = new ArrayList<>();
|
||||||
names.add("-- Select Customer --");
|
names.add("-- Select Customer --");
|
||||||
@@ -157,6 +185,9 @@ public class AdoptionDetailFragment extends Fragment {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Handles arguments to determine if the fragment is in edit or add mode.
|
||||||
|
*/
|
||||||
private void handleArguments() {
|
private void handleArguments() {
|
||||||
Bundle a = getArguments();
|
Bundle a = getArguments();
|
||||||
if (a != null && a.containsKey("adoptionId")) {
|
if (a != null && a.containsKey("adoptionId")) {
|
||||||
@@ -185,6 +216,9 @@ public class AdoptionDetailFragment extends Fragment {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Validates input and saves the adoption request to the backend.
|
||||||
|
*/
|
||||||
private void saveAdoption() {
|
private void saveAdoption() {
|
||||||
if (spinnerCustomer.getSelectedItemPosition() == 0) {
|
if (spinnerCustomer.getSelectedItemPosition() == 0) {
|
||||||
Toast.makeText(getContext(), "Select a customer", Toast.LENGTH_SHORT).show(); return;
|
Toast.makeText(getContext(), "Select a customer", Toast.LENGTH_SHORT).show(); return;
|
||||||
@@ -219,6 +253,9 @@ public class AdoptionDetailFragment extends Fragment {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* callback for adoption save/update operations.
|
||||||
|
*/
|
||||||
private Callback<AdoptionDTO> simpleCallback(String msg) {
|
private Callback<AdoptionDTO> simpleCallback(String msg) {
|
||||||
return new Callback<>() {
|
return new Callback<>() {
|
||||||
public void onResponse(Call<AdoptionDTO> c, Response<AdoptionDTO> r) {
|
public void onResponse(Call<AdoptionDTO> c, Response<AdoptionDTO> r) {
|
||||||
@@ -227,13 +264,7 @@ public class AdoptionDetailFragment extends Fragment {
|
|||||||
Toast.makeText(getContext(), msg, Toast.LENGTH_SHORT).show();
|
Toast.makeText(getContext(), msg, Toast.LENGTH_SHORT).show();
|
||||||
navigateBack();
|
navigateBack();
|
||||||
} else {
|
} else {
|
||||||
try {
|
ErrorUtils.showErrorMessage(getContext(), r, "Error " + r.code());
|
||||||
String err = r.errorBody().string();
|
|
||||||
Log.e("ADOPTION_SAVE", "Error: " + err);
|
|
||||||
Toast.makeText(getContext(), "Error " + r.code(), Toast.LENGTH_SHORT).show();
|
|
||||||
} catch (Exception e) {
|
|
||||||
Log.e("ADOPTION_SAVE", "Failed to read error");
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
public void onFailure(Call<AdoptionDTO> c, Throwable t) {
|
public void onFailure(Call<AdoptionDTO> c, Throwable t) {
|
||||||
@@ -243,15 +274,16 @@ public class AdoptionDetailFragment extends Fragment {
|
|||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Shows a confirmation dialog before deleting an adoption request.
|
||||||
|
*/
|
||||||
private void confirmDelete() {
|
private void confirmDelete() {
|
||||||
new AlertDialog.Builder(requireContext())
|
new AlertDialog.Builder(requireContext())
|
||||||
.setTitle("Delete Adoption?")
|
.setTitle("Delete Adoption?")
|
||||||
.setPositiveButton("Yes", (d, w) ->
|
.setPositiveButton("Yes", (d, w) ->
|
||||||
adoptionApi.deleteAdoption(adoptionId)
|
adoptionApi.deleteAdoption(adoptionId)
|
||||||
.enqueue(new Callback<Void>() {
|
.enqueue(new Callback<Void>() {
|
||||||
public void onResponse(Call<Void> c, Response<Void> r) {
|
public void onResponse(Call<Void> c, Response<Void> r) { navigateBack(); }
|
||||||
navigateBack();
|
|
||||||
}
|
|
||||||
public void onFailure(Call<Void> c, Throwable t) {
|
public void onFailure(Call<Void> c, Throwable t) {
|
||||||
Toast.makeText(getContext(), "Delete failed",
|
Toast.makeText(getContext(), "Delete failed",
|
||||||
Toast.LENGTH_SHORT).show();
|
Toast.LENGTH_SHORT).show();
|
||||||
@@ -260,6 +292,9 @@ public class AdoptionDetailFragment extends Fragment {
|
|||||||
.setNegativeButton("No", null).show();
|
.setNegativeButton("No", null).show();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Navigates back to the previous fragment.
|
||||||
|
*/
|
||||||
private void navigateBack() {
|
private void navigateBack() {
|
||||||
NavHostFragment.findNavController(this).popBackStack();
|
NavHostFragment.findNavController(this).popBackStack();
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -14,7 +14,8 @@ import com.example.petstoremobile.R;
|
|||||||
import com.example.petstoremobile.adapters.BlackTextArrayAdapter;
|
import com.example.petstoremobile.adapters.BlackTextArrayAdapter;
|
||||||
import com.example.petstoremobile.api.*;
|
import com.example.petstoremobile.api.*;
|
||||||
import com.example.petstoremobile.dtos.*;
|
import com.example.petstoremobile.dtos.*;
|
||||||
import com.example.petstoremobile.fragments.ListFragment;
|
import com.example.petstoremobile.utils.ErrorUtils;
|
||||||
|
|
||||||
import java.util.*;
|
import java.util.*;
|
||||||
|
|
||||||
import javax.inject.Inject;
|
import javax.inject.Inject;
|
||||||
@@ -22,6 +23,9 @@ import javax.inject.Inject;
|
|||||||
import dagger.hilt.android.AndroidEntryPoint;
|
import dagger.hilt.android.AndroidEntryPoint;
|
||||||
import retrofit2.*;
|
import retrofit2.*;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Fragment for displaying and editing appointment details.
|
||||||
|
*/
|
||||||
@AndroidEntryPoint
|
@AndroidEntryPoint
|
||||||
public class AppointmentDetailFragment extends Fragment {
|
public class AppointmentDetailFragment extends Fragment {
|
||||||
|
|
||||||
@@ -69,6 +73,9 @@ public class AppointmentDetailFragment extends Fragment {
|
|||||||
return view;
|
return view;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Initializes UI components from the layout.
|
||||||
|
*/
|
||||||
private void initViews(View v) {
|
private void initViews(View v) {
|
||||||
tvMode = v.findViewById(R.id.tvApptMode);
|
tvMode = v.findViewById(R.id.tvApptMode);
|
||||||
tvAppointmentId = v.findViewById(R.id.tvAppointmentId);
|
tvAppointmentId = v.findViewById(R.id.tvAppointmentId);
|
||||||
@@ -85,6 +92,9 @@ public class AppointmentDetailFragment extends Fragment {
|
|||||||
btnBack = v.findViewById(R.id.btnApptBack);
|
btnBack = v.findViewById(R.id.btnApptBack);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Configures the adapters for spinners.
|
||||||
|
*/
|
||||||
private void setupSpinners() {
|
private void setupSpinners() {
|
||||||
spinnerStatus.setAdapter(new BlackTextArrayAdapter<>(requireContext(),
|
spinnerStatus.setAdapter(new BlackTextArrayAdapter<>(requireContext(),
|
||||||
android.R.layout.simple_spinner_item, STATUSES));
|
android.R.layout.simple_spinner_item, STATUSES));
|
||||||
@@ -98,6 +108,9 @@ public class AppointmentDetailFragment extends Fragment {
|
|||||||
android.R.layout.simple_spinner_item, new String[]{"00","15","30","45"}));
|
android.R.layout.simple_spinner_item, new String[]{"00","15","30","45"}));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Configures the date picker dialog for the appointment date field.
|
||||||
|
*/
|
||||||
private void setupDatePicker() {
|
private void setupDatePicker() {
|
||||||
etAppointmentDate.setOnClickListener(v -> {
|
etAppointmentDate.setOnClickListener(v -> {
|
||||||
Calendar c = Calendar.getInstance();
|
Calendar c = Calendar.getInstance();
|
||||||
@@ -111,6 +124,9 @@ public class AppointmentDetailFragment extends Fragment {
|
|||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Fetches all required data from the backend to populate the fragment.
|
||||||
|
*/
|
||||||
private void loadData() {
|
private void loadData() {
|
||||||
loadPets();
|
loadPets();
|
||||||
loadServices();
|
loadServices();
|
||||||
@@ -119,6 +135,9 @@ public class AppointmentDetailFragment extends Fragment {
|
|||||||
loadAllAppointments();
|
loadAllAppointments();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Loads the list of pets from the API.
|
||||||
|
*/
|
||||||
private void loadPets() {
|
private void loadPets() {
|
||||||
petApi.getAllPets(0, 200)
|
petApi.getAllPets(0, 200)
|
||||||
.enqueue(new Callback<PageResponse<PetDTO>>() {
|
.enqueue(new Callback<PageResponse<PetDTO>>() {
|
||||||
@@ -134,6 +153,9 @@ public class AppointmentDetailFragment extends Fragment {
|
|||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Populates the pet selection spinner.
|
||||||
|
*/
|
||||||
private void populatePetSpinner() {
|
private void populatePetSpinner() {
|
||||||
List<String> names = new ArrayList<>();
|
List<String> names = new ArrayList<>();
|
||||||
names.add("-- Select Pet --");
|
names.add("-- Select Pet --");
|
||||||
@@ -149,6 +171,9 @@ public class AppointmentDetailFragment extends Fragment {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Loads the list of services from the API.
|
||||||
|
*/
|
||||||
private void loadServices() {
|
private void loadServices() {
|
||||||
serviceApi.getAllServices(0, 200)
|
serviceApi.getAllServices(0, 200)
|
||||||
.enqueue(new Callback<PageResponse<ServiceDTO>>() {
|
.enqueue(new Callback<PageResponse<ServiceDTO>>() {
|
||||||
@@ -164,6 +189,9 @@ public class AppointmentDetailFragment extends Fragment {
|
|||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Populates the service selection spinner.
|
||||||
|
*/
|
||||||
private void populateServiceSpinner() {
|
private void populateServiceSpinner() {
|
||||||
List<String> names = new ArrayList<>();
|
List<String> names = new ArrayList<>();
|
||||||
names.add("-- Select Service --");
|
names.add("-- Select Service --");
|
||||||
@@ -179,6 +207,9 @@ public class AppointmentDetailFragment extends Fragment {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Loads the list of customers from the API.
|
||||||
|
*/
|
||||||
private void loadCustomers() {
|
private void loadCustomers() {
|
||||||
customerApi.getAllCustomers(0, 200)
|
customerApi.getAllCustomers(0, 200)
|
||||||
.enqueue(new Callback<PageResponse<CustomerDTO>>() {
|
.enqueue(new Callback<PageResponse<CustomerDTO>>() {
|
||||||
@@ -194,6 +225,9 @@ public class AppointmentDetailFragment extends Fragment {
|
|||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Populates the customer spinner.
|
||||||
|
*/
|
||||||
private void populateCustomerSpinner() {
|
private void populateCustomerSpinner() {
|
||||||
List<String> names = new ArrayList<>();
|
List<String> names = new ArrayList<>();
|
||||||
names.add("-- Select Customer --");
|
names.add("-- Select Customer --");
|
||||||
@@ -210,6 +244,9 @@ public class AppointmentDetailFragment extends Fragment {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Loads the list of stores from the API.
|
||||||
|
*/
|
||||||
private void loadStores() {
|
private void loadStores() {
|
||||||
storeApi.getAllStores(0, 50)
|
storeApi.getAllStores(0, 50)
|
||||||
.enqueue(new Callback<PageResponse<StoreDTO>>() {
|
.enqueue(new Callback<PageResponse<StoreDTO>>() {
|
||||||
@@ -225,6 +262,9 @@ public class AppointmentDetailFragment extends Fragment {
|
|||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Populates the store spinner.
|
||||||
|
*/
|
||||||
private void populateStoreSpinner() {
|
private void populateStoreSpinner() {
|
||||||
List<String> names = new ArrayList<>();
|
List<String> names = new ArrayList<>();
|
||||||
names.add("-- Select Store --");
|
names.add("-- Select Store --");
|
||||||
@@ -240,6 +280,9 @@ public class AppointmentDetailFragment extends Fragment {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Loads all appointments from the API.
|
||||||
|
*/
|
||||||
private void loadAllAppointments() {
|
private void loadAllAppointments() {
|
||||||
appointmentApi.getAllAppointments(0, 500)
|
appointmentApi.getAllAppointments(0, 500)
|
||||||
.enqueue(new Callback<PageResponse<AppointmentDTO>>() {
|
.enqueue(new Callback<PageResponse<AppointmentDTO>>() {
|
||||||
@@ -251,6 +294,9 @@ public class AppointmentDetailFragment extends Fragment {
|
|||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Handles arguments to determine if the fragment is in edit or add mode.
|
||||||
|
*/
|
||||||
private void handleArguments() {
|
private void handleArguments() {
|
||||||
Bundle a = getArguments();
|
Bundle a = getArguments();
|
||||||
if (a != null && a.containsKey("appointmentId")) {
|
if (a != null && a.containsKey("appointmentId")) {
|
||||||
@@ -292,6 +338,9 @@ public class AppointmentDetailFragment extends Fragment {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Validates input and saves the appointment to the backend.
|
||||||
|
*/
|
||||||
private void saveAppointment() {
|
private void saveAppointment() {
|
||||||
if (spinnerCustomer.getSelectedItemPosition() == 0) {
|
if (spinnerCustomer.getSelectedItemPosition() == 0) {
|
||||||
Toast.makeText(getContext(), "Select a customer", Toast.LENGTH_SHORT).show(); return;
|
Toast.makeText(getContext(), "Select a customer", Toast.LENGTH_SHORT).show(); return;
|
||||||
@@ -370,6 +419,9 @@ public class AppointmentDetailFragment extends Fragment {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* callback for appointment save/update operations.
|
||||||
|
*/
|
||||||
private Callback<AppointmentDTO> simpleCallback(String msg) {
|
private Callback<AppointmentDTO> simpleCallback(String msg) {
|
||||||
return new Callback<>() {
|
return new Callback<>() {
|
||||||
public void onResponse(Call<AppointmentDTO> c, Response<AppointmentDTO> r) {
|
public void onResponse(Call<AppointmentDTO> c, Response<AppointmentDTO> r) {
|
||||||
@@ -387,22 +439,12 @@ public class AppointmentDetailFragment extends Fragment {
|
|||||||
showErrorDialog("Invalid Date/Time",
|
showErrorDialog("Invalid Date/Time",
|
||||||
"Booked appointments must be scheduled in the future. " +
|
"Booked appointments must be scheduled in the future. " +
|
||||||
"Please select a future date and time.");
|
"Please select a future date and time.");
|
||||||
//------------------------------------------
|
|
||||||
} else if (errorBody.toLowerCase().contains("not available") ||
|
} else if (errorBody.toLowerCase().contains("not available") ||
|
||||||
errorBody.toLowerCase().contains("time is not available")) {
|
errorBody.toLowerCase().contains("time is not available")) {
|
||||||
showNoAvailabilityDialog();
|
showNoAvailabilityDialog();
|
||||||
} else if (r.code() == 404) {
|
|
||||||
showErrorDialog("Not Found",
|
|
||||||
"The selected pet, customer or service was not found.");
|
|
||||||
} else if (r.code() == 403) {
|
|
||||||
showErrorDialog("Access Denied",
|
|
||||||
"You don't have permission to perform this action.");
|
|
||||||
} else if (r.code() == 400) {
|
|
||||||
showErrorDialog("Invalid Request", errorBody);
|
|
||||||
} else {
|
} else {
|
||||||
showErrorDialog("Error", "Something went wrong. Please try again.");
|
ErrorUtils.showErrorMessage(getContext(), r, "Something went wrong. Please try again.");
|
||||||
}
|
}
|
||||||
//-----------------------------
|
|
||||||
} catch (Exception e) {
|
} catch (Exception e) {
|
||||||
Log.e("APPT_SAVE", "Failed to read error body");
|
Log.e("APPT_SAVE", "Failed to read error body");
|
||||||
showErrorDialog("Error", "Something went wrong. Please try again.");
|
showErrorDialog("Error", "Something went wrong. Please try again.");
|
||||||
@@ -417,6 +459,9 @@ public class AppointmentDetailFragment extends Fragment {
|
|||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Shows a specialized dialog when a time slot is not available.
|
||||||
|
*/
|
||||||
private void showNoAvailabilityDialog() {
|
private void showNoAvailabilityDialog() {
|
||||||
new AlertDialog.Builder(requireContext())
|
new AlertDialog.Builder(requireContext())
|
||||||
.setTitle("No Availability")
|
.setTitle("No Availability")
|
||||||
@@ -427,6 +472,9 @@ public class AppointmentDetailFragment extends Fragment {
|
|||||||
.show();
|
.show();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Shows a generic error dialog with a title and message.
|
||||||
|
*/
|
||||||
private void showErrorDialog(String title, String message) {
|
private void showErrorDialog(String title, String message) {
|
||||||
new AlertDialog.Builder(requireContext())
|
new AlertDialog.Builder(requireContext())
|
||||||
.setTitle(title)
|
.setTitle(title)
|
||||||
@@ -434,6 +482,10 @@ public class AppointmentDetailFragment extends Fragment {
|
|||||||
.setPositiveButton("OK", null)
|
.setPositiveButton("OK", null)
|
||||||
.show();
|
.show();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Shows a confirmation dialog and handles the deletion of an appointment.
|
||||||
|
*/
|
||||||
private void confirmDelete() {
|
private void confirmDelete() {
|
||||||
new AlertDialog.Builder(requireContext())
|
new AlertDialog.Builder(requireContext())
|
||||||
.setTitle("Delete Appointment?")
|
.setTitle("Delete Appointment?")
|
||||||
@@ -448,6 +500,9 @@ public class AppointmentDetailFragment extends Fragment {
|
|||||||
.setNegativeButton("No", null).show();
|
.setNegativeButton("No", null).show();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Navigates back to the previous screen.
|
||||||
|
*/
|
||||||
private void navigateBack() {
|
private void navigateBack() {
|
||||||
NavHostFragment.findNavController(this).popBackStack();
|
NavHostFragment.findNavController(this).popBackStack();
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -26,8 +26,8 @@ import com.example.petstoremobile.dtos.InventoryDTO;
|
|||||||
import com.example.petstoremobile.dtos.InventoryRequest;
|
import com.example.petstoremobile.dtos.InventoryRequest;
|
||||||
import com.example.petstoremobile.dtos.PageResponse;
|
import com.example.petstoremobile.dtos.PageResponse;
|
||||||
import com.example.petstoremobile.dtos.ProductDTO;
|
import com.example.petstoremobile.dtos.ProductDTO;
|
||||||
import com.example.petstoremobile.fragments.ListFragment;
|
|
||||||
import com.example.petstoremobile.fragments.listfragments.InventoryFragment;
|
import com.example.petstoremobile.fragments.listfragments.InventoryFragment;
|
||||||
|
import com.example.petstoremobile.utils.ErrorUtils;
|
||||||
|
|
||||||
import java.util.ArrayList;
|
import java.util.ArrayList;
|
||||||
import java.util.List;
|
import java.util.List;
|
||||||
@@ -39,6 +39,9 @@ import retrofit2.Call;
|
|||||||
import retrofit2.Callback;
|
import retrofit2.Callback;
|
||||||
import retrofit2.Response;
|
import retrofit2.Response;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Fragment for displaying and editing inventory item details.
|
||||||
|
*/
|
||||||
@AndroidEntryPoint
|
@AndroidEntryPoint
|
||||||
public class InventoryDetailFragment extends Fragment {
|
public class InventoryDetailFragment extends Fragment {
|
||||||
|
|
||||||
@@ -65,6 +68,9 @@ public class InventoryDetailFragment extends Fragment {
|
|||||||
private final List<ProductDTO> productSuggestions = new ArrayList<>();
|
private final List<ProductDTO> productSuggestions = new ArrayList<>();
|
||||||
private ArrayAdapter<String> dropdownAdapter;
|
private ArrayAdapter<String> dropdownAdapter;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Sets the parent inventory fragment to notify of changes.
|
||||||
|
*/
|
||||||
public void setInventoryFragment(InventoryFragment fragment) {
|
public void setInventoryFragment(InventoryFragment fragment) {
|
||||||
this.inventoryFragment = fragment;
|
this.inventoryFragment = fragment;
|
||||||
}
|
}
|
||||||
@@ -85,6 +91,9 @@ public class InventoryDetailFragment extends Fragment {
|
|||||||
return view;
|
return view;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* get the layout view and set adapter.
|
||||||
|
*/
|
||||||
private void initViews(View view) {
|
private void initViews(View view) {
|
||||||
tvMode = view.findViewById(R.id.tvInventoryMode);
|
tvMode = view.findViewById(R.id.tvInventoryMode);
|
||||||
tvInventoryId = view.findViewById(R.id.tvInventoryId);
|
tvInventoryId = view.findViewById(R.id.tvInventoryId);
|
||||||
@@ -102,7 +111,9 @@ public class InventoryDetailFragment extends Fragment {
|
|||||||
etProductSearch.setThreshold(1); // start showing after 1 character
|
etProductSearch.setThreshold(1); // start showing after 1 character
|
||||||
}
|
}
|
||||||
|
|
||||||
// Product search dropdown
|
/**
|
||||||
|
* setup the product search dropdown.
|
||||||
|
*/
|
||||||
private void setupProductSearch() {
|
private void setupProductSearch() {
|
||||||
etProductSearch.addTextChangedListener(new TextWatcher() {
|
etProductSearch.addTextChangedListener(new TextWatcher() {
|
||||||
@Override
|
@Override
|
||||||
@@ -143,6 +154,9 @@ public class InventoryDetailFragment extends Fragment {
|
|||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Searches for products matching the query from the backend.
|
||||||
|
*/
|
||||||
private void searchProducts(String query) {
|
private void searchProducts(String query) {
|
||||||
productApi.getAllProducts(query, 0, 20).enqueue(new Callback<PageResponse<ProductDTO>>() {
|
productApi.getAllProducts(query, 0, 20).enqueue(new Callback<PageResponse<ProductDTO>>() {
|
||||||
@Override
|
@Override
|
||||||
@@ -172,8 +186,9 @@ public class InventoryDetailFragment extends Fragment {
|
|||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
// Arguments (edit mode)
|
/**
|
||||||
|
* arguments to set up edit or add mode.
|
||||||
|
*/
|
||||||
private void handleArguments() {
|
private void handleArguments() {
|
||||||
Bundle args = getArguments();
|
Bundle args = getArguments();
|
||||||
if (args != null && args.containsKey("inventoryId")) {
|
if (args != null && args.containsKey("inventoryId")) {
|
||||||
@@ -214,7 +229,9 @@ public class InventoryDetailFragment extends Fragment {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// Save
|
/**
|
||||||
|
* Saves the current inventory item details to the backend.
|
||||||
|
*/
|
||||||
private void saveInventory() {
|
private void saveInventory() {
|
||||||
if (selectedProduct == null) {
|
if (selectedProduct == null) {
|
||||||
etProductSearch.setError("Please select a product from the list");
|
etProductSearch.setError("Please select a product from the list");
|
||||||
@@ -255,7 +272,7 @@ public class InventoryDetailFragment extends Fragment {
|
|||||||
Toast.makeText(getContext(), "Inventory updated", Toast.LENGTH_SHORT).show();
|
Toast.makeText(getContext(), "Inventory updated", Toast.LENGTH_SHORT).show();
|
||||||
notifyParentAndGoBack();
|
notifyParentAndGoBack();
|
||||||
} else {
|
} else {
|
||||||
Toast.makeText(getContext(), "Update failed: " + response.code(), Toast.LENGTH_SHORT).show();
|
ErrorUtils.showErrorMessage(getContext(), response, "Update failed");
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -274,7 +291,7 @@ public class InventoryDetailFragment extends Fragment {
|
|||||||
Toast.makeText(getContext(), "Inventory created", Toast.LENGTH_SHORT).show();
|
Toast.makeText(getContext(), "Inventory created", Toast.LENGTH_SHORT).show();
|
||||||
notifyParentAndGoBack();
|
notifyParentAndGoBack();
|
||||||
} else {
|
} else {
|
||||||
Toast.makeText(getContext(), "Create failed: " + response.code(), Toast.LENGTH_SHORT).show();
|
ErrorUtils.showErrorMessage(getContext(), response, "Create failed");
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -287,7 +304,9 @@ public class InventoryDetailFragment extends Fragment {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// Delete
|
/**
|
||||||
|
* Shows a confirmation dialog before deleting an inventory item.
|
||||||
|
*/
|
||||||
private void confirmDelete() {
|
private void confirmDelete() {
|
||||||
new AlertDialog.Builder(requireContext())
|
new AlertDialog.Builder(requireContext())
|
||||||
.setTitle("Delete inventory item?")
|
.setTitle("Delete inventory item?")
|
||||||
@@ -297,6 +316,9 @@ public class InventoryDetailFragment extends Fragment {
|
|||||||
.show();
|
.show();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Sends a request to the API to delete the inventory item.
|
||||||
|
*/
|
||||||
private void deleteInventory() {
|
private void deleteInventory() {
|
||||||
setButtonsEnabled(false);
|
setButtonsEnabled(false);
|
||||||
inventoryApi.deleteInventory(inventoryId).enqueue(new Callback<Void>() {
|
inventoryApi.deleteInventory(inventoryId).enqueue(new Callback<Void>() {
|
||||||
@@ -307,7 +329,7 @@ public class InventoryDetailFragment extends Fragment {
|
|||||||
Toast.makeText(getContext(), "Inventory deleted", Toast.LENGTH_SHORT).show();
|
Toast.makeText(getContext(), "Inventory deleted", Toast.LENGTH_SHORT).show();
|
||||||
notifyParentAndGoBack();
|
notifyParentAndGoBack();
|
||||||
} else {
|
} else {
|
||||||
Toast.makeText(getContext(), "Delete failed: " + response.code(), Toast.LENGTH_SHORT).show();
|
ErrorUtils.showErrorMessage(getContext(), response, "Delete failed");
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -319,18 +341,25 @@ public class InventoryDetailFragment extends Fragment {
|
|||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
// Helpers
|
/**
|
||||||
|
* Notifies the parent fragment of a change and navigates back.
|
||||||
|
*/
|
||||||
private void notifyParentAndGoBack() {
|
private void notifyParentAndGoBack() {
|
||||||
if (inventoryFragment != null)
|
if (inventoryFragment != null)
|
||||||
inventoryFragment.onInventoryChanged();
|
inventoryFragment.onInventoryChanged();
|
||||||
navigateBack();
|
navigateBack();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Navigates back to the previous fragment.
|
||||||
|
*/
|
||||||
private void navigateBack() {
|
private void navigateBack() {
|
||||||
NavHostFragment.findNavController(this).popBackStack();
|
NavHostFragment.findNavController(this).popBackStack();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Enables or disables action buttons.
|
||||||
|
*/
|
||||||
private void setButtonsEnabled(boolean enabled) {
|
private void setButtonsEnabled(boolean enabled) {
|
||||||
btnSave.setEnabled(enabled);
|
btnSave.setEnabled(enabled);
|
||||||
btnDelete.setEnabled(enabled);
|
btnDelete.setEnabled(enabled);
|
||||||
|
|||||||
@@ -3,9 +3,7 @@ package com.example.petstoremobile.fragments.listfragments.detailfragments;
|
|||||||
import android.os.Bundle;
|
import android.os.Bundle;
|
||||||
|
|
||||||
import androidx.annotation.NonNull;
|
import androidx.annotation.NonNull;
|
||||||
import androidx.annotation.Nullable;
|
|
||||||
import androidx.appcompat.app.AlertDialog;
|
import androidx.appcompat.app.AlertDialog;
|
||||||
import androidx.core.content.ContextCompat;
|
|
||||||
import androidx.fragment.app.Fragment;
|
import androidx.fragment.app.Fragment;
|
||||||
import androidx.navigation.fragment.NavHostFragment;
|
import androidx.navigation.fragment.NavHostFragment;
|
||||||
|
|
||||||
@@ -13,7 +11,6 @@ import android.util.Log;
|
|||||||
import android.view.LayoutInflater;
|
import android.view.LayoutInflater;
|
||||||
import android.view.View;
|
import android.view.View;
|
||||||
import android.view.ViewGroup;
|
import android.view.ViewGroup;
|
||||||
import android.widget.ArrayAdapter;
|
|
||||||
import android.widget.Button;
|
import android.widget.Button;
|
||||||
import android.widget.EditText;
|
import android.widget.EditText;
|
||||||
import android.widget.Spinner;
|
import android.widget.Spinner;
|
||||||
@@ -24,9 +21,8 @@ import com.example.petstoremobile.R;
|
|||||||
import com.example.petstoremobile.adapters.BlackTextArrayAdapter;
|
import com.example.petstoremobile.adapters.BlackTextArrayAdapter;
|
||||||
import com.example.petstoremobile.api.PetApi;
|
import com.example.petstoremobile.api.PetApi;
|
||||||
import com.example.petstoremobile.dtos.PetDTO;
|
import com.example.petstoremobile.dtos.PetDTO;
|
||||||
import com.example.petstoremobile.fragments.ListFragment;
|
|
||||||
import com.example.petstoremobile.fragments.listfragments.PetFragment;
|
|
||||||
import com.example.petstoremobile.utils.ActivityLogger;
|
import com.example.petstoremobile.utils.ActivityLogger;
|
||||||
|
import com.example.petstoremobile.utils.ErrorUtils;
|
||||||
import com.example.petstoremobile.utils.InputValidator;
|
import com.example.petstoremobile.utils.InputValidator;
|
||||||
|
|
||||||
import javax.inject.Inject;
|
import javax.inject.Inject;
|
||||||
@@ -36,6 +32,9 @@ import retrofit2.Call;
|
|||||||
import retrofit2.Callback;
|
import retrofit2.Callback;
|
||||||
import retrofit2.Response;
|
import retrofit2.Response;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Fragment for displaying and editing pet details.
|
||||||
|
*/
|
||||||
@AndroidEntryPoint
|
@AndroidEntryPoint
|
||||||
public class PetDetailFragment extends Fragment {
|
public class PetDetailFragment extends Fragment {
|
||||||
|
|
||||||
@@ -66,7 +65,9 @@ public class PetDetailFragment extends Fragment {
|
|||||||
return view;
|
return view;
|
||||||
}
|
}
|
||||||
|
|
||||||
//Method to Update or Add a pet
|
/**
|
||||||
|
* Handles the saving of pet data (adding/updating).
|
||||||
|
*/
|
||||||
private void savePet() {
|
private void savePet() {
|
||||||
// Validates all fields using InputValidator
|
// Validates all fields using InputValidator
|
||||||
if (!InputValidator.isNotEmpty(etPetName, "Pet Name")) return;
|
if (!InputValidator.isNotEmpty(etPetName, "Pet Name")) return;
|
||||||
@@ -104,7 +105,7 @@ public class PetDetailFragment extends Fragment {
|
|||||||
Toast.makeText(getContext(), "Pet updated successfully!", Toast.LENGTH_SHORT).show();
|
Toast.makeText(getContext(), "Pet updated successfully!", Toast.LENGTH_SHORT).show();
|
||||||
navigateBack();
|
navigateBack();
|
||||||
} else {
|
} else {
|
||||||
Toast.makeText(getContext(), "Failed to update pet: " + response.code(), Toast.LENGTH_SHORT).show();
|
ErrorUtils.showErrorMessage(getContext(), response, "Failed to update pet");
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -112,7 +113,7 @@ public class PetDetailFragment extends Fragment {
|
|||||||
public void onFailure(Call<PetDTO> call, Throwable t) {
|
public void onFailure(Call<PetDTO> call, Throwable t) {
|
||||||
ActivityLogger.logException(requireContext(), "PetDetailFragment.updatePet", new Exception(t));
|
ActivityLogger.logException(requireContext(), "PetDetailFragment.updatePet", new Exception(t));
|
||||||
Log.e("PetDetailFragment", "Error updating pet", t);
|
Log.e("PetDetailFragment", "Error updating pet", t);
|
||||||
Toast.makeText(getContext(), "Error: " + t.getMessage(), Toast.LENGTH_SHORT).show();
|
Toast.makeText(getContext(), "Network error: " + t.getMessage(), Toast.LENGTH_SHORT).show();
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
} else {
|
} else {
|
||||||
@@ -125,7 +126,7 @@ public class PetDetailFragment extends Fragment {
|
|||||||
Toast.makeText(getContext(), "Pet added successfully!", Toast.LENGTH_SHORT).show();
|
Toast.makeText(getContext(), "Pet added successfully!", Toast.LENGTH_SHORT).show();
|
||||||
navigateBack();
|
navigateBack();
|
||||||
} else {
|
} else {
|
||||||
Toast.makeText(getContext(), "Failed to add pet: " + response.code(), Toast.LENGTH_SHORT).show();
|
ErrorUtils.showErrorMessage(getContext(), response, "Failed to add pet");
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -133,13 +134,15 @@ public class PetDetailFragment extends Fragment {
|
|||||||
public void onFailure(Call<PetDTO> call, Throwable t) {
|
public void onFailure(Call<PetDTO> call, Throwable t) {
|
||||||
ActivityLogger.logException(requireContext(), "PetDetailFragment.createPet", new Exception(t));
|
ActivityLogger.logException(requireContext(), "PetDetailFragment.createPet", new Exception(t));
|
||||||
Log.e("PetDetailFragment", "Error adding pet", t);
|
Log.e("PetDetailFragment", "Error adding pet", t);
|
||||||
Toast.makeText(getContext(), "Error: " + t.getMessage(), Toast.LENGTH_SHORT).show();
|
Toast.makeText(getContext(), "Network error: " + t.getMessage(), Toast.LENGTH_SHORT).show();
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
//Method to Delete a pet
|
/**
|
||||||
|
* Displays a confirmation dialog and handles the deletion of a pet.
|
||||||
|
*/
|
||||||
private void deletePet() {
|
private void deletePet() {
|
||||||
//Alert the user to confirm the delete
|
//Alert the user to confirm the delete
|
||||||
new AlertDialog.Builder(requireContext())
|
new AlertDialog.Builder(requireContext())
|
||||||
@@ -155,7 +158,7 @@ public class PetDetailFragment extends Fragment {
|
|||||||
Toast.makeText(getContext(), "Pet deleted successfully!", Toast.LENGTH_SHORT).show();
|
Toast.makeText(getContext(), "Pet deleted successfully!", Toast.LENGTH_SHORT).show();
|
||||||
navigateBack();
|
navigateBack();
|
||||||
} else {
|
} else {
|
||||||
Toast.makeText(getContext(), "Failed to delete pet: " + response.code(), Toast.LENGTH_SHORT).show();
|
ErrorUtils.showErrorMessage(getContext(), response, "Failed to delete pet");
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -163,7 +166,7 @@ public class PetDetailFragment extends Fragment {
|
|||||||
public void onFailure(Call<Void> call, Throwable t) {
|
public void onFailure(Call<Void> call, Throwable t) {
|
||||||
ActivityLogger.logException(requireContext(), "PetDetailFragment.deletePet", new Exception(t));
|
ActivityLogger.logException(requireContext(), "PetDetailFragment.deletePet", new Exception(t));
|
||||||
Log.e("PetDetailFragment", "Error deleting pet", t);
|
Log.e("PetDetailFragment", "Error deleting pet", t);
|
||||||
Toast.makeText(getContext(), "Error: " + t.getMessage(), Toast.LENGTH_SHORT).show();
|
Toast.makeText(getContext(), "Network error: " + t.getMessage(), Toast.LENGTH_SHORT).show();
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
})
|
})
|
||||||
@@ -171,12 +174,16 @@ public class PetDetailFragment extends Fragment {
|
|||||||
.show();
|
.show();
|
||||||
}
|
}
|
||||||
|
|
||||||
//Helper method to navigate back to the list
|
/**
|
||||||
|
* Navigates back to the previous screen.
|
||||||
|
*/
|
||||||
private void navigateBack() {
|
private void navigateBack() {
|
||||||
NavHostFragment.findNavController(this).popBackStack();
|
NavHostFragment.findNavController(this).popBackStack();
|
||||||
}
|
}
|
||||||
|
|
||||||
//helper function to check if pet is being edited or added and show the view accordingly
|
/**
|
||||||
|
* Handles arguments passed to the fragment to determine if it's in edit or add mode.
|
||||||
|
*/
|
||||||
private void handleArguments() {
|
private void handleArguments() {
|
||||||
// Pet is being edited if the bundle contains a petId
|
// Pet is being edited if the bundle contains a petId
|
||||||
if (getArguments() != null && getArguments().containsKey("petId")) {
|
if (getArguments() != null && getArguments().containsKey("petId")) {
|
||||||
@@ -208,7 +215,9 @@ public class PetDetailFragment extends Fragment {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
//helper function to get controls from layout
|
/**
|
||||||
|
* Binds UI components from the layout.
|
||||||
|
*/
|
||||||
private void initViews(View view) {
|
private void initViews(View view) {
|
||||||
tvMode = view.findViewById(R.id.tvMode);
|
tvMode = view.findViewById(R.id.tvMode);
|
||||||
tvPetId = view.findViewById(R.id.tvPetId);
|
tvPetId = view.findViewById(R.id.tvPetId);
|
||||||
@@ -223,7 +232,9 @@ public class PetDetailFragment extends Fragment {
|
|||||||
btnBack = view.findViewById(R.id.btnBack);
|
btnBack = view.findViewById(R.id.btnBack);
|
||||||
}
|
}
|
||||||
|
|
||||||
//helper function to set up the spinner menu for pet status
|
/**
|
||||||
|
* Initializes the spinner for pet status selection.
|
||||||
|
*/
|
||||||
private void setupSpinner() {
|
private void setupSpinner() {
|
||||||
BlackTextArrayAdapter<String> adapter = new BlackTextArrayAdapter<>(requireContext(),
|
BlackTextArrayAdapter<String> adapter = new BlackTextArrayAdapter<>(requireContext(),
|
||||||
android.R.layout.simple_spinner_item,
|
android.R.layout.simple_spinner_item,
|
||||||
|
|||||||
@@ -1,38 +1,25 @@
|
|||||||
package com.example.petstoremobile.fragments.listfragments.detailfragments;
|
package com.example.petstoremobile.fragments.listfragments.detailfragments;
|
||||||
|
|
||||||
import android.Manifest;
|
|
||||||
import android.app.Activity;
|
|
||||||
import android.content.Intent;
|
|
||||||
import android.content.pm.PackageManager;
|
|
||||||
import android.net.Uri;
|
import android.net.Uri;
|
||||||
import android.os.Bundle;
|
import android.os.Bundle;
|
||||||
import android.provider.MediaStore;
|
|
||||||
import android.util.Log;
|
|
||||||
import android.view.*;
|
import android.view.*;
|
||||||
import android.widget.*;
|
import android.widget.*;
|
||||||
import androidx.activity.result.ActivityResultLauncher;
|
|
||||||
import androidx.activity.result.contract.ActivityResultContracts;
|
|
||||||
import androidx.annotation.NonNull;
|
import androidx.annotation.NonNull;
|
||||||
import androidx.annotation.Nullable;
|
|
||||||
import androidx.appcompat.app.AlertDialog;
|
|
||||||
import androidx.core.content.ContextCompat;
|
|
||||||
import androidx.core.content.FileProvider;
|
|
||||||
import androidx.fragment.app.Fragment;
|
import androidx.fragment.app.Fragment;
|
||||||
import androidx.lifecycle.ViewModelProvider;
|
import androidx.lifecycle.ViewModelProvider;
|
||||||
import androidx.navigation.fragment.NavHostFragment;
|
import androidx.navigation.fragment.NavHostFragment;
|
||||||
|
|
||||||
import com.bumptech.glide.Glide;
|
import com.bumptech.glide.Glide;
|
||||||
import com.bumptech.glide.load.engine.DiskCacheStrategy;
|
|
||||||
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.BlackTextArrayAdapter;
|
import com.example.petstoremobile.adapters.BlackTextArrayAdapter;
|
||||||
import com.example.petstoremobile.api.*;
|
import com.example.petstoremobile.api.*;
|
||||||
import com.example.petstoremobile.api.auth.TokenManager;
|
import com.example.petstoremobile.api.auth.TokenManager;
|
||||||
import com.example.petstoremobile.dtos.*;
|
import com.example.petstoremobile.dtos.*;
|
||||||
import com.example.petstoremobile.viewmodels.ProductViewModel;
|
import com.example.petstoremobile.viewmodels.ProductViewModel;
|
||||||
import com.example.petstoremobile.utils.Resource;
|
import com.example.petstoremobile.utils.ErrorUtils;
|
||||||
import com.example.petstoremobile.utils.FileUtils;
|
import com.example.petstoremobile.utils.FileUtils;
|
||||||
|
import com.example.petstoremobile.utils.GlideUtils;
|
||||||
|
import com.example.petstoremobile.utils.ImagePickerHelper;
|
||||||
|
|
||||||
import java.io.File;
|
import java.io.File;
|
||||||
import java.math.BigDecimal;
|
import java.math.BigDecimal;
|
||||||
@@ -46,6 +33,9 @@ import okhttp3.MediaType;
|
|||||||
import okhttp3.MultipartBody;
|
import okhttp3.MultipartBody;
|
||||||
import okhttp3.RequestBody;
|
import okhttp3.RequestBody;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Fragment for displaying and editing product details, including image selection.
|
||||||
|
*/
|
||||||
@AndroidEntryPoint
|
@AndroidEntryPoint
|
||||||
public class ProductDetailFragment extends Fragment {
|
public class ProductDetailFragment extends Fragment {
|
||||||
|
|
||||||
@@ -65,52 +55,43 @@ public class ProductDetailFragment extends Fragment {
|
|||||||
private List<CategoryDTO> categoryList = new ArrayList<>();
|
private List<CategoryDTO> categoryList = new ArrayList<>();
|
||||||
private Uri photoUri;
|
private Uri photoUri;
|
||||||
private ProductViewModel viewModel;
|
private ProductViewModel viewModel;
|
||||||
|
private ImagePickerHelper imagePickerHelper;
|
||||||
|
|
||||||
@Inject @Named("baseUrl") String baseUrl;
|
@Inject @Named("baseUrl") String baseUrl;
|
||||||
@Inject TokenManager tokenManager;
|
@Inject TokenManager tokenManager;
|
||||||
|
|
||||||
private ActivityResultLauncher<Intent> galleryLauncher;
|
/**
|
||||||
private ActivityResultLauncher<Uri> cameraLauncher;
|
* Initializes activity launchers and the ImagePickerHelper.
|
||||||
private ActivityResultLauncher<String> permissionLauncher;
|
*/
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public void onCreate(Bundle savedInstanceState) {
|
public void onCreate(Bundle savedInstanceState) {
|
||||||
super.onCreate(savedInstanceState);
|
super.onCreate(savedInstanceState);
|
||||||
viewModel = new ViewModelProvider(this).get(ProductViewModel.class);
|
viewModel = new ViewModelProvider(this).get(ProductViewModel.class);
|
||||||
|
|
||||||
galleryLauncher = registerForActivityResult(
|
imagePickerHelper = new ImagePickerHelper(this, "product_photo.jpg", new ImagePickerHelper.ImagePickerListener() {
|
||||||
new ActivityResultContracts.StartActivityForResult(),
|
@Override
|
||||||
result -> {
|
public void onImagePicked(Uri uri) {
|
||||||
if (result.getResultCode() == Activity.RESULT_OK && result.getData() != null) {
|
photoUri = uri;
|
||||||
Uri selectedImage = result.getData().getData();
|
Glide.with(ProductDetailFragment.this).load(uri).into(ivProductImage);
|
||||||
Glide.with(this).load(selectedImage).into(ivProductImage);
|
|
||||||
photoUri = selectedImage;
|
|
||||||
hasImage = true;
|
hasImage = true;
|
||||||
isImageChanged = true;
|
isImageChanged = true;
|
||||||
isImageRemoved = false;
|
isImageRemoved = false;
|
||||||
}
|
}
|
||||||
}
|
|
||||||
);
|
|
||||||
cameraLauncher = registerForActivityResult(
|
|
||||||
new ActivityResultContracts.TakePicture(),
|
|
||||||
success -> {
|
|
||||||
if (success) {
|
|
||||||
Glide.with(this).load(photoUri).into(ivProductImage);
|
|
||||||
hasImage = true;
|
|
||||||
isImageChanged = true;
|
|
||||||
isImageRemoved = false;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
);
|
|
||||||
permissionLauncher = registerForActivityResult(
|
|
||||||
new ActivityResultContracts.RequestPermission(),
|
|
||||||
granted -> {
|
|
||||||
if (granted) launchCamera();
|
|
||||||
else Toast.makeText(getContext(), "Camera permission denied", Toast.LENGTH_SHORT).show();
|
|
||||||
}
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void onImageRemoved() {
|
||||||
|
photoUri = null;
|
||||||
|
hasImage = false;
|
||||||
|
isImageChanged = false;
|
||||||
|
isImageRemoved = true;
|
||||||
|
ivProductImage.setImageResource(R.drawable.placeholder2);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Inflates the layout and initializes UI components and listeners.
|
||||||
|
*/
|
||||||
@Override
|
@Override
|
||||||
public View onCreateView(@NonNull LayoutInflater inflater, ViewGroup container,
|
public View onCreateView(@NonNull LayoutInflater inflater, ViewGroup container,
|
||||||
Bundle savedInstanceState) {
|
Bundle savedInstanceState) {
|
||||||
@@ -122,10 +103,13 @@ public class ProductDetailFragment extends Fragment {
|
|||||||
btnBack.setOnClickListener(v -> navigateBack());
|
btnBack.setOnClickListener(v -> navigateBack());
|
||||||
btnSave.setOnClickListener(v -> saveProduct());
|
btnSave.setOnClickListener(v -> saveProduct());
|
||||||
btnDelete.setOnClickListener(v -> confirmDelete());
|
btnDelete.setOnClickListener(v -> confirmDelete());
|
||||||
ivProductImage.setOnClickListener(v -> showImagePickerDialog());
|
ivProductImage.setOnClickListener(v -> imagePickerHelper.showImagePickerDialog("Select Product Image", hasImage));
|
||||||
return view;
|
return view;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* get the UI components from the layout.
|
||||||
|
*/
|
||||||
private void initViews(View v) {
|
private void initViews(View v) {
|
||||||
tvMode = v.findViewById(R.id.tvProductMode);
|
tvMode = v.findViewById(R.id.tvProductMode);
|
||||||
tvProductId = v.findViewById(R.id.tvProductId);
|
tvProductId = v.findViewById(R.id.tvProductId);
|
||||||
@@ -139,63 +123,21 @@ public class ProductDetailFragment extends Fragment {
|
|||||||
ivProductImage = v.findViewById(R.id.ivProductImage);
|
ivProductImage = v.findViewById(R.id.ivProductImage);
|
||||||
}
|
}
|
||||||
|
|
||||||
// Helper function to show the image picker dialog
|
/**
|
||||||
private void showImagePickerDialog() {
|
* Fetches all product categories for the selection spinner.
|
||||||
List<String> options = new ArrayList<>();
|
*/
|
||||||
options.add("Take Photo");
|
|
||||||
options.add("Choose from Gallery");
|
|
||||||
if (hasImage) {
|
|
||||||
options.add("Remove Photo");
|
|
||||||
}
|
|
||||||
|
|
||||||
new AlertDialog.Builder(requireContext())
|
|
||||||
.setTitle("Select Product Image")
|
|
||||||
.setItems(options.toArray(new String[0]), (dialog, which) -> {
|
|
||||||
String selectedOption = options.get(which);
|
|
||||||
if (selectedOption.equals("Take Photo")) {
|
|
||||||
if (ContextCompat.checkSelfPermission(requireContext(), Manifest.permission.CAMERA)
|
|
||||||
== PackageManager.PERMISSION_GRANTED) {
|
|
||||||
launchCamera();
|
|
||||||
} else {
|
|
||||||
permissionLauncher.launch(Manifest.permission.CAMERA);
|
|
||||||
}
|
|
||||||
} else if (selectedOption.equals("Choose from Gallery")) {
|
|
||||||
Intent intent = new Intent(Intent.ACTION_PICK, MediaStore.Images.Media.EXTERNAL_CONTENT_URI);
|
|
||||||
galleryLauncher.launch(intent);
|
|
||||||
} else if (selectedOption.equals("Remove Photo")) {
|
|
||||||
removePhoto();
|
|
||||||
}
|
|
||||||
})
|
|
||||||
.show();
|
|
||||||
}
|
|
||||||
|
|
||||||
// Helper function to remove the photo locally
|
|
||||||
private void removePhoto() {
|
|
||||||
photoUri = null;
|
|
||||||
hasImage = false;
|
|
||||||
isImageChanged = false;
|
|
||||||
isImageRemoved = true;
|
|
||||||
Glide.with(this).load(R.drawable.placeholder2).into(ivProductImage);
|
|
||||||
}
|
|
||||||
|
|
||||||
// Helper function to launch the camera
|
|
||||||
private void launchCamera() {
|
|
||||||
File photoFile = new File(requireContext().getCacheDir(), "product_photo.jpg");
|
|
||||||
photoUri = FileProvider.getUriForFile(requireContext(), requireContext().getPackageName() + ".fileprovider", photoFile);
|
|
||||||
cameraLauncher.launch(photoUri);
|
|
||||||
}
|
|
||||||
|
|
||||||
// Helper function to load categories from the backend for the spinner
|
|
||||||
private void loadCategories() {
|
private void loadCategories() {
|
||||||
viewModel.getAllCategories(0, 100).observe(getViewLifecycleOwner(), resource -> {
|
viewModel.getAllCategories(0, 100).observe(getViewLifecycleOwner(), resource -> {
|
||||||
if (resource != null && resource.status == Resource.Status.SUCCESS && resource.data != null) {
|
if (resource != null && resource.status == com.example.petstoremobile.utils.Resource.Status.SUCCESS && resource.data != null) {
|
||||||
categoryList = resource.data.getContent();
|
categoryList = resource.data.getContent();
|
||||||
populateCategorySpinner();
|
populateCategorySpinner();
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
// Helper function to populate the category spinner
|
/**
|
||||||
|
* Fills the spinner with category names.
|
||||||
|
*/
|
||||||
private void populateCategorySpinner() {
|
private void populateCategorySpinner() {
|
||||||
List<String> names = new ArrayList<>();
|
List<String> names = new ArrayList<>();
|
||||||
names.add("-- Select Category --");
|
names.add("-- Select Category --");
|
||||||
@@ -211,6 +153,9 @@ public class ProductDetailFragment extends Fragment {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Checks if the fragment was opened with existing product data for editing.
|
||||||
|
*/
|
||||||
private void handleArguments() {
|
private void handleArguments() {
|
||||||
Bundle a = getArguments();
|
Bundle a = getArguments();
|
||||||
if (a != null && a.containsKey("prodId")) {
|
if (a != null && a.containsKey("prodId")) {
|
||||||
@@ -235,34 +180,34 @@ public class ProductDetailFragment extends Fragment {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
//load the product image from the backend
|
/**
|
||||||
|
* Loads the product image from the backend.
|
||||||
|
*/
|
||||||
private void loadProductImage() {
|
private void loadProductImage() {
|
||||||
String imageUrl = baseUrl + String.format(Locale.US, ProductApi.PRODUCT_IMAGE_PATH, prodId);
|
String imageUrl = baseUrl + String.format(Locale.US, ProductApi.PRODUCT_IMAGE_PATH, prodId);
|
||||||
String token = tokenManager.getToken();
|
String token = tokenManager.getToken();
|
||||||
|
|
||||||
Object loadTarget = imageUrl;
|
GlideUtils.loadImageWithToken(requireContext(), ivProductImage, imageUrl, token, R.drawable.placeholder2, new GlideUtils.ImageLoadListener() {
|
||||||
if (token != null) {
|
@Override
|
||||||
loadTarget = new GlideUrl(imageUrl, new LazyHeaders.Builder()
|
public void onResourceReady() {
|
||||||
.addHeader("Authorization", "Bearer " + token)
|
hasImage = true;
|
||||||
.build());
|
|
||||||
}
|
}
|
||||||
|
|
||||||
Glide.with(this)
|
@Override
|
||||||
.load(loadTarget)
|
public void onLoadFailed() {
|
||||||
.skipMemoryCache(true)
|
hasImage = false;
|
||||||
.diskCacheStrategy(DiskCacheStrategy.NONE)
|
}
|
||||||
.placeholder(R.drawable.placeholder2)
|
});
|
||||||
.error(R.drawable.placeholder2)
|
|
||||||
.into(ivProductImage);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// Function to check any changes to the image and perform the appropriate action
|
/**
|
||||||
// updating/adding photo, removing photo or no change
|
* Performs image related actions (upload/delete) after product details are saved.
|
||||||
|
*/
|
||||||
private void performPendingImageActions(String successMsg) {
|
private void performPendingImageActions(String successMsg) {
|
||||||
if (isImageRemoved) {
|
if (isImageRemoved) {
|
||||||
viewModel.deleteProductImage(prodId).observe(getViewLifecycleOwner(), resource -> {
|
viewModel.deleteProductImage(prodId).observe(getViewLifecycleOwner(), resource -> {
|
||||||
if (resource != null && resource.status != Resource.Status.LOADING) {
|
if (resource != null && resource.status != com.example.petstoremobile.utils.Resource.Status.LOADING) {
|
||||||
if (resource.status == Resource.Status.SUCCESS) {
|
if (resource.status == com.example.petstoremobile.utils.Resource.Status.SUCCESS) {
|
||||||
Toast.makeText(getContext(), successMsg, Toast.LENGTH_SHORT).show();
|
Toast.makeText(getContext(), successMsg, Toast.LENGTH_SHORT).show();
|
||||||
} else {
|
} else {
|
||||||
Toast.makeText(getContext(), successMsg + " (but image removal failed)", Toast.LENGTH_SHORT).show();
|
Toast.makeText(getContext(), successMsg + " (but image removal failed)", Toast.LENGTH_SHORT).show();
|
||||||
@@ -278,8 +223,9 @@ public class ProductDetailFragment extends Fragment {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// Helper function to upload the product image by calling the backend
|
/**
|
||||||
// and then navigate back to the previous screen
|
* Uploads the selected image file to the server.
|
||||||
|
*/
|
||||||
private void uploadProductImageAndNavigate(Uri uri, String successMsg) {
|
private void uploadProductImageAndNavigate(Uri uri, String successMsg) {
|
||||||
File file = FileUtils.getFileFromUri(requireContext(), uri);
|
File file = FileUtils.getFileFromUri(requireContext(), uri);
|
||||||
if (file == null) {
|
if (file == null) {
|
||||||
@@ -292,8 +238,8 @@ public class ProductDetailFragment extends Fragment {
|
|||||||
MultipartBody.Part body = MultipartBody.Part.createFormData("image", file.getName(), requestFile);
|
MultipartBody.Part body = MultipartBody.Part.createFormData("image", file.getName(), requestFile);
|
||||||
|
|
||||||
viewModel.uploadProductImage(prodId, body).observe(getViewLifecycleOwner(), resource -> {
|
viewModel.uploadProductImage(prodId, body).observe(getViewLifecycleOwner(), resource -> {
|
||||||
if (resource != null && resource.status != Resource.Status.LOADING) {
|
if (resource != null && resource.status != com.example.petstoremobile.utils.Resource.Status.LOADING) {
|
||||||
if (resource.status == Resource.Status.SUCCESS) {
|
if (resource.status == com.example.petstoremobile.utils.Resource.Status.SUCCESS) {
|
||||||
Toast.makeText(getContext(), successMsg, Toast.LENGTH_SHORT).show();
|
Toast.makeText(getContext(), successMsg, Toast.LENGTH_SHORT).show();
|
||||||
} else {
|
} else {
|
||||||
Toast.makeText(getContext(), successMsg + " (but image upload failed)", Toast.LENGTH_SHORT).show();
|
Toast.makeText(getContext(), successMsg + " (but image upload failed)", Toast.LENGTH_SHORT).show();
|
||||||
@@ -303,6 +249,9 @@ public class ProductDetailFragment extends Fragment {
|
|||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Validates input fields and saves product information to the backend.
|
||||||
|
*/
|
||||||
private void saveProduct() {
|
private void saveProduct() {
|
||||||
String name = etProductName.getText().toString().trim();
|
String name = etProductName.getText().toString().trim();
|
||||||
String desc = etProductDesc.getText().toString().trim();
|
String desc = etProductDesc.getText().toString().trim();
|
||||||
@@ -330,8 +279,8 @@ public class ProductDetailFragment extends Fragment {
|
|||||||
|
|
||||||
if (isEditing) {
|
if (isEditing) {
|
||||||
viewModel.updateProduct(prodId, dto).observe(getViewLifecycleOwner(), resource -> {
|
viewModel.updateProduct(prodId, dto).observe(getViewLifecycleOwner(), resource -> {
|
||||||
if (resource != null && resource.status != Resource.Status.LOADING) {
|
if (resource != null && resource.status != com.example.petstoremobile.utils.Resource.Status.LOADING) {
|
||||||
if (resource.status == Resource.Status.SUCCESS) {
|
if (resource.status == com.example.petstoremobile.utils.Resource.Status.SUCCESS) {
|
||||||
performPendingImageActions("Updated");
|
performPendingImageActions("Updated");
|
||||||
} else {
|
} else {
|
||||||
Toast.makeText(getContext(), "Error: " + resource.message, Toast.LENGTH_SHORT).show();
|
Toast.makeText(getContext(), "Error: " + resource.message, Toast.LENGTH_SHORT).show();
|
||||||
@@ -340,8 +289,8 @@ public class ProductDetailFragment extends Fragment {
|
|||||||
});
|
});
|
||||||
} else {
|
} else {
|
||||||
viewModel.createProduct(dto).observe(getViewLifecycleOwner(), resource -> {
|
viewModel.createProduct(dto).observe(getViewLifecycleOwner(), resource -> {
|
||||||
if (resource != null && resource.status != Resource.Status.LOADING) {
|
if (resource != null && resource.status != com.example.petstoremobile.utils.Resource.Status.LOADING) {
|
||||||
if (resource.status == Resource.Status.SUCCESS && resource.data != null) {
|
if (resource.status == com.example.petstoremobile.utils.Resource.Status.SUCCESS && resource.data != null) {
|
||||||
prodId = resource.data.getProdId();
|
prodId = resource.data.getProdId();
|
||||||
performPendingImageActions("Saved");
|
performPendingImageActions("Saved");
|
||||||
} else {
|
} else {
|
||||||
@@ -352,21 +301,26 @@ public class ProductDetailFragment extends Fragment {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// Function to delete the product from the server
|
/**
|
||||||
|
* Displays a confirmation dialog before deleting the product.
|
||||||
|
*/
|
||||||
private void confirmDelete() {
|
private void confirmDelete() {
|
||||||
new AlertDialog.Builder(requireContext())
|
new androidx.appcompat.app.AlertDialog.Builder(requireContext())
|
||||||
.setTitle("Delete Product?")
|
.setTitle("Delete Product?")
|
||||||
.setPositiveButton("Yes", (d, w) ->
|
.setPositiveButton("Yes", (d, w) ->
|
||||||
viewModel.deleteProduct(prodId).observe(getViewLifecycleOwner(), resource -> {
|
viewModel.deleteProduct(prodId).observe(getViewLifecycleOwner(), resource -> {
|
||||||
if (resource != null && resource.status == Resource.Status.SUCCESS) {
|
if (resource != null && resource.status == com.example.petstoremobile.utils.Resource.Status.SUCCESS) {
|
||||||
navigateBack();
|
navigateBack();
|
||||||
} else if (resource != null && resource.status == Resource.Status.ERROR) {
|
} else if (resource != null && resource.status == com.example.petstoremobile.utils.Resource.Status.ERROR) {
|
||||||
Toast.makeText(getContext(), "Delete failed: " + resource.message, Toast.LENGTH_SHORT).show();
|
Toast.makeText(getContext(), "Delete failed: " + resource.message, Toast.LENGTH_SHORT).show();
|
||||||
}
|
}
|
||||||
}))
|
}))
|
||||||
.setNegativeButton("No", null).show();
|
.setNegativeButton("No", null).show();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Navigates back to the previous fragment.
|
||||||
|
*/
|
||||||
private void navigateBack() {
|
private void navigateBack() {
|
||||||
NavHostFragment.findNavController(this).popBackStack();
|
NavHostFragment.findNavController(this).popBackStack();
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -13,7 +13,8 @@ import com.example.petstoremobile.R;
|
|||||||
import com.example.petstoremobile.adapters.BlackTextArrayAdapter;
|
import com.example.petstoremobile.adapters.BlackTextArrayAdapter;
|
||||||
import com.example.petstoremobile.api.*;
|
import com.example.petstoremobile.api.*;
|
||||||
import com.example.petstoremobile.dtos.*;
|
import com.example.petstoremobile.dtos.*;
|
||||||
import com.example.petstoremobile.fragments.ListFragment;
|
import com.example.petstoremobile.utils.ErrorUtils;
|
||||||
|
|
||||||
import java.math.BigDecimal;
|
import java.math.BigDecimal;
|
||||||
import java.util.*;
|
import java.util.*;
|
||||||
|
|
||||||
@@ -22,6 +23,9 @@ import javax.inject.Inject;
|
|||||||
import dagger.hilt.android.AndroidEntryPoint;
|
import dagger.hilt.android.AndroidEntryPoint;
|
||||||
import retrofit2.*;
|
import retrofit2.*;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Fragment for displaying and editing the relationship between products and suppliers,
|
||||||
|
*/
|
||||||
@AndroidEntryPoint
|
@AndroidEntryPoint
|
||||||
public class ProductSupplierDetailFragment extends Fragment {
|
public class ProductSupplierDetailFragment extends Fragment {
|
||||||
|
|
||||||
@@ -57,6 +61,9 @@ public class ProductSupplierDetailFragment extends Fragment {
|
|||||||
return view;
|
return view;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Initializes UI components from the layout.
|
||||||
|
*/
|
||||||
private void initViews(View v) {
|
private void initViews(View v) {
|
||||||
tvMode = v.findViewById(R.id.tvPSMode);
|
tvMode = v.findViewById(R.id.tvPSMode);
|
||||||
spinnerProduct = v.findViewById(R.id.spinnerPSProduct);
|
spinnerProduct = v.findViewById(R.id.spinnerPSProduct);
|
||||||
@@ -67,11 +74,17 @@ public class ProductSupplierDetailFragment extends Fragment {
|
|||||||
btnBack = v.findViewById(R.id.btnPSBack);
|
btnBack = v.findViewById(R.id.btnPSBack);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Fetches products and suppliers to populate the spinners.
|
||||||
|
*/
|
||||||
private void loadData() {
|
private void loadData() {
|
||||||
loadProducts();
|
loadProducts();
|
||||||
loadSuppliers();
|
loadSuppliers();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Loads the list of products from the API.
|
||||||
|
*/
|
||||||
private void loadProducts() {
|
private void loadProducts() {
|
||||||
productApi.getAllProducts(null, 0, 200)
|
productApi.getAllProducts(null, 0, 200)
|
||||||
.enqueue(new Callback<PageResponse<ProductDTO>>() {
|
.enqueue(new Callback<PageResponse<ProductDTO>>() {
|
||||||
@@ -88,6 +101,9 @@ public class ProductSupplierDetailFragment extends Fragment {
|
|||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Populates the product spinner.
|
||||||
|
*/
|
||||||
private void populateProductSpinner() {
|
private void populateProductSpinner() {
|
||||||
List<String> names = new ArrayList<>();
|
List<String> names = new ArrayList<>();
|
||||||
names.add("-- Select Product --");
|
names.add("-- Select Product --");
|
||||||
@@ -103,6 +119,9 @@ public class ProductSupplierDetailFragment extends Fragment {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Loads the list of suppliers from the API.
|
||||||
|
*/
|
||||||
private void loadSuppliers() {
|
private void loadSuppliers() {
|
||||||
supplierApi.getAllSuppliers(0, 200)
|
supplierApi.getAllSuppliers(0, 200)
|
||||||
.enqueue(new Callback<PageResponse<SupplierDTO>>() {
|
.enqueue(new Callback<PageResponse<SupplierDTO>>() {
|
||||||
@@ -119,6 +138,9 @@ public class ProductSupplierDetailFragment extends Fragment {
|
|||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Populates the supplier spinner.
|
||||||
|
*/
|
||||||
private void populateSupplierSpinner() {
|
private void populateSupplierSpinner() {
|
||||||
List<String> names = new ArrayList<>();
|
List<String> names = new ArrayList<>();
|
||||||
names.add("-- Select Supplier --");
|
names.add("-- Select Supplier --");
|
||||||
@@ -134,6 +156,9 @@ public class ProductSupplierDetailFragment extends Fragment {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Handles arguments to determine if the fragment is in edit or add mode.
|
||||||
|
*/
|
||||||
private void handleArguments() {
|
private void handleArguments() {
|
||||||
Bundle a = getArguments();
|
Bundle a = getArguments();
|
||||||
if (a != null && a.containsKey("productId")) {
|
if (a != null && a.containsKey("productId")) {
|
||||||
@@ -151,6 +176,9 @@ public class ProductSupplierDetailFragment extends Fragment {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Validates input and saves the product-supplier to the backend.
|
||||||
|
*/
|
||||||
private void save() {
|
private void save() {
|
||||||
if (spinnerProduct.getSelectedItemPosition() == 0) {
|
if (spinnerProduct.getSelectedItemPosition() == 0) {
|
||||||
Toast.makeText(getContext(), "Select a product", Toast.LENGTH_SHORT).show(); return;
|
Toast.makeText(getContext(), "Select a product", Toast.LENGTH_SHORT).show(); return;
|
||||||
@@ -183,6 +211,9 @@ public class ProductSupplierDetailFragment extends Fragment {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* callback for product-supplier save/update operations.
|
||||||
|
*/
|
||||||
private Callback<ProductSupplierDTO> simpleCallback(String msg) {
|
private Callback<ProductSupplierDTO> simpleCallback(String msg) {
|
||||||
return new Callback<>() {
|
return new Callback<>() {
|
||||||
public void onResponse(Call<ProductSupplierDTO> c, Response<ProductSupplierDTO> r) {
|
public void onResponse(Call<ProductSupplierDTO> c, Response<ProductSupplierDTO> r) {
|
||||||
@@ -190,13 +221,7 @@ public class ProductSupplierDetailFragment extends Fragment {
|
|||||||
Toast.makeText(getContext(), msg, Toast.LENGTH_SHORT).show();
|
Toast.makeText(getContext(), msg, Toast.LENGTH_SHORT).show();
|
||||||
navigateBack();
|
navigateBack();
|
||||||
} else {
|
} else {
|
||||||
try {
|
ErrorUtils.showErrorMessage(getContext(), r, "Error " + r.code());
|
||||||
String err = r.errorBody().string();
|
|
||||||
Log.e("PS_SAVE", "Error: " + err);
|
|
||||||
Toast.makeText(getContext(), "Error " + r.code(), Toast.LENGTH_SHORT).show();
|
|
||||||
} catch (Exception e) {
|
|
||||||
Log.e("PS_SAVE", "Failed to read error");
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
public void onFailure(Call<ProductSupplierDTO> c, Throwable t) {
|
public void onFailure(Call<ProductSupplierDTO> c, Throwable t) {
|
||||||
@@ -206,6 +231,9 @@ public class ProductSupplierDetailFragment extends Fragment {
|
|||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Shows a confirmation dialog before deleting a product-supplier relationship.
|
||||||
|
*/
|
||||||
private void confirmDelete() {
|
private void confirmDelete() {
|
||||||
new AlertDialog.Builder(requireContext())
|
new AlertDialog.Builder(requireContext())
|
||||||
.setTitle("Delete?")
|
.setTitle("Delete?")
|
||||||
@@ -223,6 +251,9 @@ public class ProductSupplierDetailFragment extends Fragment {
|
|||||||
.setNegativeButton("No", null).show();
|
.setNegativeButton("No", null).show();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Navigates back to the previous screen.
|
||||||
|
*/
|
||||||
private void navigateBack() {
|
private void navigateBack() {
|
||||||
NavHostFragment.findNavController(this).popBackStack();
|
NavHostFragment.findNavController(this).popBackStack();
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -9,16 +9,21 @@ import androidx.fragment.app.Fragment;
|
|||||||
import androidx.navigation.fragment.NavHostFragment;
|
import androidx.navigation.fragment.NavHostFragment;
|
||||||
|
|
||||||
import com.example.petstoremobile.R;
|
import com.example.petstoremobile.R;
|
||||||
import com.example.petstoremobile.fragments.ListFragment;
|
|
||||||
|
|
||||||
import dagger.hilt.android.AndroidEntryPoint;
|
import dagger.hilt.android.AndroidEntryPoint;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Fragment for displaying the information of a purchase order.
|
||||||
|
*/
|
||||||
@AndroidEntryPoint
|
@AndroidEntryPoint
|
||||||
public class PurchaseOrderDetailFragment extends Fragment {
|
public class PurchaseOrderDetailFragment extends Fragment {
|
||||||
|
|
||||||
private TextView tvId, tvSupplier, tvDate, tvStatus;
|
private TextView tvId, tvSupplier, tvDate, tvStatus;
|
||||||
private Button btnBack;
|
private Button btnBack;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Inflates the layout, initializes views, and populates order data from arguments.
|
||||||
|
*/
|
||||||
@Override
|
@Override
|
||||||
public View onCreateView(@NonNull LayoutInflater inflater, ViewGroup container,
|
public View onCreateView(@NonNull LayoutInflater inflater, ViewGroup container,
|
||||||
Bundle savedInstanceState) {
|
Bundle savedInstanceState) {
|
||||||
|
|||||||
@@ -20,6 +20,7 @@ import com.example.petstoremobile.api.ServiceApi;
|
|||||||
import com.example.petstoremobile.dtos.ServiceDTO;
|
import com.example.petstoremobile.dtos.ServiceDTO;
|
||||||
import com.example.petstoremobile.fragments.listfragments.ServiceFragment;
|
import com.example.petstoremobile.fragments.listfragments.ServiceFragment;
|
||||||
import com.example.petstoremobile.utils.ActivityLogger;
|
import com.example.petstoremobile.utils.ActivityLogger;
|
||||||
|
import com.example.petstoremobile.utils.ErrorUtils;
|
||||||
import com.example.petstoremobile.utils.InputValidator;
|
import com.example.petstoremobile.utils.InputValidator;
|
||||||
|
|
||||||
import javax.inject.Inject;
|
import javax.inject.Inject;
|
||||||
@@ -29,6 +30,9 @@ import retrofit2.Call;
|
|||||||
import retrofit2.Callback;
|
import retrofit2.Callback;
|
||||||
import retrofit2.Response;
|
import retrofit2.Response;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Fragment for displaying and editing service details.
|
||||||
|
*/
|
||||||
@AndroidEntryPoint
|
@AndroidEntryPoint
|
||||||
public class ServiceDetailFragment extends Fragment {
|
public class ServiceDetailFragment extends Fragment {
|
||||||
|
|
||||||
@@ -41,7 +45,9 @@ public class ServiceDetailFragment extends Fragment {
|
|||||||
|
|
||||||
@Inject ServiceApi serviceApi;
|
@Inject ServiceApi serviceApi;
|
||||||
|
|
||||||
//set the service fragment to the parent so we refer back to service view when save or delete is done
|
/**
|
||||||
|
* Sets the parent service fragment to notify of changes.
|
||||||
|
*/
|
||||||
public void setServiceFragment(ServiceFragment fragment) {
|
public void setServiceFragment(ServiceFragment fragment) {
|
||||||
this.serviceFragment = fragment;
|
this.serviceFragment = fragment;
|
||||||
}
|
}
|
||||||
@@ -63,7 +69,9 @@ public class ServiceDetailFragment extends Fragment {
|
|||||||
return view;
|
return view;
|
||||||
}
|
}
|
||||||
|
|
||||||
//Method to Update or Add a service
|
/**
|
||||||
|
* Handles the saving of service data (adding or updating).
|
||||||
|
*/
|
||||||
private void saveService() {
|
private void saveService() {
|
||||||
// Validates all fields using InputValidator
|
// Validates all fields using InputValidator
|
||||||
if (!InputValidator.isNotEmpty(etServiceName, "Service Name")) return;
|
if (!InputValidator.isNotEmpty(etServiceName, "Service Name")) return;
|
||||||
@@ -96,7 +104,7 @@ public class ServiceDetailFragment extends Fragment {
|
|||||||
Toast.makeText(getContext(), "Service updated successfully!", Toast.LENGTH_SHORT).show();
|
Toast.makeText(getContext(), "Service updated successfully!", Toast.LENGTH_SHORT).show();
|
||||||
navigateBack();
|
navigateBack();
|
||||||
} else {
|
} else {
|
||||||
Toast.makeText(getContext(), "Failed to update service: " + response.code(), Toast.LENGTH_SHORT).show();
|
ErrorUtils.showErrorMessage(getContext(), response, "Failed to update service");
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -104,7 +112,7 @@ public class ServiceDetailFragment extends Fragment {
|
|||||||
public void onFailure(Call<ServiceDTO> call, Throwable t) {
|
public void onFailure(Call<ServiceDTO> call, Throwable t) {
|
||||||
ActivityLogger.logException(requireContext(), "ServiceDetailFragment.updateService", new Exception(t));
|
ActivityLogger.logException(requireContext(), "ServiceDetailFragment.updateService", new Exception(t));
|
||||||
Log.e("ServiceDetailFragment", "Error updating service", t);
|
Log.e("ServiceDetailFragment", "Error updating service", t);
|
||||||
Toast.makeText(getContext(), "Error: " + t.getMessage(), Toast.LENGTH_SHORT).show();
|
Toast.makeText(getContext(), "Network error: " + t.getMessage(), Toast.LENGTH_SHORT).show();
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
} else {
|
} else {
|
||||||
@@ -117,7 +125,7 @@ public class ServiceDetailFragment extends Fragment {
|
|||||||
Toast.makeText(getContext(), "Service added successfully!", Toast.LENGTH_SHORT).show();
|
Toast.makeText(getContext(), "Service added successfully!", Toast.LENGTH_SHORT).show();
|
||||||
navigateBack();
|
navigateBack();
|
||||||
} else {
|
} else {
|
||||||
Toast.makeText(getContext(), "Failed to add service: " + response.code(), Toast.LENGTH_SHORT).show();
|
ErrorUtils.showErrorMessage(getContext(), response, "Failed to add service");
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -125,13 +133,15 @@ public class ServiceDetailFragment extends Fragment {
|
|||||||
public void onFailure(Call<ServiceDTO> call, Throwable t) {
|
public void onFailure(Call<ServiceDTO> call, Throwable t) {
|
||||||
ActivityLogger.logException(requireContext(), "ServiceDetailFragment.createService", new Exception(t));
|
ActivityLogger.logException(requireContext(), "ServiceDetailFragment.createService", new Exception(t));
|
||||||
Log.e("ServiceDetailFragment", "Error adding service", t);
|
Log.e("ServiceDetailFragment", "Error adding service", t);
|
||||||
Toast.makeText(getContext(), "Error: " + t.getMessage(), Toast.LENGTH_SHORT).show();
|
Toast.makeText(getContext(), "Network error: " + t.getMessage(), Toast.LENGTH_SHORT).show();
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
//Method to Delete a service
|
/**
|
||||||
|
* Displays a confirmation dialog and handles the deletion of a service.
|
||||||
|
*/
|
||||||
private void deleteService() {
|
private void deleteService() {
|
||||||
//Alert the user to confirm the delete
|
//Alert the user to confirm the delete
|
||||||
new AlertDialog.Builder(requireContext())
|
new AlertDialog.Builder(requireContext())
|
||||||
@@ -146,7 +156,7 @@ public class ServiceDetailFragment extends Fragment {
|
|||||||
Toast.makeText(getContext(), "Service deleted successfully!", Toast.LENGTH_SHORT).show();
|
Toast.makeText(getContext(), "Service deleted successfully!", Toast.LENGTH_SHORT).show();
|
||||||
navigateBack();
|
navigateBack();
|
||||||
} else {
|
} else {
|
||||||
Toast.makeText(getContext(), "Failed to delete service: " + response.code(), Toast.LENGTH_SHORT).show();
|
ErrorUtils.showErrorMessage(getContext(), response, "Failed to delete service");
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -154,7 +164,7 @@ public class ServiceDetailFragment extends Fragment {
|
|||||||
public void onFailure(Call<Void> call, Throwable t) {
|
public void onFailure(Call<Void> call, Throwable t) {
|
||||||
ActivityLogger.logException(requireContext(), "ServiceDetailFragment.deleteService", new Exception(t));
|
ActivityLogger.logException(requireContext(), "ServiceDetailFragment.deleteService", new Exception(t));
|
||||||
Log.e("ServiceDetailFragment", "Error deleting service", t);
|
Log.e("ServiceDetailFragment", "Error deleting service", t);
|
||||||
Toast.makeText(getContext(), "Error: " + t.getMessage(), Toast.LENGTH_SHORT).show();
|
Toast.makeText(getContext(), "Network error: " + t.getMessage(), Toast.LENGTH_SHORT).show();
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
})
|
})
|
||||||
@@ -162,12 +172,16 @@ public class ServiceDetailFragment extends Fragment {
|
|||||||
.show();
|
.show();
|
||||||
}
|
}
|
||||||
|
|
||||||
//Helper method to navigate back to the list
|
/**
|
||||||
|
* Navigates back to the previous screen.
|
||||||
|
*/
|
||||||
private void navigateBack() {
|
private void navigateBack() {
|
||||||
NavHostFragment.findNavController(this).popBackStack();
|
NavHostFragment.findNavController(this).popBackStack();
|
||||||
}
|
}
|
||||||
|
|
||||||
//helper function to check if service is being edited or added and show the view accordingly
|
/**
|
||||||
|
* Handles arguments passed to the fragment to determine if it's in edit or add mode.
|
||||||
|
*/
|
||||||
private void handleArguments() {
|
private void handleArguments() {
|
||||||
// Service is being edited if the bundle contains a serviceId
|
// Service is being edited if the bundle contains a serviceId
|
||||||
if (getArguments() != null && getArguments().containsKey("serviceId")) {
|
if (getArguments() != null && getArguments().containsKey("serviceId")) {
|
||||||
@@ -192,7 +206,9 @@ public class ServiceDetailFragment extends Fragment {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
//helper function to get controls from layout
|
/**
|
||||||
|
* Set UI components from the layout.
|
||||||
|
*/
|
||||||
private void initViews(View view) {
|
private void initViews(View view) {
|
||||||
tvMode = view.findViewById(R.id.tvMode);
|
tvMode = view.findViewById(R.id.tvMode);
|
||||||
tvServiceId = view.findViewById(R.id.tvServiceId);
|
tvServiceId = view.findViewById(R.id.tvServiceId);
|
||||||
|
|||||||
@@ -19,7 +19,9 @@ import com.example.petstoremobile.R;
|
|||||||
import com.example.petstoremobile.api.SupplierApi;
|
import com.example.petstoremobile.api.SupplierApi;
|
||||||
import com.example.petstoremobile.dtos.SupplierDTO;
|
import com.example.petstoremobile.dtos.SupplierDTO;
|
||||||
import com.example.petstoremobile.utils.ActivityLogger;
|
import com.example.petstoremobile.utils.ActivityLogger;
|
||||||
|
import com.example.petstoremobile.utils.ErrorUtils;
|
||||||
import com.example.petstoremobile.utils.InputValidator;
|
import com.example.petstoremobile.utils.InputValidator;
|
||||||
|
import com.example.petstoremobile.utils.UIUtils;
|
||||||
|
|
||||||
import javax.inject.Inject;
|
import javax.inject.Inject;
|
||||||
|
|
||||||
@@ -28,6 +30,9 @@ import retrofit2.Call;
|
|||||||
import retrofit2.Callback;
|
import retrofit2.Callback;
|
||||||
import retrofit2.Response;
|
import retrofit2.Response;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Fragment for displaying and editing supplier details.
|
||||||
|
*/
|
||||||
@AndroidEntryPoint
|
@AndroidEntryPoint
|
||||||
public class SupplierDetailFragment extends Fragment {
|
public class SupplierDetailFragment extends Fragment {
|
||||||
|
|
||||||
@@ -56,7 +61,9 @@ public class SupplierDetailFragment extends Fragment {
|
|||||||
return view;
|
return view;
|
||||||
}
|
}
|
||||||
|
|
||||||
//Method to Update or Add a supplier
|
/**
|
||||||
|
* Handles the saving of supplier data (adding or updating).
|
||||||
|
*/
|
||||||
private void saveSupplier() {
|
private void saveSupplier() {
|
||||||
// Validates all fields using InputValidator
|
// Validates all fields using InputValidator
|
||||||
if (!InputValidator.isNotEmpty(etSupCompany, "Company Name")) return;
|
if (!InputValidator.isNotEmpty(etSupCompany, "Company Name")) return;
|
||||||
@@ -92,7 +99,7 @@ public class SupplierDetailFragment extends Fragment {
|
|||||||
Toast.makeText(getContext(), "Supplier updated successfully!", Toast.LENGTH_SHORT).show();
|
Toast.makeText(getContext(), "Supplier updated successfully!", Toast.LENGTH_SHORT).show();
|
||||||
navigateBack();
|
navigateBack();
|
||||||
} else {
|
} else {
|
||||||
Toast.makeText(getContext(), "Failed to update supplier: " + response.code(), Toast.LENGTH_SHORT).show();
|
ErrorUtils.showErrorMessage(getContext(), response, "Failed to update supplier");
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -100,7 +107,7 @@ public class SupplierDetailFragment extends Fragment {
|
|||||||
public void onFailure(Call<SupplierDTO> call, Throwable t) {
|
public void onFailure(Call<SupplierDTO> call, Throwable t) {
|
||||||
ActivityLogger.logException(requireContext(), "SupplierDetailFragment.updateSupplier", new Exception(t));
|
ActivityLogger.logException(requireContext(), "SupplierDetailFragment.updateSupplier", new Exception(t));
|
||||||
Log.e("SupplierDetailFragment", "Error updating supplier", t);
|
Log.e("SupplierDetailFragment", "Error updating supplier", t);
|
||||||
Toast.makeText(getContext(), "Error: " + t.getMessage(), Toast.LENGTH_SHORT).show();
|
Toast.makeText(getContext(), "Network error: " + t.getMessage(), Toast.LENGTH_SHORT).show();
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
} else {
|
} else {
|
||||||
@@ -113,7 +120,7 @@ public class SupplierDetailFragment extends Fragment {
|
|||||||
Toast.makeText(getContext(), "Supplier added successfully!", Toast.LENGTH_SHORT).show();
|
Toast.makeText(getContext(), "Supplier added successfully!", Toast.LENGTH_SHORT).show();
|
||||||
navigateBack();
|
navigateBack();
|
||||||
} else {
|
} else {
|
||||||
Toast.makeText(getContext(), "Failed to add supplier: " + response.code(), Toast.LENGTH_SHORT).show();
|
ErrorUtils.showErrorMessage(getContext(), response, "Failed to add supplier");
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -121,13 +128,15 @@ public class SupplierDetailFragment extends Fragment {
|
|||||||
public void onFailure(Call<SupplierDTO> call, Throwable t) {
|
public void onFailure(Call<SupplierDTO> call, Throwable t) {
|
||||||
ActivityLogger.logException(requireContext(), "SupplierDetailFragment.createSupplier", new Exception(t));
|
ActivityLogger.logException(requireContext(), "SupplierDetailFragment.createSupplier", new Exception(t));
|
||||||
Log.e("SupplierDetailFragment", "Error adding supplier", t);
|
Log.e("SupplierDetailFragment", "Error adding supplier", t);
|
||||||
Toast.makeText(getContext(), "Error: " + t.getMessage(), Toast.LENGTH_SHORT).show();
|
Toast.makeText(getContext(), "Network error: " + t.getMessage(), Toast.LENGTH_SHORT).show();
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
//Method to Delete a supplier
|
/**
|
||||||
|
* Displays a confirmation dialog and handles the deletion of a supplier.
|
||||||
|
*/
|
||||||
private void deleteSupplier() {
|
private void deleteSupplier() {
|
||||||
//Alert the user to confirm the delete
|
//Alert the user to confirm the delete
|
||||||
new AlertDialog.Builder(requireContext())
|
new AlertDialog.Builder(requireContext())
|
||||||
@@ -142,7 +151,7 @@ public class SupplierDetailFragment extends Fragment {
|
|||||||
Toast.makeText(getContext(), "Supplier deleted successfully!", Toast.LENGTH_SHORT).show();
|
Toast.makeText(getContext(), "Supplier deleted successfully!", Toast.LENGTH_SHORT).show();
|
||||||
navigateBack();
|
navigateBack();
|
||||||
} else {
|
} else {
|
||||||
Toast.makeText(getContext(), "Failed to delete supplier: " + response.code(), Toast.LENGTH_SHORT).show();
|
ErrorUtils.showErrorMessage(getContext(), response, "Failed to delete supplier");
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -150,7 +159,7 @@ public class SupplierDetailFragment extends Fragment {
|
|||||||
public void onFailure(Call<Void> call, Throwable t) {
|
public void onFailure(Call<Void> call, Throwable t) {
|
||||||
ActivityLogger.logException(requireContext(), "SupplierDetailFragment.deleteSupplier", new Exception(t));
|
ActivityLogger.logException(requireContext(), "SupplierDetailFragment.deleteSupplier", new Exception(t));
|
||||||
Log.e("SupplierDetailFragment", "Error deleting supplier", t);
|
Log.e("SupplierDetailFragment", "Error deleting supplier", t);
|
||||||
Toast.makeText(getContext(), "Error: " + t.getMessage(), Toast.LENGTH_SHORT).show();
|
Toast.makeText(getContext(), "Network error: " + t.getMessage(), Toast.LENGTH_SHORT).show();
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
})
|
})
|
||||||
@@ -158,12 +167,16 @@ public class SupplierDetailFragment extends Fragment {
|
|||||||
.show();
|
.show();
|
||||||
}
|
}
|
||||||
|
|
||||||
//Helper method to navigate back to the list
|
/**
|
||||||
|
* Navigates back to the previous screen.
|
||||||
|
*/
|
||||||
private void navigateBack() {
|
private void navigateBack() {
|
||||||
NavHostFragment.findNavController(this).popBackStack();
|
NavHostFragment.findNavController(this).popBackStack();
|
||||||
}
|
}
|
||||||
|
|
||||||
//helper function to check if supplier is being edited or added and show the view accordingly
|
/**
|
||||||
|
* Handles arguments passed to the fragment to determine if it's in edit or add mode.
|
||||||
|
*/
|
||||||
private void handleArguments() {
|
private void handleArguments() {
|
||||||
// Supplier is being edited if the bundle contains a supId
|
// Supplier is being edited if the bundle contains a supId
|
||||||
if (getArguments() != null && getArguments().containsKey("supId")) {
|
if (getArguments() != null && getArguments().containsKey("supId")) {
|
||||||
@@ -189,7 +202,9 @@ public class SupplierDetailFragment extends Fragment {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
//helper function to get controls from layout
|
/**
|
||||||
|
* Initializes the UI components and sets up formatting for phone input.
|
||||||
|
*/
|
||||||
private void initViews(View view) {
|
private void initViews(View view) {
|
||||||
tvMode = view.findViewById(R.id.tvMode);
|
tvMode = view.findViewById(R.id.tvMode);
|
||||||
tvSupId = view.findViewById(R.id.tvSupId);
|
tvSupId = view.findViewById(R.id.tvSupId);
|
||||||
@@ -200,8 +215,7 @@ public class SupplierDetailFragment extends Fragment {
|
|||||||
etSupPhone = view.findViewById(R.id.etSupPhone);
|
etSupPhone = view.findViewById(R.id.etSupPhone);
|
||||||
|
|
||||||
// Add phone number formatting (CA) and limit length to 14 characters
|
// Add phone number formatting (CA) and limit length to 14 characters
|
||||||
etSupPhone.addTextChangedListener(new android.telephony.PhoneNumberFormattingTextWatcher("CA"));
|
UIUtils.formatPhoneInput(etSupPhone);
|
||||||
etSupPhone.setFilters(new android.text.InputFilter[]{new android.text.InputFilter.LengthFilter(14)});
|
|
||||||
|
|
||||||
btnSaveSupplier = view.findViewById(R.id.btnSaveSupplier);
|
btnSaveSupplier = view.findViewById(R.id.btnSaveSupplier);
|
||||||
btnDeleteSupplier = view.findViewById(R.id.btnDeleteSupplier);
|
btnDeleteSupplier = view.findViewById(R.id.btnDeleteSupplier);
|
||||||
|
|||||||
@@ -1,22 +1,11 @@
|
|||||||
package com.example.petstoremobile.fragments.listfragments.listprofilefragments;
|
package com.example.petstoremobile.fragments.listfragments.listprofilefragments;
|
||||||
|
|
||||||
import android.Manifest;
|
|
||||||
import android.app.Activity;
|
|
||||||
import android.content.Intent;
|
|
||||||
import android.content.pm.PackageManager;
|
|
||||||
import android.graphics.Color;
|
|
||||||
import android.net.Uri;
|
import android.net.Uri;
|
||||||
import android.os.Bundle;
|
import android.os.Bundle;
|
||||||
|
|
||||||
import androidx.activity.result.ActivityResultLauncher;
|
|
||||||
import androidx.activity.result.contract.ActivityResultContracts;
|
|
||||||
import androidx.appcompat.app.AlertDialog;
|
|
||||||
import androidx.core.content.ContextCompat;
|
|
||||||
import androidx.core.content.FileProvider;
|
|
||||||
import androidx.fragment.app.Fragment;
|
import androidx.fragment.app.Fragment;
|
||||||
import androidx.navigation.fragment.NavHostFragment;
|
import androidx.navigation.fragment.NavHostFragment;
|
||||||
|
|
||||||
import android.provider.MediaStore;
|
|
||||||
import android.util.Log;
|
import android.util.Log;
|
||||||
import android.view.LayoutInflater;
|
import android.view.LayoutInflater;
|
||||||
import android.view.View;
|
import android.view.View;
|
||||||
@@ -26,21 +15,14 @@ import android.widget.ImageView;
|
|||||||
import android.widget.TextView;
|
import android.widget.TextView;
|
||||||
import android.widget.Toast;
|
import android.widget.Toast;
|
||||||
|
|
||||||
import com.bumptech.glide.Glide;
|
|
||||||
import com.bumptech.glide.load.engine.DiskCacheStrategy;
|
|
||||||
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.api.PetApi;
|
import com.example.petstoremobile.api.PetApi;
|
||||||
import com.example.petstoremobile.api.auth.TokenManager;
|
import com.example.petstoremobile.api.auth.TokenManager;
|
||||||
import com.example.petstoremobile.fragments.ListFragment;
|
import com.example.petstoremobile.utils.FileUtils;
|
||||||
import com.example.petstoremobile.fragments.listfragments.detailfragments.PetDetailFragment;
|
import com.example.petstoremobile.utils.GlideUtils;
|
||||||
|
import com.example.petstoremobile.utils.ImagePickerHelper;
|
||||||
|
|
||||||
import java.io.File;
|
import java.io.File;
|
||||||
import java.io.FileOutputStream;
|
|
||||||
import java.io.InputStream;
|
|
||||||
import java.util.ArrayList;
|
|
||||||
import java.util.List;
|
|
||||||
import java.util.Locale;
|
import java.util.Locale;
|
||||||
|
|
||||||
import javax.inject.Inject;
|
import javax.inject.Inject;
|
||||||
@@ -60,7 +42,6 @@ public class PetProfileFragment extends Fragment {
|
|||||||
private TextView tvPetName, tvPetSpecies, tvPetBreed, tvPetAge, tvPetPrice;
|
private TextView tvPetName, tvPetSpecies, tvPetBreed, tvPetAge, tvPetPrice;
|
||||||
private Button btnBack, btnEditPet, btnChangePhoto;
|
private Button btnBack, btnEditPet, btnChangePhoto;
|
||||||
private ImageView imgPet;
|
private ImageView imgPet;
|
||||||
private Uri photoUri;
|
|
||||||
private int petId;
|
private int petId;
|
||||||
private boolean hasImage = false;
|
private boolean hasImage = false;
|
||||||
|
|
||||||
@@ -68,59 +49,32 @@ public class PetProfileFragment extends Fragment {
|
|||||||
@Inject @Named("baseUrl") String baseUrl;
|
@Inject @Named("baseUrl") String baseUrl;
|
||||||
@Inject TokenManager tokenManager;
|
@Inject TokenManager tokenManager;
|
||||||
|
|
||||||
// launchers for camera and gallery
|
private ImagePickerHelper imagePickerHelper;
|
||||||
private ActivityResultLauncher<Intent> galleryLauncher;
|
|
||||||
private ActivityResultLauncher<Uri> cameraLauncher;
|
|
||||||
private ActivityResultLauncher<String> permissionLauncher;
|
|
||||||
|
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Initializes activity launchers for gallery, camera, and permissions.
|
||||||
|
*/
|
||||||
@Override
|
@Override
|
||||||
public void onCreate(Bundle savedInstanceState) {
|
public void onCreate(Bundle savedInstanceState) {
|
||||||
super.onCreate(savedInstanceState);
|
super.onCreate(savedInstanceState);
|
||||||
|
|
||||||
// Launcher to open gallery to select image
|
imagePickerHelper = new ImagePickerHelper(this, "pet_photo.jpg", new ImagePickerHelper.ImagePickerListener() {
|
||||||
galleryLauncher = registerForActivityResult(
|
@Override
|
||||||
new ActivityResultContracts.StartActivityForResult(),
|
public void onImagePicked(Uri uri) {
|
||||||
result -> {
|
uploadPetImage(uri);
|
||||||
if (result.getResultCode() == Activity.RESULT_OK && result.getData() != null) {
|
|
||||||
Uri selectedImage = result.getData().getData();
|
|
||||||
uploadPetImage(selectedImage);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
);
|
|
||||||
|
|
||||||
// Launcher for camera to open and capture image
|
|
||||||
cameraLauncher = registerForActivityResult(
|
|
||||||
new ActivityResultContracts.TakePicture(),
|
|
||||||
success -> {
|
|
||||||
if (success) {
|
|
||||||
uploadPetImage(photoUri);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
);
|
|
||||||
|
|
||||||
// Launcher to request camera permission
|
|
||||||
permissionLauncher = registerForActivityResult(
|
|
||||||
new ActivityResultContracts.RequestPermission(),
|
|
||||||
granted -> {
|
|
||||||
if (granted) {
|
|
||||||
launchCamera();
|
|
||||||
} else {
|
|
||||||
new AlertDialog.Builder(requireContext())
|
|
||||||
.setTitle("Permission Required")
|
|
||||||
.setMessage("Please grant camera permission to use this feature")
|
|
||||||
.setPositiveButton("Open Settings", (dialog, which) -> {
|
|
||||||
Intent intent = new Intent(android.provider.Settings.ACTION_APPLICATION_DETAILS_SETTINGS);
|
|
||||||
intent.setData(Uri.fromParts("package", requireContext().getPackageName(), null));
|
|
||||||
startActivity(intent);
|
|
||||||
})
|
|
||||||
.setNegativeButton("Cancel", null)
|
|
||||||
.show();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void onImageRemoved() {
|
||||||
|
deletePetImage();
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Inflates the layout, initializes views, and sets up click listeners.
|
||||||
|
*/
|
||||||
@Override
|
@Override
|
||||||
public View onCreateView(LayoutInflater inflater, ViewGroup container,
|
public View onCreateView(LayoutInflater inflater, ViewGroup container,
|
||||||
Bundle savedInstanceState) {
|
Bundle savedInstanceState) {
|
||||||
@@ -164,78 +118,38 @@ public class PetProfileFragment extends Fragment {
|
|||||||
|
|
||||||
//Make change photo button ask user to select a new photo
|
//Make change photo button ask user to select a new photo
|
||||||
btnChangePhoto.setOnClickListener(v -> {
|
btnChangePhoto.setOnClickListener(v -> {
|
||||||
List<String> options = new ArrayList<>();
|
imagePickerHelper.showImagePickerDialog("Change Pet Photo", hasImage);
|
||||||
options.add("Take Photo");
|
|
||||||
options.add("Choose from Gallery");
|
|
||||||
if (hasImage) {
|
|
||||||
options.add("Remove Photo");
|
|
||||||
}
|
|
||||||
|
|
||||||
new AlertDialog.Builder(requireContext())
|
|
||||||
.setTitle("Change Pet Photo")
|
|
||||||
.setItems(options.toArray(new String[0]), (dialog, which) -> {
|
|
||||||
String selected = options.get(which);
|
|
||||||
if (selected.equals("Take Photo")) {
|
|
||||||
// Choose Camera
|
|
||||||
//Checks if the user has granted the camera permission already
|
|
||||||
if (ContextCompat.checkSelfPermission(requireContext(), Manifest.permission.CAMERA) == PackageManager.PERMISSION_GRANTED) {
|
|
||||||
//if the permission is already granted then launch the camera
|
|
||||||
launchCamera();
|
|
||||||
} else {
|
|
||||||
//otherwise request the permission
|
|
||||||
permissionLauncher.launch(Manifest.permission.CAMERA);
|
|
||||||
}
|
|
||||||
} else if (selected.equals("Choose from Gallery")) {
|
|
||||||
Intent intent = new Intent(Intent.ACTION_PICK, MediaStore.Images.Media.EXTERNAL_CONTENT_URI);
|
|
||||||
galleryLauncher.launch(intent);
|
|
||||||
} else if (selected.equals("Remove Photo")) {
|
|
||||||
deletePetImage();
|
|
||||||
}
|
|
||||||
})
|
|
||||||
.show();
|
|
||||||
});
|
});
|
||||||
|
|
||||||
return view;
|
return view;
|
||||||
}
|
}
|
||||||
|
|
||||||
// Helper function to load pet image from backend
|
/**
|
||||||
|
* Fetches and displays the pet's image from the server.
|
||||||
|
*/
|
||||||
private void loadPetImage(int petId) {
|
private void loadPetImage(int petId) {
|
||||||
String imageUrl = baseUrl + String.format(Locale.US, PetApi.PET_IMAGE_PATH, petId);
|
String imageUrl = baseUrl + String.format(Locale.US, PetApi.PET_IMAGE_PATH, petId);
|
||||||
String token = tokenManager.getToken();
|
String token = tokenManager.getToken();
|
||||||
|
|
||||||
Object loadTarget = imageUrl;
|
GlideUtils.loadImageWithToken(requireContext(), imgPet, imageUrl, token, R.drawable.placeholder, new GlideUtils.ImageLoadListener() {
|
||||||
if (token != null) {
|
|
||||||
loadTarget = new GlideUrl(imageUrl, new LazyHeaders.Builder()
|
|
||||||
.addHeader("Authorization", "Bearer " + token)
|
|
||||||
.build());
|
|
||||||
}
|
|
||||||
|
|
||||||
Glide.with(this)
|
|
||||||
.load(loadTarget)
|
|
||||||
.skipMemoryCache(true)
|
|
||||||
.diskCacheStrategy(DiskCacheStrategy.NONE)
|
|
||||||
.placeholder(R.drawable.placeholder)
|
|
||||||
.error(R.drawable.placeholder)
|
|
||||||
.listener(new com.bumptech.glide.request.RequestListener<android.graphics.drawable.Drawable>() {
|
|
||||||
@Override
|
@Override
|
||||||
public boolean onLoadFailed(@androidx.annotation.Nullable com.bumptech.glide.load.engine.GlideException e, Object model, com.bumptech.glide.request.target.Target<android.graphics.drawable.Drawable> target, boolean isFirstResource) {
|
public void onResourceReady() {
|
||||||
hasImage = false;
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public boolean onResourceReady(android.graphics.drawable.Drawable resource, Object model, com.bumptech.glide.request.target.Target<android.graphics.drawable.Drawable> target, com.bumptech.glide.load.DataSource dataSource, boolean isFirstResource) {
|
|
||||||
hasImage = true;
|
hasImage = true;
|
||||||
return false;
|
|
||||||
}
|
|
||||||
})
|
|
||||||
.into(imgPet);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// Helper function to upload pet image to backend
|
@Override
|
||||||
|
public void onLoadFailed() {
|
||||||
|
hasImage = false;
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Uploads a selected or captured image a pet photo through the API.
|
||||||
|
*/
|
||||||
private void uploadPetImage(Uri uri) {
|
private void uploadPetImage(Uri uri) {
|
||||||
try {
|
try {
|
||||||
File file = getFileFromUri(uri);
|
File file = FileUtils.getFileFromUri(requireContext(), uri);
|
||||||
if (file == null) return;
|
if (file == null) return;
|
||||||
|
|
||||||
// Create RequestBody for file upload
|
// Create RequestBody for file upload
|
||||||
@@ -266,6 +180,9 @@ public class PetProfileFragment extends Fragment {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Sends a request to the API to remove the current pet photo.
|
||||||
|
*/
|
||||||
private void deletePetImage() {
|
private void deletePetImage() {
|
||||||
petApi.deletePetImage((long) petId).enqueue(new Callback<Void>() {
|
petApi.deletePetImage((long) petId).enqueue(new Callback<Void>() {
|
||||||
@Override
|
@Override
|
||||||
@@ -286,30 +203,4 @@ public class PetProfileFragment extends Fragment {
|
|||||||
}
|
}
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
// Helper function to create a temporary File object from a Uri for uploading
|
|
||||||
private File getFileFromUri(Uri uri) {
|
|
||||||
try {
|
|
||||||
InputStream inputStream = requireContext().getContentResolver().openInputStream(uri);
|
|
||||||
File tempFile = new File(requireContext().getCacheDir(), "upload_pet_image.jpg");
|
|
||||||
FileOutputStream outputStream = new FileOutputStream(tempFile);
|
|
||||||
byte[] buffer = new byte[1024];
|
|
||||||
int length;
|
|
||||||
while ((length = inputStream.read(buffer)) > 0) {
|
|
||||||
outputStream.write(buffer, 0, length);
|
|
||||||
}
|
|
||||||
outputStream.close();
|
|
||||||
inputStream.close();
|
|
||||||
return tempFile;
|
|
||||||
} catch (Exception e) {
|
|
||||||
Log.e("FILE_UTILS", "Error creating temp file", e);
|
|
||||||
return null;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
private void launchCamera() {
|
|
||||||
File photoFile = new File(requireContext().getCacheDir(), "pet_photo.jpg");
|
|
||||||
photoUri = FileProvider.getUriForFile(requireContext(), requireContext().getPackageName() + ".fileprovider", photoFile);
|
|
||||||
cameraLauncher.launch(photoUri);
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -0,0 +1,32 @@
|
|||||||
|
package com.example.petstoremobile.utils;
|
||||||
|
|
||||||
|
import android.content.Context;
|
||||||
|
import android.util.Log;
|
||||||
|
import android.widget.Toast;
|
||||||
|
import com.example.petstoremobile.dtos.ErrorResponse;
|
||||||
|
import com.google.gson.Gson;
|
||||||
|
import retrofit2.Response;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Utility class for handling API error responses.
|
||||||
|
*/
|
||||||
|
public class ErrorUtils {
|
||||||
|
/**
|
||||||
|
* Shows an error message to toast based on the response.
|
||||||
|
*/
|
||||||
|
public static void showErrorMessage(Context context, Response<?> response, String defaultMessage) {
|
||||||
|
try {
|
||||||
|
if (response.errorBody() != null) {
|
||||||
|
String errorJson = response.errorBody().string();
|
||||||
|
ErrorResponse errorResponse = new Gson().fromJson(errorJson, ErrorResponse.class);
|
||||||
|
if (errorResponse != null && errorResponse.getMessage() != null) {
|
||||||
|
Toast.makeText(context, errorResponse.getMessage(), Toast.LENGTH_LONG).show();
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
} catch (Exception e) {
|
||||||
|
Log.e("ErrorUtils", "Error parsing error body", e);
|
||||||
|
}
|
||||||
|
Toast.makeText(context, defaultMessage, Toast.LENGTH_SHORT).show();
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,122 @@
|
|||||||
|
package com.example.petstoremobile.utils;
|
||||||
|
|
||||||
|
import android.content.Context;
|
||||||
|
import android.graphics.drawable.Drawable;
|
||||||
|
import android.widget.ImageView;
|
||||||
|
import androidx.annotation.Nullable;
|
||||||
|
import com.bumptech.glide.Glide;
|
||||||
|
import com.bumptech.glide.load.DataSource;
|
||||||
|
import com.bumptech.glide.load.engine.DiskCacheStrategy;
|
||||||
|
import com.bumptech.glide.load.engine.GlideException;
|
||||||
|
import com.bumptech.glide.load.model.GlideUrl;
|
||||||
|
import com.bumptech.glide.load.model.LazyHeaders;
|
||||||
|
import com.bumptech.glide.request.RequestListener;
|
||||||
|
import com.bumptech.glide.request.target.Target;
|
||||||
|
import com.example.petstoremobile.R;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Utility class for loading images using Glide with authentication tokens.
|
||||||
|
*/
|
||||||
|
public class GlideUtils {
|
||||||
|
|
||||||
|
/**
|
||||||
|
* interface to check the status of the image load.
|
||||||
|
*/
|
||||||
|
public interface ImageLoadListener {
|
||||||
|
void onResourceReady();
|
||||||
|
void onLoadFailed();
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Loads an image from a URL into an ImageView with token.
|
||||||
|
*/
|
||||||
|
public static void loadImageWithToken(Context context, ImageView imageView, String url, String token, int placeholder) {
|
||||||
|
loadImageWithToken(context, imageView, url, token, placeholder, null);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Loads an image from a URL into an ImageView with token and listener.
|
||||||
|
*/
|
||||||
|
public static void loadImageWithToken(Context context, ImageView imageView, String url, String token, int placeholder, ImageLoadListener listener) {
|
||||||
|
if (url == null) {
|
||||||
|
imageView.setImageResource(placeholder);
|
||||||
|
if (listener != null) listener.onLoadFailed();
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
Object loadTarget = url;
|
||||||
|
if (token != null && url.startsWith("http")) {
|
||||||
|
loadTarget = new GlideUrl(url, new LazyHeaders.Builder()
|
||||||
|
.addHeader("Authorization", "Bearer " + token)
|
||||||
|
.build());
|
||||||
|
}
|
||||||
|
|
||||||
|
Glide.with(context)
|
||||||
|
.load(loadTarget)
|
||||||
|
.diskCacheStrategy(DiskCacheStrategy.NONE)
|
||||||
|
.skipMemoryCache(true)
|
||||||
|
.placeholder(placeholder)
|
||||||
|
.error(placeholder)
|
||||||
|
.listener(new RequestListener<Drawable>() {
|
||||||
|
@Override
|
||||||
|
public boolean onLoadFailed(@Nullable GlideException e, Object model, Target<Drawable> target, boolean isFirstResource) {
|
||||||
|
if (listener != null) listener.onLoadFailed();
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public boolean onResourceReady(Drawable resource, Object model, Target<Drawable> target, DataSource dataSource, boolean isFirstResource) {
|
||||||
|
if (listener != null) listener.onResourceReady();
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
})
|
||||||
|
.into(imageView);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Loads an image from a URL into an ImageView with token and applies circle cropping for image.
|
||||||
|
*/
|
||||||
|
public static void loadImageWithTokenCircle(Context context, ImageView imageView, String url, String token, int placeholder) {
|
||||||
|
loadImageWithTokenCircle(context, imageView, url, token, placeholder, null);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Loads an image from a URL into an ImageView with token, circle cropping, and listener.
|
||||||
|
*/
|
||||||
|
public static void loadImageWithTokenCircle(Context context, ImageView imageView, String url, String token, int placeholder, ImageLoadListener listener) {
|
||||||
|
if (url == null) {
|
||||||
|
imageView.setImageResource(placeholder);
|
||||||
|
if (listener != null) listener.onLoadFailed();
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
Object loadTarget = url;
|
||||||
|
if (token != null && url.startsWith("http")) {
|
||||||
|
loadTarget = new GlideUrl(url, new LazyHeaders.Builder()
|
||||||
|
.addHeader("Authorization", "Bearer " + token)
|
||||||
|
.build());
|
||||||
|
}
|
||||||
|
|
||||||
|
Glide.with(context)
|
||||||
|
.load(loadTarget)
|
||||||
|
.circleCrop()
|
||||||
|
.diskCacheStrategy(DiskCacheStrategy.NONE)
|
||||||
|
.skipMemoryCache(true)
|
||||||
|
.placeholder(placeholder)
|
||||||
|
.error(placeholder)
|
||||||
|
.listener(new RequestListener<Drawable>() {
|
||||||
|
@Override
|
||||||
|
public boolean onLoadFailed(@Nullable GlideException e, Object model, Target<Drawable> target, boolean isFirstResource) {
|
||||||
|
if (listener != null) listener.onLoadFailed();
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public boolean onResourceReady(Drawable resource, Object model, Target<Drawable> target, DataSource dataSource, boolean isFirstResource) {
|
||||||
|
if (listener != null) listener.onResourceReady();
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
})
|
||||||
|
.into(imageView);
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,160 @@
|
|||||||
|
package com.example.petstoremobile.utils;
|
||||||
|
|
||||||
|
import android.Manifest;
|
||||||
|
import android.app.Activity;
|
||||||
|
import android.content.Intent;
|
||||||
|
import android.content.pm.PackageManager;
|
||||||
|
import android.net.Uri;
|
||||||
|
import android.provider.MediaStore;
|
||||||
|
import androidx.activity.result.ActivityResultLauncher;
|
||||||
|
import androidx.activity.result.contract.ActivityResultContracts;
|
||||||
|
import androidx.appcompat.app.AlertDialog;
|
||||||
|
import androidx.core.content.ContextCompat;
|
||||||
|
import androidx.core.content.FileProvider;
|
||||||
|
import androidx.fragment.app.Fragment;
|
||||||
|
import java.io.File;
|
||||||
|
import java.util.ArrayList;
|
||||||
|
import java.util.List;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Helper class to handle image picking from camera or gallery.
|
||||||
|
*/
|
||||||
|
public class ImagePickerHelper {
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Listener interface to handle the results of image picking.
|
||||||
|
*/
|
||||||
|
public interface ImagePickerListener {
|
||||||
|
/**
|
||||||
|
* Called when an image has been successfully selected or captured.
|
||||||
|
*/
|
||||||
|
void onImagePicked(Uri uri);
|
||||||
|
/**
|
||||||
|
* Called when the user chooses to remove the existing image.
|
||||||
|
*/
|
||||||
|
void onImageRemoved();
|
||||||
|
}
|
||||||
|
|
||||||
|
private final Fragment fragment;
|
||||||
|
private final ImagePickerListener listener;
|
||||||
|
private final ActivityResultLauncher<Intent> galleryLauncher;
|
||||||
|
private final ActivityResultLauncher<Uri> cameraLauncher;
|
||||||
|
private final ActivityResultLauncher<String> permissionLauncher;
|
||||||
|
private Uri photoUri;
|
||||||
|
private final String tempFileName;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Constructor for ImagePickerHelper.
|
||||||
|
* Registers activity launchers for gallery, camera, and permissions.
|
||||||
|
*/
|
||||||
|
public ImagePickerHelper(Fragment fragment, String tempFileName, ImagePickerListener listener) {
|
||||||
|
this.fragment = fragment;
|
||||||
|
this.tempFileName = tempFileName;
|
||||||
|
this.listener = listener;
|
||||||
|
|
||||||
|
// Launcher to open gallery to select image
|
||||||
|
galleryLauncher = fragment.registerForActivityResult(
|
||||||
|
new ActivityResultContracts.StartActivityForResult(),
|
||||||
|
result -> {
|
||||||
|
if (result.getResultCode() == Activity.RESULT_OK && result.getData() != null) {
|
||||||
|
Uri selectedImage = result.getData().getData();
|
||||||
|
if (selectedImage != null) {
|
||||||
|
listener.onImagePicked(selectedImage);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
);
|
||||||
|
|
||||||
|
// Launcher for camera to open and capture image
|
||||||
|
cameraLauncher = fragment.registerForActivityResult(
|
||||||
|
new ActivityResultContracts.TakePicture(),
|
||||||
|
success -> {
|
||||||
|
if (success && photoUri != null) {
|
||||||
|
listener.onImagePicked(photoUri);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
);
|
||||||
|
|
||||||
|
// Launcher to request camera permission
|
||||||
|
permissionLauncher = fragment.registerForActivityResult(
|
||||||
|
new ActivityResultContracts.RequestPermission(),
|
||||||
|
granted -> {
|
||||||
|
if (granted) {
|
||||||
|
launchCamera();
|
||||||
|
} else {
|
||||||
|
showPermissionDeniedDialog();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Shows a dialog to choose between camera, gallery, and optionally remove photo.
|
||||||
|
*/
|
||||||
|
public void showImagePickerDialog(String title, boolean hasImage) {
|
||||||
|
List<String> options = new ArrayList<>();
|
||||||
|
options.add("Take Photo");
|
||||||
|
options.add("Choose from Gallery");
|
||||||
|
if (hasImage) {
|
||||||
|
options.add("Remove Photo");
|
||||||
|
}
|
||||||
|
|
||||||
|
new AlertDialog.Builder(fragment.requireContext())
|
||||||
|
.setTitle(title)
|
||||||
|
.setItems(options.toArray(new String[0]), (dialog, which) -> {
|
||||||
|
String selected = options.get(which);
|
||||||
|
if (selected.equals("Take Photo")) {
|
||||||
|
checkCameraPermission();
|
||||||
|
} else if (selected.equals("Choose from Gallery")) {
|
||||||
|
launchGallery();
|
||||||
|
} else if (selected.equals("Remove Photo")) {
|
||||||
|
listener.onImageRemoved();
|
||||||
|
}
|
||||||
|
})
|
||||||
|
.show();
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Checks if camera permission is granted and launches camera or requests permission.
|
||||||
|
*/
|
||||||
|
private void checkCameraPermission() {
|
||||||
|
if (ContextCompat.checkSelfPermission(fragment.requireContext(), Manifest.permission.CAMERA) == PackageManager.PERMISSION_GRANTED) {
|
||||||
|
launchCamera();
|
||||||
|
} else {
|
||||||
|
permissionLauncher.launch(Manifest.permission.CAMERA);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Prepares a temporary file and launches the camera app.
|
||||||
|
*/
|
||||||
|
private void launchCamera() {
|
||||||
|
File photoFile = new File(fragment.requireContext().getCacheDir(), tempFileName);
|
||||||
|
photoUri = FileProvider.getUriForFile(fragment.requireContext(), fragment.requireContext().getPackageName() + ".fileprovider", photoFile);
|
||||||
|
cameraLauncher.launch(photoUri);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Launches the gallery app to select an existing image.
|
||||||
|
*/
|
||||||
|
private void launchGallery() {
|
||||||
|
Intent intent = new Intent(Intent.ACTION_PICK, MediaStore.Images.Media.EXTERNAL_CONTENT_URI);
|
||||||
|
galleryLauncher.launch(intent);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Shows a dialog explaining why camera permission is needed when denied.
|
||||||
|
*/
|
||||||
|
private void showPermissionDeniedDialog() {
|
||||||
|
new AlertDialog.Builder(fragment.requireContext())
|
||||||
|
.setTitle("Permission Required")
|
||||||
|
.setMessage("Please grant camera permission to use this feature")
|
||||||
|
.setPositiveButton("Open Settings", (dialog, which) -> {
|
||||||
|
Intent intent = new Intent(android.provider.Settings.ACTION_APPLICATION_DETAILS_SETTINGS);
|
||||||
|
intent.setData(Uri.fromParts("package", fragment.requireContext().getPackageName(), null));
|
||||||
|
fragment.startActivity(intent);
|
||||||
|
})
|
||||||
|
.setNegativeButton("Cancel", null)
|
||||||
|
.show();
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,18 @@
|
|||||||
|
package com.example.petstoremobile.utils;
|
||||||
|
|
||||||
|
import android.telephony.PhoneNumberFormattingTextWatcher;
|
||||||
|
import android.text.InputFilter;
|
||||||
|
import android.widget.EditText;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Utility class for shared UI component logic and formatting.
|
||||||
|
*/
|
||||||
|
public class UIUtils {
|
||||||
|
/**
|
||||||
|
* Formats an EditText for to phone format
|
||||||
|
*/
|
||||||
|
public static void formatPhoneInput(EditText editText) {
|
||||||
|
editText.addTextChangedListener(new PhoneNumberFormattingTextWatcher("CA"));
|
||||||
|
editText.setFilters(new InputFilter[]{new InputFilter.LengthFilter(14)});
|
||||||
|
}
|
||||||
|
}
|
||||||
Reference in New Issue
Block a user