Azure deployment setup #297
@@ -22,9 +22,15 @@ public class MessageAdapter extends RecyclerView.Adapter<RecyclerView.ViewHolder
|
||||
private static final int TYPE_SENT = 1;
|
||||
private static final int TYPE_RECEIVED = 2;
|
||||
|
||||
public interface OnAttachmentClickListener {
|
||||
void onAttachmentClick(Message message);
|
||||
}
|
||||
|
||||
private final List<Message> messages;
|
||||
private Long currentUserId;
|
||||
private String token;
|
||||
private String baseUrl;
|
||||
private OnAttachmentClickListener attachmentClickListener;
|
||||
|
||||
public MessageAdapter(List<Message> messages, Long currentUserId) {
|
||||
this.messages = messages;
|
||||
@@ -40,6 +46,14 @@ public class MessageAdapter extends RecyclerView.Adapter<RecyclerView.ViewHolder
|
||||
this.token = token;
|
||||
}
|
||||
|
||||
public void setBaseUrl(String baseUrl) {
|
||||
this.baseUrl = baseUrl;
|
||||
}
|
||||
|
||||
public void setOnAttachmentClickListener(OnAttachmentClickListener listener) {
|
||||
this.attachmentClickListener = listener;
|
||||
}
|
||||
|
||||
@Override
|
||||
public int getItemViewType(int position) {
|
||||
Message m = messages.get(position);
|
||||
@@ -64,8 +78,8 @@ public class MessageAdapter extends RecyclerView.Adapter<RecyclerView.ViewHolder
|
||||
@Override
|
||||
public void onBindViewHolder(@NonNull RecyclerView.ViewHolder holder, int position) {
|
||||
Message m = messages.get(position);
|
||||
if (holder instanceof SentHolder) ((SentHolder) holder).bind(m, token);
|
||||
if (holder instanceof ReceivedHolder) ((ReceivedHolder) holder).bind(m, token);
|
||||
if (holder instanceof SentHolder) ((SentHolder) holder).bind(m, token, baseUrl, attachmentClickListener);
|
||||
if (holder instanceof ReceivedHolder) ((ReceivedHolder) holder).bind(m, token, baseUrl, attachmentClickListener);
|
||||
}
|
||||
|
||||
@Override public int getItemCount() { return messages.size(); }
|
||||
@@ -76,9 +90,23 @@ public class MessageAdapter extends RecyclerView.Adapter<RecyclerView.ViewHolder
|
||||
super(binding.getRoot());
|
||||
this.binding = binding;
|
||||
}
|
||||
void bind(Message m, String token) {
|
||||
binding.tvMessageContent.setText(m.getContent());
|
||||
displayAttachment(m, binding.ivAttachment, binding.tvAttachmentName, token);
|
||||
void bind(Message m, String token, String baseUrl, OnAttachmentClickListener listener) {
|
||||
// Check for Text
|
||||
if (m.getContent() != null && !m.getContent().isEmpty()) {
|
||||
binding.tvMessageContent.setVisibility(View.VISIBLE);
|
||||
binding.tvMessageContent.setText(m.getContent());
|
||||
} else {
|
||||
binding.tvMessageContent.setVisibility(View.GONE);
|
||||
}
|
||||
|
||||
// Check for Attachment
|
||||
displayAttachment(m, binding.ivAttachment, binding.tvAttachmentName, token, baseUrl);
|
||||
|
||||
View.OnClickListener click = v -> {
|
||||
if (listener != null) listener.onAttachmentClick(m);
|
||||
};
|
||||
binding.ivAttachment.setOnClickListener(click);
|
||||
binding.tvAttachmentName.setOnClickListener(click);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -88,22 +116,52 @@ public class MessageAdapter extends RecyclerView.Adapter<RecyclerView.ViewHolder
|
||||
super(binding.getRoot());
|
||||
this.binding = binding;
|
||||
}
|
||||
void bind(Message m, String token) {
|
||||
binding.tvMessageContent.setText(m.getContent());
|
||||
displayAttachment(m, binding.ivAttachment, binding.tvAttachmentName, token);
|
||||
void bind(Message m, String token, String baseUrl, OnAttachmentClickListener listener) {
|
||||
// Check for Text
|
||||
if (m.getContent() != null && !m.getContent().isEmpty()) {
|
||||
binding.tvMessageContent.setVisibility(View.VISIBLE);
|
||||
binding.tvMessageContent.setText(m.getContent());
|
||||
} else {
|
||||
binding.tvMessageContent.setVisibility(View.GONE);
|
||||
}
|
||||
|
||||
// Check for Attachment
|
||||
displayAttachment(m, binding.ivAttachment, binding.tvAttachmentName, token, baseUrl);
|
||||
|
||||
View.OnClickListener click = v -> {
|
||||
if (listener != null) listener.onAttachmentClick(m);
|
||||
};
|
||||
binding.ivAttachment.setOnClickListener(click);
|
||||
binding.tvAttachmentName.setOnClickListener(click);
|
||||
}
|
||||
}
|
||||
|
||||
// helper function to display the attachment to the chat bubble
|
||||
private static void displayAttachment(Message m, ImageView iv, TextView tvName, String token) {
|
||||
if (m.getAttachmentUrl() != null) {
|
||||
if (m.getAttachmentType() != null && m.getAttachmentType().startsWith("image/")) {
|
||||
private static void displayAttachment(Message m, ImageView iv, TextView tvName, String token, String baseUrl) {
|
||||
// Check if there's an attachment by looking at name or mime type
|
||||
if (m.getAttachmentName() != null || m.getAttachmentMimeType() != null) {
|
||||
// Construct the download URL using the message ID
|
||||
String url;
|
||||
if (baseUrl != null) {
|
||||
String cleanBase = baseUrl.endsWith("/") ? baseUrl.substring(0, baseUrl.length() - 1) : baseUrl;
|
||||
url = cleanBase + "/api/v1/chat/messages/" + m.getId() + "/attachment";
|
||||
} else {
|
||||
url = m.getAttachmentUrl(); // Fallback
|
||||
}
|
||||
|
||||
if (url == null) {
|
||||
iv.setVisibility(View.GONE);
|
||||
tvName.setVisibility(View.GONE);
|
||||
return;
|
||||
}
|
||||
|
||||
if (m.getAttachmentMimeType() != null && m.getAttachmentMimeType().startsWith("image/")) {
|
||||
iv.setVisibility(View.VISIBLE);
|
||||
tvName.setVisibility(View.GONE);
|
||||
|
||||
Object loadTarget = m.getAttachmentUrl();
|
||||
if (token != null && m.getAttachmentUrl().startsWith("http")) {
|
||||
loadTarget = new GlideUrl(m.getAttachmentUrl(), new LazyHeaders.Builder()
|
||||
Object loadTarget = url;
|
||||
if (token != null) {
|
||||
loadTarget = new GlideUrl(url, new LazyHeaders.Builder()
|
||||
.addHeader("Authorization", "Bearer " + token)
|
||||
.build());
|
||||
}
|
||||
|
||||
@@ -1,6 +1,7 @@
|
||||
package com.example.petstoremobile.api;
|
||||
|
||||
import com.example.petstoremobile.dtos.CustomerDTO;
|
||||
import com.example.petstoremobile.dtos.DropdownDTO;
|
||||
import com.example.petstoremobile.dtos.PageResponse;
|
||||
|
||||
import java.util.List;
|
||||
@@ -18,4 +19,7 @@ public interface CustomerApi {
|
||||
|
||||
@GET("api/v1/customers/{customerId}")
|
||||
Call<CustomerDTO> getCustomerById(@Path("customerId") Long customerId);
|
||||
|
||||
@GET("api/v1/dropdowns/customers")
|
||||
Call<List<DropdownDTO>> getCustomerDropdowns();
|
||||
}
|
||||
@@ -3,11 +3,17 @@ package com.example.petstoremobile.api;
|
||||
import com.example.petstoremobile.dtos.MessageDTO;
|
||||
import com.example.petstoremobile.dtos.SendMessageRequest;
|
||||
import java.util.List;
|
||||
|
||||
import okhttp3.MultipartBody;
|
||||
import okhttp3.ResponseBody;
|
||||
import retrofit2.Call;
|
||||
import retrofit2.http.Body;
|
||||
import retrofit2.http.GET;
|
||||
import retrofit2.http.Multipart;
|
||||
import retrofit2.http.POST;
|
||||
import retrofit2.http.Part;
|
||||
import retrofit2.http.Path;
|
||||
import retrofit2.http.Streaming;
|
||||
|
||||
//api calls to get and send messages
|
||||
public interface MessageApi {
|
||||
@@ -17,4 +23,16 @@ public interface MessageApi {
|
||||
|
||||
@POST("api/v1/chat/conversations/{id}/messages")
|
||||
Call<MessageDTO> sendMessage(@Path("id") Long conversationId, @Body SendMessageRequest request);
|
||||
|
||||
@Multipart
|
||||
@POST("api/v1/chat/conversations/{id}/attachments")
|
||||
Call<MessageDTO> sendMessageWithAttachment(
|
||||
@Path("id") Long conversationId,
|
||||
@Part MultipartBody.Part content,
|
||||
@Part MultipartBody.Part file
|
||||
);
|
||||
|
||||
@GET("api/v1/chat/messages/{id}/attachment")
|
||||
@Streaming
|
||||
Call<ResponseBody> downloadAttachment(@Path("id") Long messageId);
|
||||
}
|
||||
@@ -1,9 +1,12 @@
|
||||
package com.example.petstoremobile.api;
|
||||
|
||||
import com.example.petstoremobile.dtos.BulkDeleteRequest;
|
||||
import com.example.petstoremobile.dtos.DropdownDTO;
|
||||
import com.example.petstoremobile.dtos.PageResponse;
|
||||
import com.example.petstoremobile.dtos.PetDTO;
|
||||
|
||||
import java.util.List;
|
||||
|
||||
import okhttp3.MultipartBody;
|
||||
import retrofit2.Call;
|
||||
import retrofit2.http.Body;
|
||||
@@ -31,9 +34,16 @@ public interface PetApi {
|
||||
@Query("status") String status,
|
||||
@Query("species") String species,
|
||||
@Query("storeId") Long storeId,
|
||||
@Query("customerId") Long customerId,
|
||||
@Query("sort") String sort
|
||||
);
|
||||
|
||||
@GET("api/v1/dropdowns/customers/{customerId}/pets")
|
||||
Call<List<DropdownDTO>> getCustomerPets(@Path("customerId") Long customerId);
|
||||
|
||||
@GET("api/v1/dropdowns/adoption-pets")
|
||||
Call<List<DropdownDTO>> getAdoptionPets();
|
||||
|
||||
// Get pet by id
|
||||
@GET("api/v1/pets/{id}")
|
||||
Call<PetDTO> getPetById(@Path("id") Long id);
|
||||
|
||||
@@ -1,10 +1,14 @@
|
||||
package com.example.petstoremobile.api;
|
||||
|
||||
import com.example.petstoremobile.dtos.DropdownDTO;
|
||||
import com.example.petstoremobile.dtos.PageResponse;
|
||||
import com.example.petstoremobile.dtos.StoreDTO;
|
||||
|
||||
import java.util.List;
|
||||
|
||||
import retrofit2.Call;
|
||||
import retrofit2.http.GET;
|
||||
import retrofit2.http.Path;
|
||||
import retrofit2.http.Query;
|
||||
|
||||
public interface StoreApi {
|
||||
@@ -13,4 +17,10 @@ public interface StoreApi {
|
||||
Call<PageResponse<StoreDTO>> getAllStores(
|
||||
@Query("page") int page,
|
||||
@Query("size") int size);
|
||||
|
||||
@GET("api/v1/dropdowns/stores")
|
||||
Call<List<DropdownDTO>> getStoreDropdowns();
|
||||
|
||||
@GET("api/v1/dropdowns/stores/{storeId}/employees")
|
||||
Call<List<DropdownDTO>> getStoreEmployees(@Path("storeId") Long storeId);
|
||||
}
|
||||
|
||||
@@ -55,7 +55,7 @@ public class NetworkModule {
|
||||
@Singleton
|
||||
public static OkHttpClient provideOkHttpClient(TokenManager tokenManager) {
|
||||
HttpLoggingInterceptor interceptor = new HttpLoggingInterceptor();
|
||||
interceptor.setLevel(HttpLoggingInterceptor.Level.BODY);
|
||||
interceptor.setLevel(HttpLoggingInterceptor.Level.HEADERS);
|
||||
|
||||
return new OkHttpClient.Builder()
|
||||
.addInterceptor(interceptor)
|
||||
@@ -191,4 +191,4 @@ public class NetworkModule {
|
||||
public static RefundApi provideRefundApi(Retrofit retrofit) {
|
||||
return retrofit.create(RefundApi.class);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -12,6 +12,14 @@ public class ConversationDTO {
|
||||
|
||||
public ConversationDTO() {}
|
||||
|
||||
public ConversationDTO(Long id, Long customerId, Long staffId, String lastMessage, String status) {
|
||||
this.id = id;
|
||||
this.customerId = customerId;
|
||||
this.staffId = staffId;
|
||||
this.lastMessage = lastMessage;
|
||||
this.status = status;
|
||||
}
|
||||
|
||||
public Long getId() {
|
||||
return id;
|
||||
}
|
||||
|
||||
@@ -0,0 +1,29 @@
|
||||
package com.example.petstoremobile.dtos;
|
||||
|
||||
public class DropdownDTO {
|
||||
private Long id;
|
||||
private String label;
|
||||
|
||||
public DropdownDTO() {}
|
||||
|
||||
public DropdownDTO(Long id, String label) {
|
||||
this.id = id;
|
||||
this.label = label;
|
||||
}
|
||||
|
||||
public Long getId() {
|
||||
return id;
|
||||
}
|
||||
|
||||
public void setId(Long id) {
|
||||
this.id = id;
|
||||
}
|
||||
|
||||
public String getLabel() {
|
||||
return label;
|
||||
}
|
||||
|
||||
public void setLabel(String label) {
|
||||
this.label = label;
|
||||
}
|
||||
}
|
||||
@@ -28,8 +28,11 @@ public class MessageDTO {
|
||||
@SerializedName("attachmentName")
|
||||
private String attachmentName;
|
||||
|
||||
@SerializedName("attachmentType")
|
||||
private String attachmentType;
|
||||
@SerializedName("attachmentMimeType")
|
||||
private String attachmentMimeType;
|
||||
|
||||
@SerializedName("attachmentSizeBytes")
|
||||
private Long attachmentSizeBytes;
|
||||
|
||||
public MessageDTO() {}
|
||||
|
||||
@@ -57,6 +60,9 @@ public class MessageDTO {
|
||||
public String getAttachmentName() { return attachmentName; }
|
||||
public void setAttachmentName(String attachmentName) { this.attachmentName = attachmentName; }
|
||||
|
||||
public String getAttachmentType() { return attachmentType; }
|
||||
public void setAttachmentType(String attachmentType) { this.attachmentType = attachmentType; }
|
||||
public String getAttachmentMimeType() { return attachmentMimeType; }
|
||||
public void setAttachmentMimeType(String attachmentMimeType) { this.attachmentMimeType = attachmentMimeType; }
|
||||
|
||||
public Long getAttachmentSizeBytes() { return attachmentSizeBytes; }
|
||||
public void setAttachmentSizeBytes(Long attachmentSizeBytes) { this.attachmentSizeBytes = attachmentSizeBytes; }
|
||||
}
|
||||
@@ -1,15 +1,27 @@
|
||||
package com.example.petstoremobile.fragments;
|
||||
|
||||
import android.app.Activity;
|
||||
import android.app.Dialog;
|
||||
import android.content.ContentValues;
|
||||
import android.content.Intent;
|
||||
import android.database.Cursor;
|
||||
import android.net.Uri;
|
||||
import android.os.Build;
|
||||
import android.os.Bundle;
|
||||
import android.provider.OpenableColumns;
|
||||
import android.os.Environment;
|
||||
import android.provider.MediaStore;
|
||||
import android.util.Log;
|
||||
import android.view.*;
|
||||
import android.view.LayoutInflater;
|
||||
import android.view.View;
|
||||
import android.view.ViewGroup;
|
||||
import android.view.inputmethod.EditorInfo;
|
||||
import android.widget.Button;
|
||||
import android.widget.EditText;
|
||||
import android.widget.ImageButton;
|
||||
import android.widget.ImageView;
|
||||
import android.widget.LinearLayout;
|
||||
import android.widget.TextView;
|
||||
import android.widget.Toast;
|
||||
|
||||
import androidx.activity.result.ActivityResultLauncher;
|
||||
import androidx.activity.result.contract.ActivityResultContracts;
|
||||
import androidx.annotation.NonNull;
|
||||
@@ -17,6 +29,7 @@ import androidx.core.view.GravityCompat;
|
||||
import androidx.fragment.app.Fragment;
|
||||
import androidx.lifecycle.ViewModelProvider;
|
||||
import androidx.recyclerview.widget.LinearLayoutManager;
|
||||
|
||||
import com.bumptech.glide.Glide;
|
||||
import com.example.petstoremobile.R;
|
||||
import com.example.petstoremobile.adapters.ChatAdapter;
|
||||
@@ -25,20 +38,32 @@ import com.example.petstoremobile.api.auth.TokenManager;
|
||||
import com.example.petstoremobile.databinding.FragmentChatBinding;
|
||||
import com.example.petstoremobile.dtos.ConversationDTO;
|
||||
import com.example.petstoremobile.dtos.MessageDTO;
|
||||
import com.example.petstoremobile.dtos.SendMessageRequest;
|
||||
import com.example.petstoremobile.models.Chat;
|
||||
import com.example.petstoremobile.models.Message;
|
||||
import com.example.petstoremobile.services.ChatNotificationService;
|
||||
import com.example.petstoremobile.utils.DialogUtils;
|
||||
import com.example.petstoremobile.utils.FileUtils;
|
||||
import com.example.petstoremobile.utils.GlideUtils;
|
||||
import com.example.petstoremobile.utils.Resource;
|
||||
import com.example.petstoremobile.viewmodels.ChatViewModel;
|
||||
import com.example.petstoremobile.utils.UIUtils;
|
||||
import com.example.petstoremobile.viewmodels.ChatListViewModel;
|
||||
import com.example.petstoremobile.websocket.StompChatManager;
|
||||
|
||||
import java.util.*;
|
||||
import java.io.File;
|
||||
import java.io.FileOutputStream;
|
||||
import java.io.InputStream;
|
||||
import java.io.OutputStream;
|
||||
import java.util.ArrayList;
|
||||
import java.util.List;
|
||||
|
||||
import javax.inject.Inject;
|
||||
import javax.inject.Named;
|
||||
|
||||
import dagger.hilt.android.AndroidEntryPoint;
|
||||
import okhttp3.MediaType;
|
||||
import okhttp3.MultipartBody;
|
||||
import okhttp3.RequestBody;
|
||||
import okhttp3.ResponseBody;
|
||||
|
||||
@AndroidEntryPoint
|
||||
public class ChatFragment extends Fragment implements ChatAdapter.OnChatClickListener, StompChatManager.MessageListener,
|
||||
@@ -47,59 +72,46 @@ public class ChatFragment extends Fragment implements ChatAdapter.OnChatClickLis
|
||||
private static final String TAG = "ChatFragment";
|
||||
|
||||
private FragmentChatBinding binding;
|
||||
private ChatViewModel viewModel;
|
||||
private ChatListViewModel viewModel;
|
||||
|
||||
// Adapters
|
||||
private ChatAdapter chatAdapter;
|
||||
private ChatAdapter activeChatAdapter;
|
||||
private ChatAdapter closedChatAdapter;
|
||||
private MessageAdapter messageAdapter;
|
||||
|
||||
// Data
|
||||
private final List<Chat> chatList = new ArrayList<>();
|
||||
private final List<Chat> activeChatList = new ArrayList<>();
|
||||
private final List<Chat> closedChatList = new ArrayList<>();
|
||||
private final List<Message> messageList = new ArrayList<>();
|
||||
private final Map<Long, String> customerNames = new HashMap<>();
|
||||
private Uri pendingAttachmentUri;
|
||||
|
||||
@Inject TokenManager tokenManager;
|
||||
@Inject @Named("baseUrl") String baseUrl;
|
||||
|
||||
// chat
|
||||
private Long currentUserId;
|
||||
private Long activeConversationId;
|
||||
private StompChatManager stompChatManager;
|
||||
private ActivityResultLauncher<Intent> attachmentLauncher;
|
||||
|
||||
/**
|
||||
* Initializes the attachment launcher to handle file selection from the gallery.
|
||||
*/
|
||||
@Override
|
||||
public void onCreate(Bundle savedInstanceState) {
|
||||
super.onCreate(savedInstanceState);
|
||||
viewModel = new ViewModelProvider(this).get(ChatViewModel.class);
|
||||
viewModel = new ViewModelProvider(requireActivity()).get(ChatListViewModel.class);
|
||||
attachmentLauncher = registerForActivityResult(
|
||||
new ActivityResultContracts.StartActivityForResult(),
|
||||
result -> {
|
||||
if (result.getResultCode() == Activity.RESULT_OK && result.getData() != null) {
|
||||
Uri uri = result.getData().getData();
|
||||
if (uri != null) {
|
||||
showAttachmentPreview(uri);
|
||||
}
|
||||
if (uri != null) showAttachmentPreview(uri);
|
||||
}
|
||||
}
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* Inflates the layout, initializes UI components, and sets up click listeners for messaging.
|
||||
*/
|
||||
@Override
|
||||
public View onCreateView(@NonNull LayoutInflater inflater,
|
||||
ViewGroup container, Bundle savedInstanceState) {
|
||||
|
||||
binding = FragmentChatBinding.inflate(inflater, container, false);
|
||||
|
||||
binding.btnHamburger.setOnClickListener(v -> binding.chatDrawerLayout.openDrawer(GravityCompat.START));
|
||||
|
||||
// Set editor action listener for message field to send when enter is pressed
|
||||
binding.etMessage.setOnEditorActionListener((v, actionId, event) -> {
|
||||
if (actionId == EditorInfo.IME_ACTION_SEND || actionId == EditorInfo.IME_NULL) {
|
||||
binding.btnSend.performClick();
|
||||
@@ -108,63 +120,208 @@ public class ChatFragment extends Fragment implements ChatAdapter.OnChatClickLis
|
||||
return false;
|
||||
});
|
||||
|
||||
//When the send button is clicked check if there is an attachment and send using the correct helper function
|
||||
binding.btnSend.setOnClickListener(v -> {
|
||||
if (pendingAttachmentUri != null) {
|
||||
sendWithAttachment(pendingAttachmentUri);
|
||||
} else {
|
||||
sendMessage();
|
||||
}
|
||||
if (pendingAttachmentUri != null) sendWithAttachment(pendingAttachmentUri);
|
||||
else sendMessage();
|
||||
});
|
||||
|
||||
//When the attachment button is clicked open the file picker
|
||||
binding.btnAttach.setOnClickListener(v -> selectAttachment());
|
||||
binding.btnRemoveAttachment.setOnClickListener(v -> removeAttachment());
|
||||
|
||||
setupDrawerToggles();
|
||||
setupRecyclerViews();
|
||||
observeViewModel();
|
||||
loadInitialData();
|
||||
|
||||
return binding.getRoot();
|
||||
}
|
||||
|
||||
/**
|
||||
* Configures the RecyclerViews for the conversation list and the message history.
|
||||
*/
|
||||
private void setupRecyclerViews() {
|
||||
// Set up Drawer menu to select conversation
|
||||
chatAdapter = new ChatAdapter(chatList, this);
|
||||
binding.rvChatList.setLayoutManager(new LinearLayoutManager(getContext()));
|
||||
binding.rvChatList.setAdapter(chatAdapter);
|
||||
private void setupDrawerToggles() {
|
||||
binding.headerActiveChats.setOnClickListener(v -> {
|
||||
if (binding.rvActiveChats.getVisibility() == View.VISIBLE) {
|
||||
binding.rvActiveChats.setVisibility(View.GONE);
|
||||
binding.ivActiveChevron.setImageResource(android.R.drawable.arrow_down_float);
|
||||
} else {
|
||||
binding.rvActiveChats.setVisibility(View.VISIBLE);
|
||||
binding.ivActiveChevron.setImageResource(android.R.drawable.arrow_up_float);
|
||||
}
|
||||
});
|
||||
|
||||
binding.headerClosedChats.setOnClickListener(v -> {
|
||||
if (binding.rvClosedChats.getVisibility() == View.VISIBLE) {
|
||||
binding.rvClosedChats.setVisibility(View.GONE);
|
||||
binding.ivClosedChevron.setImageResource(android.R.drawable.arrow_down_float);
|
||||
} else {
|
||||
binding.rvClosedChats.setVisibility(View.VISIBLE);
|
||||
binding.ivClosedChevron.setImageResource(android.R.drawable.arrow_up_float);
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
private void setupRecyclerViews() {
|
||||
activeChatAdapter = new ChatAdapter(activeChatList, this);
|
||||
binding.rvActiveChats.setLayoutManager(new LinearLayoutManager(getContext()));
|
||||
binding.rvActiveChats.setAdapter(activeChatAdapter);
|
||||
|
||||
closedChatAdapter = new ChatAdapter(closedChatList, this);
|
||||
binding.rvClosedChats.setLayoutManager(new LinearLayoutManager(getContext()));
|
||||
binding.rvClosedChats.setAdapter(closedChatAdapter);
|
||||
|
||||
// set up RecyclerView for selected chat to show messages
|
||||
messageAdapter = new MessageAdapter(messageList, null);
|
||||
messageAdapter.setBaseUrl(baseUrl);
|
||||
|
||||
messageAdapter.setOnAttachmentClickListener(message -> {
|
||||
if (message.getAttachmentMimeType() != null && message.getAttachmentMimeType().startsWith("image/")) {
|
||||
showFullScreenImage(message);
|
||||
} else {
|
||||
downloadFile(message);
|
||||
}
|
||||
});
|
||||
|
||||
LinearLayoutManager lm = new LinearLayoutManager(getContext());
|
||||
lm.setStackFromEnd(true);
|
||||
binding.rvMessages.setLayoutManager(lm);
|
||||
binding.rvMessages.setAdapter(messageAdapter);
|
||||
setConversationActive(false);
|
||||
setConversationActive(false, null);
|
||||
}
|
||||
|
||||
private void showFullScreenImage(Message message) {
|
||||
if (baseUrl == null || message.getId() == null) return;
|
||||
|
||||
Dialog dialog = new Dialog(requireContext(), android.R.style.Theme_Black_NoTitleBar_Fullscreen);
|
||||
dialog.setContentView(R.layout.dialog_full_screen_image);
|
||||
|
||||
ImageView imageView = dialog.findViewById(R.id.ivFullScreen);
|
||||
ImageButton closeButton = dialog.findViewById(R.id.btnClose);
|
||||
ImageButton downloadButton = dialog.findViewById(R.id.btnDownload);
|
||||
|
||||
String cleanBase = baseUrl.endsWith("/") ? baseUrl.substring(0, baseUrl.length() - 1) : baseUrl;
|
||||
String downloadUrl = cleanBase + "/api/v1/chat/messages/" + message.getId() + "/attachment";
|
||||
|
||||
GlideUtils.loadImageWithToken(requireContext(), imageView, downloadUrl, tokenManager.getToken(), R.drawable.placeholder);
|
||||
|
||||
closeButton.setOnClickListener(v -> dialog.dismiss());
|
||||
downloadButton.setOnClickListener(v -> downloadFile(message));
|
||||
imageView.setOnClickListener(v -> dialog.dismiss());
|
||||
dialog.show();
|
||||
}
|
||||
|
||||
private void downloadFile(Message message) {
|
||||
if (message.getId() == null) return;
|
||||
|
||||
DialogUtils.showConfirmDialog(requireContext(), "Download Attachment",
|
||||
"Do you want to download \"" + message.getAttachmentName() + "\"?", () -> {
|
||||
Toast.makeText(requireContext(), "Downloading " + message.getAttachmentName() + "...", Toast.LENGTH_SHORT).show();
|
||||
|
||||
viewModel.downloadAttachment(message.getId()).observe(getViewLifecycleOwner(), resource -> {
|
||||
if (resource == null) return;
|
||||
setLoading(resource.status == Resource.Status.LOADING);
|
||||
if (resource.status == Resource.Status.SUCCESS && resource.data != null) {
|
||||
saveFileToDownloads(resource.data, message.getAttachmentName(), message.getAttachmentMimeType());
|
||||
} else if (resource.status == Resource.Status.ERROR) {
|
||||
Toast.makeText(requireContext(), "Download failed: " + resource.message, Toast.LENGTH_SHORT).show();
|
||||
}
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
private void saveFileToDownloads(ResponseBody body, String fileName, String mimeType) {
|
||||
try {
|
||||
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.Q) {
|
||||
ContentValues values = new ContentValues();
|
||||
values.put(MediaStore.Downloads.DISPLAY_NAME, fileName);
|
||||
values.put(MediaStore.Downloads.MIME_TYPE, mimeType);
|
||||
values.put(MediaStore.Downloads.RELATIVE_PATH, Environment.DIRECTORY_DOWNLOADS);
|
||||
|
||||
Uri uri = requireContext().getContentResolver().insert(MediaStore.Downloads.EXTERNAL_CONTENT_URI, values);
|
||||
if (uri != null) {
|
||||
try (OutputStream outputStream = requireContext().getContentResolver().openOutputStream(uri);
|
||||
InputStream inputStream = body.byteStream()) {
|
||||
byte[] buffer = new byte[4096];
|
||||
int bytesRead;
|
||||
while ((bytesRead = inputStream.read(buffer)) != -1) {
|
||||
outputStream.write(buffer, 0, bytesRead);
|
||||
}
|
||||
Toast.makeText(requireContext(), "File saved to Downloads", Toast.LENGTH_SHORT).show();
|
||||
}
|
||||
}
|
||||
} else {
|
||||
File downloadsDir = Environment.getExternalStoragePublicDirectory(Environment.DIRECTORY_DOWNLOADS);
|
||||
File file = new File(downloadsDir, fileName);
|
||||
try (OutputStream outputStream = new FileOutputStream(file);
|
||||
InputStream inputStream = body.byteStream()) {
|
||||
byte[] buffer = new byte[4096];
|
||||
int bytesRead;
|
||||
while ((bytesRead = inputStream.read(buffer)) != -1) {
|
||||
outputStream.write(buffer, 0, bytesRead);
|
||||
}
|
||||
Toast.makeText(requireContext(), "File saved to Downloads: " + file.getAbsolutePath(), Toast.LENGTH_LONG).show();
|
||||
}
|
||||
}
|
||||
body.close();
|
||||
} catch (Exception e) {
|
||||
Log.e(TAG, "Error saving file", e);
|
||||
Toast.makeText(requireContext(), "Error saving file", Toast.LENGTH_SHORT).show();
|
||||
}
|
||||
}
|
||||
|
||||
private void observeViewModel() {
|
||||
viewModel.getActiveChats().observe(getViewLifecycleOwner(), list -> {
|
||||
activeChatList.clear();
|
||||
activeChatList.addAll(list);
|
||||
activeChatAdapter.notifyDataSetChanged();
|
||||
updateTitleAndStateIfActive(list);
|
||||
});
|
||||
|
||||
viewModel.getClosedChats().observe(getViewLifecycleOwner(), list -> {
|
||||
closedChatList.clear();
|
||||
closedChatList.addAll(list);
|
||||
closedChatAdapter.notifyDataSetChanged();
|
||||
updateTitleAndStateIfActive(list);
|
||||
});
|
||||
|
||||
viewModel.getMessageList().observe(getViewLifecycleOwner(), list -> {
|
||||
messageList.clear();
|
||||
messageList.addAll(list);
|
||||
messageAdapter.notifyDataSetChanged();
|
||||
scrollToBottom();
|
||||
});
|
||||
|
||||
viewModel.getIsLoading().observe(getViewLifecycleOwner(), this::setLoading);
|
||||
}
|
||||
|
||||
private void setLoading(boolean loading) {
|
||||
if (binding != null && binding.progressBar != null) {
|
||||
binding.progressBar.setVisibility(loading ? View.VISIBLE : View.GONE);
|
||||
}
|
||||
}
|
||||
|
||||
private void updateTitleAndStateIfActive(List<Chat> list) {
|
||||
if (activeConversationId != null) {
|
||||
for (Chat chat : list) {
|
||||
if (chat.getChatId().equals(String.valueOf(activeConversationId))) {
|
||||
binding.tvChatTitle.setText(chat.getCustomerName());
|
||||
setConversationActive(true, chat.getStatus());
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Loads authentication tokens and user info, then initializes the Stomp WebSocket connection.
|
||||
*/
|
||||
private void loadInitialData() {
|
||||
String token = tokenManager.getToken();
|
||||
currentUserId = tokenManager.getUserId();
|
||||
Long currentUserId = tokenManager.getUserId();
|
||||
String role = tokenManager.getRole();
|
||||
|
||||
messageAdapter.setCurrentUserId(currentUserId);
|
||||
messageAdapter.setToken(token);
|
||||
|
||||
// if token exist then connect to websocket
|
||||
if (token != null) {
|
||||
stompChatManager = new StompChatManager(token, role, baseUrl);
|
||||
stompChatManager.setMessageListener(this);
|
||||
stompChatManager.setConversationListener(this);
|
||||
stompChatManager.setConnectionListener(this);
|
||||
stompChatManager.connect();
|
||||
} else {
|
||||
Log.e(TAG, "No token found");
|
||||
}
|
||||
|
||||
if (getArguments() != null && getArguments().containsKey("conversation_id")) {
|
||||
@@ -172,141 +329,60 @@ public class ChatFragment extends Fragment implements ChatAdapter.OnChatClickLis
|
||||
} else if (getActivity() != null && getActivity().getIntent().hasExtra("conversation_id")) {
|
||||
activeConversationId = getActivity().getIntent().getLongExtra("conversation_id", -1);
|
||||
getActivity().getIntent().removeExtra("conversation_id");
|
||||
getActivity().getIntent().removeExtra("navigate_to");
|
||||
} else {
|
||||
activeConversationId = viewModel.getLastActiveConversationId();
|
||||
}
|
||||
|
||||
loadCustomers();
|
||||
viewModel.loadCustomers();
|
||||
|
||||
if (activeConversationId != null) {
|
||||
if (stompChatManager != null) stompChatManager.subscribeToConversation(activeConversationId);
|
||||
viewModel.loadMessageHistory(activeConversationId);
|
||||
} else {
|
||||
setConversationActive(false, null);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Fetches a list of customers from the ViewModel to display customer names for the chat list.
|
||||
*/
|
||||
private void loadCustomers() {
|
||||
viewModel.getAllCustomers(0, 100).observe(getViewLifecycleOwner(), resource -> {
|
||||
if (resource != null && resource.status == Resource.Status.SUCCESS && resource.data != null) {
|
||||
resource.data.getContent().forEach(c -> customerNames.put(c.getCustomerId(), c.getFullName()));
|
||||
loadConversations();
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* Retrieves all conversations for the current user through the ViewModel and populates the chat drawer.
|
||||
*/
|
||||
private void loadConversations() {
|
||||
viewModel.getAllConversations().observe(getViewLifecycleOwner(), resource -> {
|
||||
if (resource != null && resource.status == Resource.Status.SUCCESS && resource.data != null) {
|
||||
chatList.clear();
|
||||
for (ConversationDTO dto : resource.data) {
|
||||
String name = customerNames.getOrDefault(
|
||||
dto.getCustomerId(), "Customer #" + dto.getCustomerId());
|
||||
chatList.add(new Chat(String.valueOf(dto.getId()),
|
||||
name, dto.getLastMessage(),
|
||||
dto.getCustomerId(), dto.getStaffId()));
|
||||
}
|
||||
chatAdapter.notifyDataSetChanged();
|
||||
|
||||
if (activeConversationId != null) {
|
||||
setConversationActive(true);
|
||||
// Update title to customer name of active conversation
|
||||
for (Chat chat : chatList) {
|
||||
if (chat.getChatId().equals(String.valueOf(activeConversationId))) {
|
||||
binding.tvChatTitle.setText(chat.getCustomerName());
|
||||
break;
|
||||
}
|
||||
}
|
||||
if (stompChatManager != null) {
|
||||
stompChatManager.subscribeToConversation(activeConversationId);
|
||||
}
|
||||
loadMessageHistory(activeConversationId);
|
||||
} else {
|
||||
messageList.clear();
|
||||
messageAdapter.notifyDataSetChanged();
|
||||
setConversationActive(false);
|
||||
}
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* Handles selection of a chat from the drawer, updating the UI and subscribing to the WebSocket.
|
||||
*/
|
||||
@Override
|
||||
public void onChatClick(Chat chat) {
|
||||
activeConversationId = Long.parseLong(chat.getChatId());
|
||||
setConversationActive(true);
|
||||
viewModel.setLastActiveConversationId(activeConversationId);
|
||||
|
||||
setConversationActive(true, chat.getStatus());
|
||||
binding.tvChatTitle.setText(chat.getCustomerName());
|
||||
binding.chatDrawerLayout.closeDrawer(GravityCompat.START);
|
||||
|
||||
if (stompChatManager != null) {
|
||||
stompChatManager.subscribeToConversation(activeConversationId);
|
||||
}
|
||||
|
||||
loadMessageHistory(activeConversationId);
|
||||
if (stompChatManager != null) stompChatManager.subscribeToConversation(activeConversationId);
|
||||
viewModel.loadMessageHistory(activeConversationId);
|
||||
}
|
||||
|
||||
/**
|
||||
* Fetches the full message history for a specific conversation from the ViewModel.
|
||||
*/
|
||||
private void loadMessageHistory(Long conversationId) {
|
||||
viewModel.getMessages(conversationId).observe(getViewLifecycleOwner(), resource -> {
|
||||
if (resource != null && resource.status == Resource.Status.SUCCESS && resource.data != null) {
|
||||
messageList.clear();
|
||||
for (MessageDTO dto : resource.data) {
|
||||
messageList.add(dtoToModel(dto));
|
||||
}
|
||||
messageAdapter.notifyDataSetChanged();
|
||||
scrollToBottom();
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* Sends a plain text message to the currently active conversation through the ViewModel.
|
||||
*/
|
||||
private void sendMessage() {
|
||||
//check if a chat is selected
|
||||
if (activeConversationId == null) return;
|
||||
|
||||
//get the message from text field
|
||||
String text = binding.etMessage.getText().toString().trim();
|
||||
if (text.isEmpty()) return;
|
||||
|
||||
//clear text field after sending
|
||||
binding.etMessage.setText("");
|
||||
|
||||
//calls viewmodel to send the message
|
||||
viewModel.sendMessage(activeConversationId, new SendMessageRequest(text)).observe(getViewLifecycleOwner(), resource -> {
|
||||
if (resource != null && resource.status == Resource.Status.SUCCESS && resource.data != null) {
|
||||
messageList.add(dtoToModel(resource.data));
|
||||
messageAdapter.notifyItemInserted(messageList.size() - 1);
|
||||
scrollToBottom();
|
||||
loadConversations();
|
||||
viewModel.sendMessage(activeConversationId, text).observe(getViewLifecycleOwner(), resource -> {
|
||||
if (resource == null) return;
|
||||
setLoading(resource.status == Resource.Status.LOADING);
|
||||
if (resource.status == Resource.Status.SUCCESS && resource.data != null) {
|
||||
viewModel.addMessageLocally(resource.data);
|
||||
viewModel.loadConversations();
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* Launches a file picker intent to select an attachment for the message.
|
||||
*/
|
||||
private void selectAttachment() {
|
||||
Intent intent = new Intent(Intent.ACTION_GET_CONTENT);
|
||||
intent.setType("*/*");
|
||||
attachmentLauncher.launch(intent);
|
||||
}
|
||||
|
||||
/**
|
||||
* Displays a preview of the selected attachment in the UI.
|
||||
*/
|
||||
private void showAttachmentPreview(Uri uri) {
|
||||
pendingAttachmentUri = uri;
|
||||
binding.layoutAttachmentPreview.setVisibility(View.VISIBLE);
|
||||
|
||||
String mimeType = requireContext().getContentResolver().getType(uri);
|
||||
String fileName = getFileName(uri);
|
||||
binding.tvPreviewName.setText(fileName);
|
||||
|
||||
// If the file is an image, display a thumbnail of the image as well
|
||||
binding.tvPreviewName.setText(FileUtils.getFileName(requireContext(), uri));
|
||||
if (mimeType != null && mimeType.startsWith("image/")) {
|
||||
binding.ivPreview.setVisibility(View.VISIBLE);
|
||||
Glide.with(this).load(uri).into(binding.ivPreview);
|
||||
@@ -315,183 +391,94 @@ public class ChatFragment extends Fragment implements ChatAdapter.OnChatClickLis
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Clears the current attachment selection and hides the preview UI.
|
||||
*/
|
||||
private void removeAttachment() {
|
||||
pendingAttachmentUri = null;
|
||||
binding.layoutAttachmentPreview.setVisibility(View.GONE);
|
||||
}
|
||||
|
||||
/**
|
||||
* Show the display name of the file from its Uri.
|
||||
*/
|
||||
private String getFileName(Uri uri) {
|
||||
String result = null;
|
||||
if (uri.getScheme().equals("content")) {
|
||||
try (Cursor cursor = requireContext().getContentResolver().query(uri, null, null, null, null)) {
|
||||
if (cursor != null && cursor.moveToFirst()) {
|
||||
int index = cursor.getColumnIndex(OpenableColumns.DISPLAY_NAME);
|
||||
if (index != -1) {
|
||||
result = cursor.getString(index);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
if (result == null) {
|
||||
result = uri.getPath();
|
||||
int cut = result.lastIndexOf('/');
|
||||
if (cut != -1) {
|
||||
result = result.substring(cut + 1);
|
||||
}
|
||||
}
|
||||
return result;
|
||||
}
|
||||
|
||||
/**
|
||||
* Handles sending a message that includes a file attachment via the ViewModel.
|
||||
*/
|
||||
private void sendWithAttachment(Uri uri) {
|
||||
if (activeConversationId == null) return;
|
||||
|
||||
File file = FileUtils.getFileFromUri(requireContext(), uri);
|
||||
if (file == null) {
|
||||
Toast.makeText(requireContext(), "Failed to prepare file", Toast.LENGTH_SHORT).show();
|
||||
return;
|
||||
}
|
||||
|
||||
String text = binding.etMessage.getText().toString().trim();
|
||||
|
||||
MultipartBody.Part contentPart = text.isEmpty()
|
||||
? null
|
||||
: MultipartBody.Part.createFormData("content", text);
|
||||
|
||||
String mimeType = requireContext().getContentResolver().getType(uri);
|
||||
if (mimeType == null) mimeType = "application/octet-stream";
|
||||
|
||||
RequestBody filePartBody = RequestBody.create(file, MediaType.parse(mimeType));
|
||||
MultipartBody.Part filePart = MultipartBody.Part.createFormData("file", file.getName(), filePartBody);
|
||||
|
||||
binding.etMessage.setText("");
|
||||
removeAttachment();
|
||||
|
||||
if (!text.isEmpty()) {
|
||||
binding.etMessage.setText(text);
|
||||
}
|
||||
Toast.makeText(requireContext(), "File attachments are not supported", Toast.LENGTH_SHORT).show();
|
||||
}
|
||||
|
||||
/**
|
||||
* Callback triggered when a new message is received via the WebSocket.
|
||||
*/
|
||||
@Override
|
||||
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 (activeConversationId == null || !activeConversationId.equals(dto.getConversationId())) {
|
||||
updateConversationPreview(dto.getConversationId(), dto.getContent());
|
||||
return;
|
||||
}
|
||||
updateConversationPreview(dto.getConversationId(), dto.getContent());
|
||||
|
||||
if (currentUserId != null && currentUserId.equals(dto.getSenderId())) return;
|
||||
|
||||
//else add the message to the active chat if it's not from the current user
|
||||
messageList.add(dtoToModel(dto));
|
||||
requireActivity().runOnUiThread(() -> {
|
||||
messageAdapter.notifyItemInserted(messageList.size() - 1);
|
||||
scrollToBottom();
|
||||
viewModel.sendMessageWithAttachment(activeConversationId, contentPart, filePart).observe(getViewLifecycleOwner(), resource -> {
|
||||
if (resource == null) return;
|
||||
setLoading(resource.status == Resource.Status.LOADING);
|
||||
if (resource.status == Resource.Status.SUCCESS && resource.data != null) {
|
||||
viewModel.addMessageLocally(resource.data);
|
||||
viewModel.loadConversations();
|
||||
} else if (resource.status == Resource.Status.ERROR) {
|
||||
Toast.makeText(requireContext(), "Failed to send attachment: " + resource.message, Toast.LENGTH_SHORT).show();
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onMessageReceived(MessageDTO dto) {
|
||||
requireActivity().runOnUiThread(() -> {
|
||||
if (activeConversationId != null && activeConversationId.equals(dto.getConversationId())) {
|
||||
if (!tokenManager.getUserId().equals(dto.getSenderId())) {
|
||||
viewModel.addMessageLocally(dto);
|
||||
}
|
||||
}
|
||||
viewModel.updateConversationLocally(new ConversationDTO(dto.getConversationId(), 0L, 0L, dto.getContent(), ""));
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* Callback triggered when a conversation is created or updated via the WebSocket.
|
||||
*/
|
||||
@Override
|
||||
public void onConversationUpdated(ConversationDTO dto) {
|
||||
requireActivity().runOnUiThread(() -> {
|
||||
boolean updated = false;
|
||||
String name = customerNames.getOrDefault(
|
||||
dto.getCustomerId(), "Customer #" + dto.getCustomerId());
|
||||
|
||||
for (int i = 0; i < chatList.size(); i++) {
|
||||
Chat existing = chatList.get(i);
|
||||
if (existing.getChatId().equals(String.valueOf(dto.getId()))) {
|
||||
chatList.set(i, new Chat(
|
||||
String.valueOf(dto.getId()),
|
||||
name,
|
||||
dto.getLastMessage(),
|
||||
dto.getCustomerId(),
|
||||
dto.getStaffId()
|
||||
));
|
||||
chatAdapter.notifyItemChanged(i);
|
||||
updated = true;
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
if (!updated) {
|
||||
chatList.add(0, new Chat(
|
||||
String.valueOf(dto.getId()),
|
||||
name,
|
||||
dto.getLastMessage(),
|
||||
dto.getCustomerId(),
|
||||
dto.getStaffId()
|
||||
));
|
||||
chatAdapter.notifyItemInserted(0);
|
||||
}
|
||||
|
||||
viewModel.updateConversationLocally(dto);
|
||||
if (activeConversationId != null && activeConversationId.equals(dto.getId())) {
|
||||
setConversationActive(true);
|
||||
binding.tvChatTitle.setText(name);
|
||||
setConversationActive(true, dto.getStatus());
|
||||
binding.tvChatTitle.setText(viewModel.getCustomerName(dto.getCustomerId()));
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* Callback triggered when the WebSocket connection is successfully opened.
|
||||
*/
|
||||
@Override
|
||||
public void onSocketOpened() {
|
||||
if (!isAdded()) {
|
||||
return;
|
||||
}
|
||||
if (!isAdded()) return;
|
||||
requireActivity().runOnUiThread(() -> {
|
||||
loadConversations();
|
||||
if (activeConversationId != null) {
|
||||
loadMessageHistory(activeConversationId);
|
||||
}
|
||||
viewModel.loadConversations();
|
||||
if (activeConversationId != null) viewModel.loadMessageHistory(activeConversationId);
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* Callback triggered when the WebSocket connection is closed.
|
||||
*/
|
||||
@Override
|
||||
public void onSocketClosed() {
|
||||
if (!isAdded()) {
|
||||
return;
|
||||
}
|
||||
requireActivity().runOnUiThread(this::loadConversations);
|
||||
if (!isAdded()) return;
|
||||
requireActivity().runOnUiThread(viewModel::loadConversations);
|
||||
}
|
||||
|
||||
/**
|
||||
* Callback triggered when a WebSocket connection error occurs.
|
||||
*/
|
||||
@Override
|
||||
public void onSocketError() {
|
||||
if (!isAdded()) {
|
||||
return;
|
||||
}
|
||||
if (!isAdded()) return;
|
||||
requireActivity().runOnUiThread(() -> {
|
||||
loadConversations();
|
||||
if (activeConversationId != null) {
|
||||
loadMessageHistory(activeConversationId);
|
||||
}
|
||||
viewModel.loadConversations();
|
||||
if (activeConversationId != null) viewModel.loadMessageHistory(activeConversationId);
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* Converts a MessageDTO into a Message object.
|
||||
*/
|
||||
private Message dtoToModel(MessageDTO dto) {
|
||||
Message m = new Message();
|
||||
m.setId(dto.getId());
|
||||
m.setConversationId(dto.getConversationId());
|
||||
m.setSenderId(dto.getSenderId());
|
||||
m.setContent(dto.getContent());
|
||||
m.setTimestamp(dto.getTimestamp());
|
||||
m.setIsRead(dto.getIsRead());
|
||||
m.setAttachmentUrl(dto.getAttachmentUrl());
|
||||
m.setAttachmentName(dto.getAttachmentName());
|
||||
m.setAttachmentType(dto.getAttachmentType());
|
||||
return m;
|
||||
}
|
||||
|
||||
/**
|
||||
* Scrolls the message history RecyclerView to the most recent message.
|
||||
*/
|
||||
private void scrollToBottom() {
|
||||
if (!messageList.isEmpty()) {
|
||||
binding.rvMessages.post(() ->
|
||||
@@ -499,60 +486,26 @@ public class ChatFragment extends Fragment implements ChatAdapter.OnChatClickLis
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Updates the preview snippet of the last message for a specific conversation in the drawer.
|
||||
*/
|
||||
private void updateConversationPreview(Long conversationId, String lastMessage) {
|
||||
if (conversationId == null) {
|
||||
return;
|
||||
}
|
||||
requireActivity().runOnUiThread(() -> {
|
||||
for (int i = 0; i < chatList.size(); i++) {
|
||||
Chat existing = chatList.get(i);
|
||||
if (existing.getChatId().equals(String.valueOf(conversationId))) {
|
||||
Chat updated = new Chat(
|
||||
existing.getChatId(),
|
||||
existing.getCustomerName(),
|
||||
lastMessage,
|
||||
existing.getCustomerId(),
|
||||
existing.getStaffId()
|
||||
);
|
||||
chatList.set(i, updated);
|
||||
chatAdapter.notifyItemChanged(i);
|
||||
return;
|
||||
}
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* Toggles the UI state based on whether a conversation is currently selected.
|
||||
*/
|
||||
private void setConversationActive(boolean active) {
|
||||
binding.btnSend.setEnabled(active);
|
||||
binding.etMessage.setEnabled(active);
|
||||
binding.btnAttach.setEnabled(active);
|
||||
private void setConversationActive(boolean active, String status) {
|
||||
boolean isClosed = "CLOSED".equalsIgnoreCase(status);
|
||||
UIUtils.setViewsEnabled(active && !isClosed, binding.btnSend, binding.etMessage, binding.btnAttach);
|
||||
|
||||
if (!active) {
|
||||
activeConversationId = null;
|
||||
ChatNotificationService.activeConversationIdInUi = null;
|
||||
removeAttachment();
|
||||
if (binding != null && binding.tvChatTitle != null) binding.tvChatTitle.setText("Customer Chat");
|
||||
if (stompChatManager != null) {
|
||||
stompChatManager.clearConversationSubscription();
|
||||
}
|
||||
if (stompChatManager != null) stompChatManager.clearConversationSubscription();
|
||||
messageList.clear();
|
||||
messageAdapter.notifyDataSetChanged();
|
||||
binding.etMessage.setText("");
|
||||
binding.etMessage.setHint("Select a chat to start messaging");
|
||||
} else {
|
||||
binding.etMessage.setHint("Type a message...");
|
||||
binding.etMessage.setHint(isClosed ? "This chat is closed" : "Type a message...");
|
||||
ChatNotificationService.activeConversationIdInUi = activeConversationId;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Disconnects the WebSocket manager when the fragment view is destroyed.
|
||||
*/
|
||||
@Override
|
||||
public void onDestroyView() {
|
||||
super.onDestroyView();
|
||||
|
||||
@@ -172,6 +172,12 @@ public class ProfileFragment extends Fragment {
|
||||
return binding.getRoot();
|
||||
}
|
||||
|
||||
private void setLoading(boolean loading) {
|
||||
if (binding != null && binding.progressBar != null) {
|
||||
binding.progressBar.setVisibility(loading ? View.VISIBLE : View.GONE);
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onDestroyView() {
|
||||
super.onDestroyView();
|
||||
@@ -184,6 +190,7 @@ public class ProfileFragment extends Fragment {
|
||||
private void loadProfileData() {
|
||||
viewModel.getMe().observe(getViewLifecycleOwner(), resource -> {
|
||||
if (resource == null) return;
|
||||
setLoading(resource.status == Resource.Status.LOADING);
|
||||
if (resource.status == Resource.Status.SUCCESS && resource.data != null) {
|
||||
currentUser = resource.data;
|
||||
|
||||
@@ -229,6 +236,7 @@ public class ProfileFragment extends Fragment {
|
||||
//Call the backend to upload the avatar
|
||||
viewModel.uploadAvatar(body).observe(getViewLifecycleOwner(), resource -> {
|
||||
if (resource == null) return;
|
||||
setLoading(resource.status == Resource.Status.LOADING);
|
||||
if (resource.status == Resource.Status.SUCCESS) {
|
||||
Toast.makeText(getContext(), "Avatar updated successfully", Toast.LENGTH_SHORT).show();
|
||||
loadProfileData();
|
||||
@@ -247,6 +255,7 @@ public class ProfileFragment extends Fragment {
|
||||
private void deleteAvatar() {
|
||||
viewModel.deleteAvatar().observe(getViewLifecycleOwner(), resource -> {
|
||||
if (resource == null) return;
|
||||
setLoading(resource.status == Resource.Status.LOADING);
|
||||
if (resource.status == Resource.Status.SUCCESS) {
|
||||
hasImage = false;
|
||||
binding.imgProfile.setImageResource(R.drawable.placeholder);
|
||||
@@ -266,6 +275,7 @@ public class ProfileFragment extends Fragment {
|
||||
|
||||
viewModel.updateMe(updates).observe(getViewLifecycleOwner(), resource -> {
|
||||
if (resource == null) return;
|
||||
setLoading(resource.status == Resource.Status.LOADING);
|
||||
if (resource.status == Resource.Status.SUCCESS && resource.data != null) {
|
||||
currentUser = resource.data;
|
||||
Toast.makeText(getContext(), "Profile updated successfully", Toast.LENGTH_SHORT).show();
|
||||
|
||||
@@ -24,9 +24,8 @@ import com.example.petstoremobile.utils.BulkDeleteHandler;
|
||||
import com.example.petstoremobile.utils.Resource;
|
||||
import com.example.petstoremobile.utils.SpinnerUtils;
|
||||
import com.example.petstoremobile.utils.UIUtils;
|
||||
import com.example.petstoremobile.viewmodels.AdoptionViewModel;
|
||||
import com.example.petstoremobile.viewmodels.AdoptionListViewModel;
|
||||
import com.example.petstoremobile.utils.EventDecorator;
|
||||
import com.example.petstoremobile.viewmodels.StoreViewModel;
|
||||
import com.prolificinteractive.materialcalendarview.CalendarDay;
|
||||
import com.prolificinteractive.materialcalendarview.CalendarMode;
|
||||
|
||||
@@ -46,28 +45,19 @@ public class AdoptionFragment extends Fragment implements AdoptionAdapter.OnAdop
|
||||
|
||||
private FragmentAdoptionBinding binding;
|
||||
private List<AdoptionDTO> adoptionList = new ArrayList<>();
|
||||
private List<StoreDTO> storeList = new ArrayList<>();
|
||||
private AdoptionAdapter adapter;
|
||||
private AdoptionViewModel adoptionViewModel;
|
||||
private StoreViewModel storeViewModel;
|
||||
private AdoptionListViewModel viewModel;
|
||||
private BulkDeleteHandler bulkDeleteHandler;
|
||||
private CalendarDay selectedCalendarDay;
|
||||
private boolean isMonthMode = false;
|
||||
private final SimpleDateFormat dateFormat = new SimpleDateFormat("yyyy-MM-dd", Locale.getDefault());
|
||||
|
||||
/**
|
||||
* Initializes the fragment and its ViewModels.
|
||||
*/
|
||||
@Override
|
||||
public void onCreate(@Nullable Bundle savedInstanceState) {
|
||||
super.onCreate(savedInstanceState);
|
||||
adoptionViewModel = new ViewModelProvider(this).get(AdoptionViewModel.class);
|
||||
storeViewModel = new ViewModelProvider(this).get(StoreViewModel.class);
|
||||
viewModel = new ViewModelProvider(this).get(AdoptionListViewModel.class);
|
||||
}
|
||||
|
||||
/**
|
||||
* Sets up the fragment's UI components, including RecyclerView, Search, SwipeRefresh, and Calendar.
|
||||
*/
|
||||
@Override
|
||||
public View onCreateView(@NonNull LayoutInflater inflater, ViewGroup container,
|
||||
Bundle savedInstanceState) {
|
||||
@@ -81,6 +71,7 @@ public class AdoptionFragment extends Fragment implements AdoptionAdapter.OnAdop
|
||||
setupCalendar();
|
||||
setupFilterToggle();
|
||||
setupBulkDelete();
|
||||
observeViewModel();
|
||||
|
||||
binding.fabAddAdoption.setOnClickListener(v -> openDetail(-1));
|
||||
|
||||
@@ -91,6 +82,24 @@ public class AdoptionFragment extends Fragment implements AdoptionAdapter.OnAdop
|
||||
return binding.getRoot();
|
||||
}
|
||||
|
||||
private void observeViewModel() {
|
||||
viewModel.getAdoptions().observe(getViewLifecycleOwner(), list -> {
|
||||
adoptionList.clear();
|
||||
adoptionList.addAll(list);
|
||||
updateCalendarDecorators();
|
||||
adapter.notifyDataSetChanged();
|
||||
});
|
||||
|
||||
viewModel.getStores().observe(getViewLifecycleOwner(), list -> {
|
||||
SpinnerUtils.populateWhiteSpinner(requireContext(), binding.spinnerStoreAdoption, list,
|
||||
StoreDTO::getStoreName, "All Stores", -1L, StoreDTO::getStoreId);
|
||||
});
|
||||
|
||||
viewModel.getIsLoading().observe(getViewLifecycleOwner(), loading -> {
|
||||
binding.swipeRefreshAdoption.setRefreshing(loading);
|
||||
});
|
||||
}
|
||||
|
||||
private void setupBulkDelete() {
|
||||
bulkDeleteHandler = new BulkDeleteHandler(
|
||||
this,
|
||||
@@ -99,27 +108,18 @@ public class AdoptionFragment extends Fragment implements AdoptionAdapter.OnAdop
|
||||
binding.btnBulkDelete,
|
||||
adapter,
|
||||
"adoption",
|
||||
adoptionViewModel::bulkDeleteAdoptions,
|
||||
viewModel::bulkDeleteAdoptions,
|
||||
this::loadAdoptions
|
||||
);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onDestroyView() {
|
||||
super.onDestroyView();
|
||||
binding = null;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onResume() {
|
||||
super.onResume();
|
||||
loadAdoptions();
|
||||
loadStoreData();
|
||||
viewModel.loadStores();
|
||||
}
|
||||
|
||||
/**
|
||||
* Toggles the calendar display between week and month modes.
|
||||
*/
|
||||
private void toggleCalendarMode() {
|
||||
isMonthMode = !isMonthMode;
|
||||
binding.calendarViewAdoption.state().edit()
|
||||
@@ -127,35 +127,11 @@ public class AdoptionFragment extends Fragment implements AdoptionAdapter.OnAdop
|
||||
.commit();
|
||||
}
|
||||
|
||||
/**
|
||||
* Sets up the filter toggle button to show/hide the filter layout.
|
||||
*/
|
||||
private void setupFilterToggle() {
|
||||
UIUtils.setupFilterToggle(binding.btnToggleFilterAdoption, binding.layoutFilterAdoption,
|
||||
binding.etSearchAdoption, binding.spinnerStatusAdoption, binding.spinnerStoreAdoption);
|
||||
|
||||
binding.btnToggleFilterAdoption.setOnClickListener(v -> {
|
||||
boolean isVisible = binding.layoutFilterAdoption.getVisibility() == View.VISIBLE;
|
||||
binding.layoutFilterAdoption.setVisibility(isVisible ? View.GONE : View.VISIBLE);
|
||||
|
||||
binding.btnToggleFilterAdoption.setImageResource(isVisible ?
|
||||
android.R.drawable.ic_menu_search :
|
||||
android.R.drawable.ic_menu_close_clear_cancel);
|
||||
|
||||
if (isVisible) {
|
||||
binding.etSearchAdoption.setText("");
|
||||
binding.spinnerStatusAdoption.setSelection(0);
|
||||
binding.spinnerStoreAdoption.setSelection(0);
|
||||
selectedCalendarDay = null;
|
||||
binding.calendarViewAdoption.clearSelection();
|
||||
loadAdoptions();
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* Sets up the date selection listener for the calendar.
|
||||
*/
|
||||
private void setupCalendar() {
|
||||
binding.calendarViewAdoption.setOnDateChangedListener((widget, date, selected) -> {
|
||||
if (selected) {
|
||||
@@ -172,9 +148,6 @@ public class AdoptionFragment extends Fragment implements AdoptionAdapter.OnAdop
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* Updates the calendar decorators to highlight days with adoptions.
|
||||
*/
|
||||
private void updateCalendarDecorators() {
|
||||
HashSet<CalendarDay> datesWithAdoptions = new HashSet<>();
|
||||
for (AdoptionDTO adoption : adoptionList) {
|
||||
@@ -195,67 +168,37 @@ public class AdoptionFragment extends Fragment implements AdoptionAdapter.OnAdop
|
||||
binding.calendarViewAdoption.addDecorator(new EventDecorator(Color.RED, datesWithAdoptions));
|
||||
}
|
||||
|
||||
/**
|
||||
* Initializes the RecyclerView for displaying adoptions.
|
||||
*/
|
||||
private void setupRecyclerView() {
|
||||
adapter = new AdoptionAdapter(adoptionList, this);
|
||||
binding.recyclerViewAdoptions.setLayoutManager(new LinearLayoutManager(getContext()));
|
||||
binding.recyclerViewAdoptions.setAdapter(adapter);
|
||||
}
|
||||
|
||||
/**
|
||||
* Sets up the search bar for filtering
|
||||
*/
|
||||
private void setupSearch() {
|
||||
UIUtils.attachSearch(binding.etSearchAdoption, this::loadAdoptions);
|
||||
}
|
||||
|
||||
/**
|
||||
* Configures the status filter spinner.
|
||||
*/
|
||||
private void setupStatusFilter() {
|
||||
String[] statuses = {"All Statuses", "Completed", "Pending", "Cancelled"};
|
||||
SpinnerUtils.setupStringFilterSpinner(requireContext(), binding.spinnerStatusAdoption, statuses, this::loadAdoptions);
|
||||
}
|
||||
|
||||
/**
|
||||
* Configures the store filter spinner.
|
||||
*/
|
||||
private void setupStoreFilter() {
|
||||
SpinnerUtils.setupFilterSpinner(binding.spinnerStoreAdoption, this::loadAdoptions);
|
||||
}
|
||||
|
||||
/**
|
||||
* Fetches store data to populate the store filter.
|
||||
*/
|
||||
private void loadStoreData() {
|
||||
storeViewModel.getAllStores(0, 100).observe(getViewLifecycleOwner(), resource -> {
|
||||
if (resource.status == Resource.Status.SUCCESS && resource.data != null) {
|
||||
storeList = resource.data.getContent();
|
||||
SpinnerUtils.populateWhiteSpinner(requireContext(), binding.spinnerStoreAdoption, storeList,
|
||||
StoreDTO::getStoreName, "All Stores", -1L, StoreDTO::getStoreId);
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* Sets up the SwipeRefreshLayout to reload adoption data.
|
||||
*/
|
||||
private void setupSwipeRefresh() {
|
||||
binding.swipeRefreshAdoption.setOnRefreshListener(this::loadAdoptions);
|
||||
}
|
||||
|
||||
/**
|
||||
* Fetches the adoption list from the server through the ViewModel.
|
||||
*/
|
||||
private void loadAdoptions() {
|
||||
String query = binding.etSearchAdoption.getText().toString().trim();
|
||||
String status = binding.spinnerStatusAdoption.getSelectedItem() != null ? binding.spinnerStatusAdoption.getSelectedItem().toString() : "All Statuses";
|
||||
|
||||
Long storeId = null;
|
||||
if (binding.spinnerStoreAdoption.getSelectedItemPosition() > 0 && !storeList.isEmpty()) {
|
||||
storeId = storeList.get(binding.spinnerStoreAdoption.getSelectedItemPosition() - 1).getStoreId();
|
||||
List<StoreDTO> stores = viewModel.getStores().getValue();
|
||||
if (binding.spinnerStoreAdoption.getSelectedItemPosition() > 0 && stores != null && !stores.isEmpty()) {
|
||||
storeId = stores.get(binding.spinnerStoreAdoption.getSelectedItemPosition() - 1).getStoreId();
|
||||
}
|
||||
|
||||
String selectedDateString = null;
|
||||
@@ -267,52 +210,18 @@ public class AdoptionFragment extends Fragment implements AdoptionAdapter.OnAdop
|
||||
if (status.equals("All Statuses")) status = null;
|
||||
else status = status.toUpperCase();
|
||||
|
||||
adoptionViewModel.getAllAdoptions(0, 500, query, status, storeId, selectedDateString, null).observe(getViewLifecycleOwner(), resource -> {
|
||||
if (resource == null) return;
|
||||
|
||||
// Check the status to see if the resource is loaded and display the data
|
||||
switch (resource.status) {
|
||||
case LOADING:
|
||||
// Show loading indicator
|
||||
binding.swipeRefreshAdoption.setRefreshing(true);
|
||||
break;
|
||||
case SUCCESS:
|
||||
// Hide loading indicator and display data
|
||||
binding.swipeRefreshAdoption.setRefreshing(false);
|
||||
if (resource.data != null) {
|
||||
adoptionList.clear();
|
||||
adoptionList.addAll(resource.data.getContent());
|
||||
updateCalendarDecorators();
|
||||
adapter.notifyDataSetChanged();
|
||||
}
|
||||
break;
|
||||
case ERROR:
|
||||
// Hide loading indicator and toast error message
|
||||
binding.swipeRefreshAdoption.setRefreshing(false);
|
||||
Toast.makeText(getContext(), "Failed to load adoptions: " + resource.message, Toast.LENGTH_SHORT).show();
|
||||
Log.e("AdoptionFragment", "Error loading adoptions: " + resource.message);
|
||||
break;
|
||||
}
|
||||
});
|
||||
viewModel.loadAdoptions(true, query, status, storeId, selectedDateString, null);
|
||||
}
|
||||
|
||||
/**
|
||||
* Navigates to the adoption detail screen for a specific adoption or to create a new one.
|
||||
*/
|
||||
private void openDetail(int position) {
|
||||
Bundle args = new Bundle();
|
||||
|
||||
if (position != -1) {
|
||||
AdoptionDTO a = adoptionList.get(position);
|
||||
args.putLong("adoptionId", a.getAdoptionId());
|
||||
}
|
||||
|
||||
NavHostFragment.findNavController(this).navigate(R.id.nav_adoption_detail, args);
|
||||
}
|
||||
|
||||
/**
|
||||
* Handles item click in the adoption list.
|
||||
*/
|
||||
@Override
|
||||
public void onAdoptionClick(int position) { openDetail(position); }
|
||||
|
||||
@@ -322,4 +231,10 @@ public class AdoptionFragment extends Fragment implements AdoptionAdapter.OnAdop
|
||||
bulkDeleteHandler.onSelectionChanged(selectedCount);
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onDestroyView() {
|
||||
super.onDestroyView();
|
||||
binding = null;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -2,16 +2,14 @@ package com.example.petstoremobile.fragments.listfragments;
|
||||
|
||||
import android.graphics.Color;
|
||||
import android.os.Bundle;
|
||||
import android.util.Log;
|
||||
import android.view.*;
|
||||
import android.widget.*;
|
||||
import androidx.annotation.NonNull;
|
||||
import androidx.fragment.app.Fragment;
|
||||
import androidx.lifecycle.ViewModelProvider;
|
||||
import com.example.petstoremobile.databinding.FragmentAnalyticsBinding;
|
||||
import com.example.petstoremobile.dtos.SaleDTO;
|
||||
import com.example.petstoremobile.utils.UIUtils;
|
||||
import com.example.petstoremobile.viewmodels.SaleViewModel;
|
||||
import com.example.petstoremobile.viewmodels.AnalyticsViewModel;
|
||||
import dagger.hilt.android.AndroidEntryPoint;
|
||||
import java.math.BigDecimal;
|
||||
import java.math.RoundingMode;
|
||||
@@ -21,228 +19,130 @@ import java.util.*;
|
||||
public class AnalyticsFragment extends Fragment {
|
||||
|
||||
private FragmentAnalyticsBinding binding;
|
||||
private SaleViewModel saleViewModel;
|
||||
private AnalyticsViewModel viewModel;
|
||||
|
||||
@Override
|
||||
public View onCreateView(@NonNull LayoutInflater inflater, ViewGroup container,
|
||||
Bundle savedInstanceState) {
|
||||
binding = FragmentAnalyticsBinding.inflate(inflater, container, false);
|
||||
saleViewModel = new ViewModelProvider(this).get(SaleViewModel.class);
|
||||
viewModel = new ViewModelProvider(this).get(AnalyticsViewModel.class);
|
||||
|
||||
loadAnalytics();
|
||||
observeViewModel();
|
||||
viewModel.loadAnalytics();
|
||||
|
||||
binding.btnRefreshAnalytics.setOnClickListener(v -> loadAnalytics());
|
||||
binding.btnRefreshAnalytics.setOnClickListener(v -> viewModel.loadAnalytics());
|
||||
|
||||
UIUtils.setupHamburgerMenu(binding.btnHamburgerAnalytics, this);
|
||||
|
||||
return binding.getRoot();
|
||||
}
|
||||
|
||||
private void observeViewModel() {
|
||||
viewModel.getAnalyticsData().observe(getViewLifecycleOwner(), this::computeAndDisplay);
|
||||
|
||||
viewModel.getIsLoading().observe(getViewLifecycleOwner(), loading -> {
|
||||
binding.progressBar.setVisibility(loading ? View.VISIBLE : View.GONE);
|
||||
if (loading) {
|
||||
binding.tvTotalRevenue.setText("Loading...");
|
||||
binding.tvTotalTransactions.setText("...");
|
||||
binding.tvAvgTransaction.setText("...");
|
||||
binding.tvTotalItems.setText("...");
|
||||
}
|
||||
});
|
||||
|
||||
viewModel.getErrorMessage().observe(getViewLifecycleOwner(), error -> {
|
||||
if (error != null) showError(error);
|
||||
});
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onDestroyView() {
|
||||
super.onDestroyView();
|
||||
binding = null;
|
||||
}
|
||||
|
||||
private void loadAnalytics() {
|
||||
// Clear all sections
|
||||
private void computeAndDisplay(AnalyticsViewModel.AnalyticsData data) {
|
||||
if (data == null) return;
|
||||
|
||||
// Summary
|
||||
binding.tvTotalRevenue.setText("$" + data.totalRevenue.setScale(2, RoundingMode.HALF_UP));
|
||||
binding.tvTotalTransactions.setText(String.valueOf(data.totalTransactions));
|
||||
binding.tvAvgTransaction.setText("$" + data.avgTransaction);
|
||||
binding.tvTotalItems.setText(String.valueOf(data.totalItems));
|
||||
|
||||
// Top Revenue Products
|
||||
binding.llTopRevenue.removeAllViews();
|
||||
binding.llTopQuantity.removeAllViews();
|
||||
binding.llPaymentMethods.removeAllViews();
|
||||
binding.llEmployeePerformance.removeAllViews();
|
||||
binding.llDailyRevenue.removeAllViews();
|
||||
|
||||
// Show loading
|
||||
binding.tvTotalRevenue.setText("Loading...");
|
||||
binding.tvTotalTransactions.setText("...");
|
||||
binding.tvAvgTransaction.setText("...");
|
||||
binding.tvTotalItems.setText("...");
|
||||
|
||||
saleViewModel.getAllSales(0, 1000, null, null, null, "saleDate,desc")
|
||||
.observe(getViewLifecycleOwner(), resource -> {
|
||||
if (resource != null) {
|
||||
switch (resource.status) {
|
||||
case SUCCESS:
|
||||
if (resource.data != null) {
|
||||
computeAndDisplay(resource.data.getContent());
|
||||
}
|
||||
break;
|
||||
case ERROR:
|
||||
Log.e("Analytics", resource.message != null ? resource.message : "Error loading sales");
|
||||
showError("Failed to load sales data");
|
||||
break;
|
||||
case LOADING:
|
||||
// Already showing loading state in UI
|
||||
break;
|
||||
}
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
private void computeAndDisplay(List<SaleDTO> sales) {
|
||||
// Filter out refunds for most metrics
|
||||
List<SaleDTO> regularSales = new ArrayList<>();
|
||||
for (SaleDTO s : sales) {
|
||||
if (!Boolean.TRUE.equals(s.getIsRefund()))
|
||||
regularSales.add(s);
|
||||
}
|
||||
|
||||
// ── Summary ──────────────────────────────────────────
|
||||
BigDecimal totalRevenue = BigDecimal.ZERO;
|
||||
int totalItems = 0;
|
||||
|
||||
for (SaleDTO s : regularSales) {
|
||||
if (s.getTotalAmount() != null)
|
||||
totalRevenue = totalRevenue.add(s.getTotalAmount());
|
||||
if (s.getItems() != null) {
|
||||
for (SaleDTO.SaleItemDTO item : s.getItems()) {
|
||||
if (item.getQuantity() != null)
|
||||
totalItems += Math.abs(item.getQuantity());
|
||||
}
|
||||
if (data.topRevenueProducts != null && !data.topRevenueProducts.isEmpty()) {
|
||||
BigDecimal maxRevenue = data.topRevenueProducts.get(0).getValue();
|
||||
if (maxRevenue.compareTo(BigDecimal.ZERO) == 0) maxRevenue = BigDecimal.ONE;
|
||||
for (Map.Entry<String, BigDecimal> e : data.topRevenueProducts) {
|
||||
addBarRow(binding.llTopRevenue, e.getKey(), "$" + e.getValue().setScale(2, RoundingMode.HALF_UP),
|
||||
e.getValue().floatValue() / maxRevenue.floatValue(), "#ff6b35");
|
||||
}
|
||||
}
|
||||
|
||||
int totalTx = regularSales.size();
|
||||
BigDecimal avgTx = totalTx > 0
|
||||
? totalRevenue.divide(BigDecimal.valueOf(totalTx), 2, RoundingMode.HALF_UP)
|
||||
: BigDecimal.ZERO;
|
||||
|
||||
binding.tvTotalRevenue.setText("$" + totalRevenue.setScale(2, RoundingMode.HALF_UP));
|
||||
binding.tvTotalTransactions.setText(String.valueOf(totalTx));
|
||||
binding.tvAvgTransaction.setText("$" + avgTx);
|
||||
binding.tvTotalItems.setText(String.valueOf(totalItems));
|
||||
|
||||
// ── Top Products by Revenue ───────────────────────────
|
||||
Map<String, BigDecimal> revenueByProduct = new LinkedHashMap<>();
|
||||
Map<String, Integer> quantityByProduct = new LinkedHashMap<>();
|
||||
|
||||
for (SaleDTO s : regularSales) {
|
||||
if (s.getItems() != null) {
|
||||
for (SaleDTO.SaleItemDTO item : s.getItems()) {
|
||||
String name = item.getProductName() != null ? item.getProductName() : "Unknown";
|
||||
int qty = item.getQuantity() != null ? Math.abs(item.getQuantity()) : 0;
|
||||
BigDecimal lineTotal = item.getUnitPrice() != null
|
||||
? item.getUnitPrice().multiply(BigDecimal.valueOf(qty))
|
||||
: BigDecimal.ZERO;
|
||||
|
||||
revenueByProduct.merge(name, lineTotal, BigDecimal::add);
|
||||
quantityByProduct.merge(name, qty, Integer::sum);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Sort by revenue desc, take top 5
|
||||
List<Map.Entry<String, BigDecimal>> topRevenue = new ArrayList<>(revenueByProduct.entrySet());
|
||||
topRevenue.sort((a, b) -> b.getValue().compareTo(a.getValue()));
|
||||
BigDecimal maxRevenue = topRevenue.isEmpty() ? BigDecimal.ONE : topRevenue.get(0).getValue();
|
||||
|
||||
binding.llTopRevenue.removeAllViews();
|
||||
for (int i = 0; i < Math.min(5, topRevenue.size()); i++) {
|
||||
Map.Entry<String, BigDecimal> e = topRevenue.get(i);
|
||||
addBarRow(binding.llTopRevenue, e.getKey(), "$" + e.getValue().setScale(2, RoundingMode.HALF_UP),
|
||||
e.getValue().floatValue() / maxRevenue.floatValue(), "#ff6b35");
|
||||
}
|
||||
if (topRevenue.isEmpty())
|
||||
} else {
|
||||
addEmptyRow(binding.llTopRevenue, "No data");
|
||||
}
|
||||
|
||||
// Sort by quantity desc, take top 5
|
||||
List<Map.Entry<String, Integer>> topQuantity = new ArrayList<>(quantityByProduct.entrySet());
|
||||
topQuantity.sort((a, b) -> b.getValue() - a.getValue());
|
||||
int maxQty = topQuantity.isEmpty() ? 1 : topQuantity.get(0).getValue();
|
||||
|
||||
// Top Quantity Products
|
||||
binding.llTopQuantity.removeAllViews();
|
||||
for (int i = 0; i < Math.min(5, topQuantity.size()); i++) {
|
||||
Map.Entry<String, Integer> e = topQuantity.get(i);
|
||||
addBarRow(binding.llTopQuantity, e.getKey(), e.getValue() + " units",
|
||||
(float) e.getValue() / maxQty, "#4ecdc4");
|
||||
}
|
||||
if (topQuantity.isEmpty())
|
||||
addEmptyRow(binding.llTopQuantity, "No data");
|
||||
|
||||
// ── Payment Methods ───────────────────────────────────
|
||||
Map<String, Integer> paymentCount = new LinkedHashMap<>();
|
||||
for (SaleDTO s : regularSales) {
|
||||
String method = s.getPaymentMethod() != null ? s.getPaymentMethod() : "Unknown";
|
||||
paymentCount.merge(method, 1, Integer::sum);
|
||||
}
|
||||
|
||||
int maxPayment = paymentCount.values().stream().max(Integer::compare).orElse(1);
|
||||
String[] paymentColors = { "#1a759f", "#ff9f1c", "#577590", "#90be6d" };
|
||||
int ci = 0;
|
||||
binding.llPaymentMethods.removeAllViews();
|
||||
for (Map.Entry<String, Integer> e : paymentCount.entrySet()) {
|
||||
addBarRow(binding.llPaymentMethods, e.getKey(),
|
||||
e.getValue() + " transactions",
|
||||
(float) e.getValue() / maxPayment,
|
||||
paymentColors[ci % paymentColors.length]);
|
||||
ci++;
|
||||
}
|
||||
if (paymentCount.isEmpty())
|
||||
addEmptyRow(binding.llPaymentMethods, "No data");
|
||||
|
||||
// ── Employee Performance ──────────────────────────────
|
||||
Map<String, BigDecimal> employeeRevenue = new LinkedHashMap<>();
|
||||
for (SaleDTO s : regularSales) {
|
||||
String emp = s.getEmployeeName() != null ? s.getEmployeeName() : "Unknown";
|
||||
if (s.getTotalAmount() != null)
|
||||
employeeRevenue.merge(emp, s.getTotalAmount(), BigDecimal::add);
|
||||
}
|
||||
|
||||
List<Map.Entry<String, BigDecimal>> empList = new ArrayList<>(employeeRevenue.entrySet());
|
||||
empList.sort((a, b) -> b.getValue().compareTo(a.getValue()));
|
||||
BigDecimal maxEmp = empList.isEmpty() ? BigDecimal.ONE : empList.get(0).getValue();
|
||||
|
||||
binding.llEmployeePerformance.removeAllViews();
|
||||
for (Map.Entry<String, BigDecimal> e : empList) {
|
||||
addBarRow(binding.llEmployeePerformance, e.getKey(),
|
||||
"$" + e.getValue().setScale(2, RoundingMode.HALF_UP),
|
||||
e.getValue().floatValue() / maxEmp.floatValue(),
|
||||
"#1a759f");
|
||||
}
|
||||
if (empList.isEmpty())
|
||||
addEmptyRow(binding.llEmployeePerformance, "No data");
|
||||
|
||||
// ── Daily Revenue (last 7 days) ───────────────────────
|
||||
Map<String, BigDecimal> dailyRevenue = new TreeMap<>();
|
||||
|
||||
// Initialize last 7 days
|
||||
Calendar cal = Calendar.getInstance();
|
||||
for (int i = 6; i >= 0; i--) {
|
||||
Calendar day = Calendar.getInstance();
|
||||
day.add(Calendar.DAY_OF_YEAR, -i);
|
||||
String key = String.format("%04d-%02d-%02d",
|
||||
day.get(Calendar.YEAR),
|
||||
day.get(Calendar.MONTH) + 1,
|
||||
day.get(Calendar.DAY_OF_MONTH));
|
||||
dailyRevenue.put(key, BigDecimal.ZERO);
|
||||
}
|
||||
|
||||
for (SaleDTO s : regularSales) {
|
||||
if (s.getSaleDate() != null && s.getTotalAmount() != null) {
|
||||
String date = s.getSaleDate().length() >= 10
|
||||
? s.getSaleDate().substring(0, 10)
|
||||
: s.getSaleDate();
|
||||
if (dailyRevenue.containsKey(date)) {
|
||||
dailyRevenue.merge(date, s.getTotalAmount(), BigDecimal::add);
|
||||
}
|
||||
if (data.topQuantityProducts != null && !data.topQuantityProducts.isEmpty()) {
|
||||
int maxQty = data.topQuantityProducts.get(0).getValue();
|
||||
if (maxQty == 0) maxQty = 1;
|
||||
for (Map.Entry<String, Integer> e : data.topQuantityProducts) {
|
||||
addBarRow(binding.llTopQuantity, e.getKey(), e.getValue() + " units",
|
||||
(float) e.getValue() / maxQty, "#4ecdc4");
|
||||
}
|
||||
} else {
|
||||
addEmptyRow(binding.llTopQuantity, "No data");
|
||||
}
|
||||
|
||||
BigDecimal maxDaily = dailyRevenue.values().stream()
|
||||
.max(BigDecimal::compareTo).orElse(BigDecimal.ONE);
|
||||
if (maxDaily.compareTo(BigDecimal.ZERO) == 0)
|
||||
maxDaily = BigDecimal.ONE;
|
||||
// Payment Methods
|
||||
binding.llPaymentMethods.removeAllViews();
|
||||
if (data.paymentMethodStats != null && !data.paymentMethodStats.isEmpty()) {
|
||||
int maxPayment = data.paymentMethodStats.stream().mapToInt(Map.Entry::getValue).max().orElse(1);
|
||||
String[] paymentColors = { "#1a759f", "#ff9f1c", "#577590", "#90be6d" };
|
||||
int ci = 0;
|
||||
for (Map.Entry<String, Integer> e : data.paymentMethodStats) {
|
||||
addBarRow(binding.llPaymentMethods, e.getKey(),
|
||||
e.getValue() + " transactions",
|
||||
(float) e.getValue() / maxPayment,
|
||||
paymentColors[ci % paymentColors.length]);
|
||||
ci++;
|
||||
}
|
||||
} else {
|
||||
addEmptyRow(binding.llPaymentMethods, "No data");
|
||||
}
|
||||
|
||||
// Employee Performance
|
||||
binding.llEmployeePerformance.removeAllViews();
|
||||
if (data.employeePerformance != null && !data.employeePerformance.isEmpty()) {
|
||||
BigDecimal maxEmp = data.employeePerformance.get(data.employeePerformance.size() - 1).getValue();
|
||||
if (maxEmp.compareTo(BigDecimal.ZERO) == 0) maxEmp = BigDecimal.ONE;
|
||||
maxEmp = data.employeePerformance.get(0).getValue();
|
||||
if (maxEmp.compareTo(BigDecimal.ZERO) == 0) maxEmp = BigDecimal.ONE;
|
||||
|
||||
for (Map.Entry<String, BigDecimal> e : data.employeePerformance) {
|
||||
addBarRow(binding.llEmployeePerformance, e.getKey(),
|
||||
"$" + e.getValue().setScale(2, RoundingMode.HALF_UP),
|
||||
e.getValue().floatValue() / maxEmp.floatValue(),
|
||||
"#1a759f");
|
||||
}
|
||||
} else {
|
||||
addEmptyRow(binding.llEmployeePerformance, "No data");
|
||||
}
|
||||
|
||||
// Daily Revenue
|
||||
binding.llDailyRevenue.removeAllViews();
|
||||
for (Map.Entry<String, BigDecimal> e : dailyRevenue.entrySet()) {
|
||||
// Show just MM-DD
|
||||
String label = e.getKey().length() >= 10
|
||||
? e.getKey().substring(5)
|
||||
: e.getKey();
|
||||
addBarRow(binding.llDailyRevenue, label,
|
||||
"$" + e.getValue().setScale(2, RoundingMode.HALF_UP),
|
||||
e.getValue().floatValue() / maxDaily.floatValue(),
|
||||
"#ff6b35");
|
||||
if (data.dailyRevenue != null && !data.dailyRevenue.isEmpty()) {
|
||||
BigDecimal maxDaily = data.dailyRevenue.stream().map(Map.Entry::getValue).max(BigDecimal::compareTo).orElse(BigDecimal.ONE);
|
||||
if (maxDaily.compareTo(BigDecimal.ZERO) == 0) maxDaily = BigDecimal.ONE;
|
||||
for (Map.Entry<String, BigDecimal> e : data.dailyRevenue) {
|
||||
String label = e.getKey().length() >= 10 ? e.getKey().substring(5) : e.getKey();
|
||||
addBarRow(binding.llDailyRevenue, label,
|
||||
"$" + e.getValue().setScale(2, RoundingMode.HALF_UP),
|
||||
e.getValue().floatValue() / maxDaily.floatValue(),
|
||||
"#ff6b35");
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -14,7 +14,6 @@ import android.util.Log;
|
||||
import android.view.LayoutInflater;
|
||||
import android.view.View;
|
||||
import android.view.ViewGroup;
|
||||
import android.widget.Toast;
|
||||
|
||||
import com.example.petstoremobile.R;
|
||||
import com.example.petstoremobile.adapters.AppointmentAdapter;
|
||||
@@ -25,10 +24,9 @@ import com.example.petstoremobile.utils.BulkDeleteHandler;
|
||||
import com.example.petstoremobile.utils.Resource;
|
||||
import com.example.petstoremobile.utils.SpinnerUtils;
|
||||
import com.example.petstoremobile.utils.UIUtils;
|
||||
import com.example.petstoremobile.viewmodels.AppointmentViewModel;
|
||||
import com.example.petstoremobile.viewmodels.AppointmentListViewModel;
|
||||
import com.example.petstoremobile.utils.EventDecorator;
|
||||
import com.example.petstoremobile.viewmodels.AuthViewModel;
|
||||
import com.example.petstoremobile.viewmodels.StoreViewModel;
|
||||
import com.prolificinteractive.materialcalendarview.CalendarDay;
|
||||
import com.prolificinteractive.materialcalendarview.CalendarMode;
|
||||
|
||||
@@ -48,11 +46,9 @@ public class AppointmentFragment extends Fragment implements AppointmentAdapter.
|
||||
|
||||
private FragmentAppointmentBinding binding;
|
||||
private List<AppointmentDTO> appointmentList = new ArrayList<>();
|
||||
private List<StoreDTO> storeList = new ArrayList<>();
|
||||
|
||||
private AppointmentAdapter adapter;
|
||||
private AppointmentViewModel appointmentViewModel;
|
||||
private StoreViewModel storeViewModel;
|
||||
private AppointmentListViewModel viewModel;
|
||||
private AuthViewModel authViewModel;
|
||||
private BulkDeleteHandler bulkDeleteHandler;
|
||||
|
||||
@@ -61,20 +57,13 @@ public class AppointmentFragment extends Fragment implements AppointmentAdapter.
|
||||
private Long currentUserId = null;
|
||||
private final SimpleDateFormat dateFormat = new SimpleDateFormat("yyyy-MM-dd", Locale.getDefault());
|
||||
|
||||
/**
|
||||
* Initializes the fragment and its associated ViewModels.
|
||||
*/
|
||||
@Override
|
||||
public void onCreate(@Nullable Bundle savedInstanceState) {
|
||||
super.onCreate(savedInstanceState);
|
||||
appointmentViewModel = new ViewModelProvider(this).get(AppointmentViewModel.class);
|
||||
storeViewModel = new ViewModelProvider(this).get(StoreViewModel.class);
|
||||
viewModel = new ViewModelProvider(this).get(AppointmentListViewModel.class);
|
||||
authViewModel = new ViewModelProvider(this).get(AuthViewModel.class);
|
||||
}
|
||||
|
||||
/**
|
||||
* Sets up the fragment's UI, including RecyclerView, search, swipe-to-refresh, and calendar.
|
||||
*/
|
||||
@Override
|
||||
public View onCreateView(@NonNull LayoutInflater inflater, ViewGroup container,
|
||||
Bundle savedInstanceState) {
|
||||
@@ -89,6 +78,7 @@ public class AppointmentFragment extends Fragment implements AppointmentAdapter.
|
||||
setupFilterToggle();
|
||||
setupMyAppointmentFilter();
|
||||
setupBulkDelete();
|
||||
observeViewModel();
|
||||
|
||||
binding.fabAddAppointment.setOnClickListener(v -> openAppointmentDetails(-1));
|
||||
|
||||
@@ -101,6 +91,24 @@ public class AppointmentFragment extends Fragment implements AppointmentAdapter.
|
||||
return binding.getRoot();
|
||||
}
|
||||
|
||||
private void observeViewModel() {
|
||||
viewModel.getAppointments().observe(getViewLifecycleOwner(), list -> {
|
||||
appointmentList.clear();
|
||||
appointmentList.addAll(list);
|
||||
updateCalendarDecorators();
|
||||
adapter.notifyDataSetChanged();
|
||||
});
|
||||
|
||||
viewModel.getStores().observe(getViewLifecycleOwner(), list -> {
|
||||
SpinnerUtils.populateWhiteSpinner(requireContext(), binding.spinnerStore, list,
|
||||
StoreDTO::getStoreName, "All Stores", -1L, StoreDTO::getStoreId);
|
||||
});
|
||||
|
||||
viewModel.getIsLoading().observe(getViewLifecycleOwner(), loading -> {
|
||||
binding.swipeRefreshAppointment.setRefreshing(loading);
|
||||
});
|
||||
}
|
||||
|
||||
private void setupBulkDelete() {
|
||||
bulkDeleteHandler = new BulkDeleteHandler(
|
||||
this,
|
||||
@@ -109,27 +117,18 @@ public class AppointmentFragment extends Fragment implements AppointmentAdapter.
|
||||
binding.btnBulkDelete,
|
||||
adapter,
|
||||
"appointment",
|
||||
appointmentViewModel::bulkDeleteAppointments,
|
||||
viewModel::bulkDeleteAppointments,
|
||||
this::loadAppointmentData
|
||||
);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onDestroyView() {
|
||||
super.onDestroyView();
|
||||
binding = null;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onResume() {
|
||||
super.onResume();
|
||||
loadAppointmentData();
|
||||
loadStoreData();
|
||||
viewModel.loadStores();
|
||||
}
|
||||
|
||||
/**
|
||||
* Toggles the calendar between week and month display modes.
|
||||
*/
|
||||
private void toggleCalendarMode() {
|
||||
isMonthMode = !isMonthMode;
|
||||
binding.calendarView.state().edit()
|
||||
@@ -137,18 +136,12 @@ public class AppointmentFragment extends Fragment implements AppointmentAdapter.
|
||||
.commit();
|
||||
}
|
||||
|
||||
/**
|
||||
* Sets up the "My Appointments" filter button.
|
||||
*/
|
||||
private void setupMyAppointmentFilter() {
|
||||
binding.btnMyAppointments.setOnClickListener(v -> {
|
||||
loadAppointmentData();
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* Fetches current user info to get the employeeId.
|
||||
*/
|
||||
private void loadCurrentUserInfo() {
|
||||
authViewModel.getMe().observe(getViewLifecycleOwner(), resource -> {
|
||||
if (resource.status == Resource.Status.SUCCESS && resource.data != null) {
|
||||
@@ -157,37 +150,11 @@ public class AppointmentFragment extends Fragment implements AppointmentAdapter.
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* Sets up the filter toggle button to show/hide the filter layout.
|
||||
*/
|
||||
private void setupFilterToggle() {
|
||||
UIUtils.setupFilterToggle(binding.btnToggleFilter, binding.layoutFilter, binding.etSearchAppointment,
|
||||
binding.spinnerStatus, binding.spinnerStore);
|
||||
|
||||
// Add additional reset logic for elements specific to this fragment
|
||||
binding.btnToggleFilter.setOnClickListener(v -> {
|
||||
boolean isVisible = binding.layoutFilter.getVisibility() == View.VISIBLE;
|
||||
binding.layoutFilter.setVisibility(isVisible ? View.GONE : View.VISIBLE);
|
||||
|
||||
binding.btnToggleFilter.setImageResource(isVisible ?
|
||||
android.R.drawable.ic_menu_search :
|
||||
android.R.drawable.ic_menu_close_clear_cancel);
|
||||
|
||||
if (isVisible) {
|
||||
binding.etSearchAppointment.setText("");
|
||||
binding.spinnerStatus.setSelection(0);
|
||||
binding.spinnerStore.setSelection(0);
|
||||
binding.btnMyAppointments.setChecked(false);
|
||||
selectedCalendarDay = null;
|
||||
binding.calendarView.clearSelection();
|
||||
loadAppointmentData();
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* Sets up the date selection listener for the calendar.
|
||||
*/
|
||||
private void setupCalendar() {
|
||||
binding.calendarView.setOnDateChangedListener((widget, date, selected) -> {
|
||||
if (selected) {
|
||||
@@ -204,17 +171,11 @@ public class AppointmentFragment extends Fragment implements AppointmentAdapter.
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* Updates calendar indicators to highlight dates that have scheduled appointments.
|
||||
*/
|
||||
private void updateCalendarDecorators() {
|
||||
HashSet<CalendarDay> datesWithAppointments = new HashSet<>();
|
||||
SimpleDateFormat displayFormat = new SimpleDateFormat("yyyy-MM-dd", Locale.getDefault());
|
||||
for (AppointmentDTO appointment : appointmentList) {
|
||||
try {
|
||||
//Get the appointment date
|
||||
Date date = displayFormat.parse(appointment.getAppointmentDate());
|
||||
//if the date is not null, add it to the hashset
|
||||
Date date = dateFormat.parse(appointment.getAppointmentDate());
|
||||
if (date != null) {
|
||||
Calendar cal = Calendar.getInstance();
|
||||
cal.setTime(date);
|
||||
@@ -224,56 +185,27 @@ public class AppointmentFragment extends Fragment implements AppointmentAdapter.
|
||||
Log.e("AppointmentFragment", "Error parsing date: " + appointment.getAppointmentDate());
|
||||
}
|
||||
}
|
||||
//update the indicators to the calendar
|
||||
binding.calendarView.removeDecorators();
|
||||
binding.calendarView.addDecorator(new EventDecorator(Color.RED, datesWithAppointments));
|
||||
}
|
||||
|
||||
/**
|
||||
* Configures the search bar for filtering.
|
||||
*/
|
||||
private void setupSearch() {
|
||||
UIUtils.attachSearch(binding.etSearchAppointment, this::loadAppointmentData);
|
||||
}
|
||||
|
||||
/**
|
||||
* Configures the status filter spinner.
|
||||
*/
|
||||
private void setupStatusFilter() {
|
||||
String[] statuses = {"All Statuses", "Booked", "Completed", "Cancelled", "Missed"};
|
||||
SpinnerUtils.setupStringFilterSpinner(requireContext(), binding.spinnerStatus, statuses, this::loadAppointmentData);
|
||||
}
|
||||
|
||||
/**
|
||||
* Configures the store filter spinner.
|
||||
*/
|
||||
private void setupStoreFilter() {
|
||||
SpinnerUtils.setupFilterSpinner(binding.spinnerStore, this::loadAppointmentData);
|
||||
}
|
||||
|
||||
/**
|
||||
* Fetches store data to populate the store filter.
|
||||
*/
|
||||
private void loadStoreData() {
|
||||
storeViewModel.getAllStores(0, 100).observe(getViewLifecycleOwner(), resource -> {
|
||||
if (resource.status == Resource.Status.SUCCESS && resource.data != null) {
|
||||
storeList = resource.data.getContent();
|
||||
SpinnerUtils.populateWhiteSpinner(requireContext(), binding.spinnerStore, storeList,
|
||||
StoreDTO::getStoreName, "All Stores", -1L, StoreDTO::getStoreId);
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* Initializes the SwipeRefreshLayout to allow manual data refreshing.
|
||||
*/
|
||||
private void setupSwipeRefresh() {
|
||||
binding.swipeRefreshAppointment.setOnRefreshListener(this::loadAppointmentData);
|
||||
}
|
||||
|
||||
/**
|
||||
* Navigates to the appointment detail screen for editing or creating an appointment.
|
||||
*/
|
||||
private void openAppointmentDetails(int position) {
|
||||
Bundle args = new Bundle();
|
||||
if (position != -1) {
|
||||
@@ -283,9 +215,6 @@ public class AppointmentFragment extends Fragment implements AppointmentAdapter.
|
||||
NavHostFragment.findNavController(this).navigate(R.id.nav_appointment_detail, args);
|
||||
}
|
||||
|
||||
/**
|
||||
* Handles item click in the appointment list.
|
||||
*/
|
||||
@Override
|
||||
public void onAppointmentClick(int position) {
|
||||
openAppointmentDetails(position);
|
||||
@@ -298,16 +227,14 @@ public class AppointmentFragment extends Fragment implements AppointmentAdapter.
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Fetches appointment data from the server with all active filters.
|
||||
*/
|
||||
private void loadAppointmentData() {
|
||||
String query = binding.etSearchAppointment.getText().toString().trim();
|
||||
String status = binding.spinnerStatus.getSelectedItem() != null ? binding.spinnerStatus.getSelectedItem().toString() : "All Statuses";
|
||||
|
||||
Long storeId = null;
|
||||
if (binding.spinnerStore.getSelectedItemPosition() > 0 && !storeList.isEmpty()) {
|
||||
storeId = storeList.get(binding.spinnerStore.getSelectedItemPosition() - 1).getStoreId();
|
||||
List<StoreDTO> stores = viewModel.getStores().getValue();
|
||||
if (binding.spinnerStore.getSelectedItemPosition() > 0 && stores != null && !stores.isEmpty()) {
|
||||
storeId = stores.get(binding.spinnerStore.getSelectedItemPosition() - 1).getStoreId();
|
||||
}
|
||||
|
||||
String selectedDateString = null;
|
||||
@@ -324,41 +251,18 @@ public class AppointmentFragment extends Fragment implements AppointmentAdapter.
|
||||
if (status.equals("All Statuses")) status = null;
|
||||
else status = status.toUpperCase();
|
||||
|
||||
appointmentViewModel.getAllAppointments(0, 500, query, status, storeId, selectedDateString, employeeId).observe(getViewLifecycleOwner(), resource -> {
|
||||
if (resource == null) return;
|
||||
|
||||
// Check the status to see if the resource is loaded and display the data
|
||||
switch (resource.status) {
|
||||
case LOADING:
|
||||
// Show loading indicator
|
||||
binding.swipeRefreshAppointment.setRefreshing(true);
|
||||
break;
|
||||
case SUCCESS:
|
||||
// Hide loading indicator and display data
|
||||
binding.swipeRefreshAppointment.setRefreshing(false);
|
||||
if (resource.data != null) {
|
||||
appointmentList.clear();
|
||||
appointmentList.addAll(resource.data.getContent());
|
||||
updateCalendarDecorators();
|
||||
adapter.notifyDataSetChanged();
|
||||
}
|
||||
break;
|
||||
case ERROR:
|
||||
// Hide loading indicator and toast error message
|
||||
binding.swipeRefreshAppointment.setRefreshing(false);
|
||||
Toast.makeText(getContext(), "Failed to load appointments: " + resource.message, Toast.LENGTH_SHORT).show();
|
||||
Log.e("AppointmentFragment", "Error loading appointments: " + resource.message);
|
||||
break;
|
||||
}
|
||||
});
|
||||
viewModel.loadAppointments(query, status, storeId, selectedDateString, employeeId);
|
||||
}
|
||||
|
||||
/**
|
||||
* Initializes the RecyclerView for displaying appointments.
|
||||
*/
|
||||
private void setupRecyclerView() {
|
||||
adapter = new AppointmentAdapter(appointmentList, this);
|
||||
binding.recyclerViewAppointments.setLayoutManager(new LinearLayoutManager(getContext()));
|
||||
binding.recyclerViewAppointments.setAdapter(adapter);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onDestroyView() {
|
||||
super.onDestroyView();
|
||||
binding = null;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,7 +1,6 @@
|
||||
package com.example.petstoremobile.fragments.listfragments;
|
||||
|
||||
import android.os.Bundle;
|
||||
import android.util.Log;
|
||||
import android.view.LayoutInflater;
|
||||
import android.view.View;
|
||||
import android.view.ViewGroup;
|
||||
@@ -22,8 +21,7 @@ import com.example.petstoremobile.dtos.InventoryDTO;
|
||||
import com.example.petstoremobile.dtos.StoreDTO;
|
||||
import com.example.petstoremobile.utils.BulkDeleteHandler;
|
||||
import com.example.petstoremobile.utils.UIUtils;
|
||||
import com.example.petstoremobile.viewmodels.InventoryViewModel;
|
||||
import com.example.petstoremobile.utils.Resource;
|
||||
import com.example.petstoremobile.viewmodels.InventoryListViewModel;
|
||||
import com.example.petstoremobile.utils.SpinnerUtils;
|
||||
|
||||
import java.util.ArrayList;
|
||||
@@ -34,33 +32,18 @@ import dagger.hilt.android.AndroidEntryPoint;
|
||||
@AndroidEntryPoint
|
||||
public class InventoryFragment extends Fragment implements InventoryAdapter.OnInventoryClickListener {
|
||||
|
||||
private static final String TAG = "InventoryFragment";
|
||||
private static final int PAGE_SIZE = 20;
|
||||
|
||||
private FragmentInventoryBinding binding;
|
||||
private final List<InventoryDTO> inventoryList = new ArrayList<>();
|
||||
private List<StoreDTO> storeList = new ArrayList<>();
|
||||
private InventoryAdapter adapter;
|
||||
private InventoryViewModel viewModel;
|
||||
private InventoryListViewModel viewModel;
|
||||
private BulkDeleteHandler bulkDeleteHandler;
|
||||
|
||||
// Pagination
|
||||
private int currentPage = 0;
|
||||
private boolean isLastPage = false;
|
||||
private boolean isLoading = false;
|
||||
|
||||
/**
|
||||
* Initializes the fragment and its ViewModel.
|
||||
*/
|
||||
@Override
|
||||
public void onCreate(@Nullable Bundle savedInstanceState) {
|
||||
super.onCreate(savedInstanceState);
|
||||
viewModel = new ViewModelProvider(this).get(InventoryViewModel.class);
|
||||
viewModel = new ViewModelProvider(this).get(InventoryListViewModel.class);
|
||||
}
|
||||
|
||||
/**
|
||||
* Sets up the fragment's UI components, including the inventory list and search.
|
||||
*/
|
||||
@Override
|
||||
public View onCreateView(@NonNull LayoutInflater inflater, ViewGroup container,
|
||||
Bundle savedInstanceState) {
|
||||
@@ -72,8 +55,9 @@ public class InventoryFragment extends Fragment implements InventoryAdapter.OnIn
|
||||
setupSwipeRefresh();
|
||||
setupFilterToggle();
|
||||
setupBulkDelete();
|
||||
observeViewModel();
|
||||
|
||||
loadInventory(true);
|
||||
loadStoreData();
|
||||
|
||||
binding.fabAddInventory.setOnClickListener(v -> openDetail(null));
|
||||
|
||||
@@ -82,6 +66,23 @@ public class InventoryFragment extends Fragment implements InventoryAdapter.OnIn
|
||||
return binding.getRoot();
|
||||
}
|
||||
|
||||
private void observeViewModel() {
|
||||
viewModel.getInventory().observe(getViewLifecycleOwner(), list -> {
|
||||
inventoryList.clear();
|
||||
inventoryList.addAll(list);
|
||||
adapter.notifyDataSetChanged();
|
||||
});
|
||||
|
||||
viewModel.getStores().observe(getViewLifecycleOwner(), list -> {
|
||||
SpinnerUtils.populateWhiteSpinner(requireContext(), binding.spinnerStore, list,
|
||||
StoreDTO::getStoreName, "All Stores", -1L, StoreDTO::getStoreId);
|
||||
});
|
||||
|
||||
viewModel.getIsLoading().observe(getViewLifecycleOwner(), loading -> {
|
||||
binding.swipeRefreshInventory.setRefreshing(loading);
|
||||
});
|
||||
}
|
||||
|
||||
private void setupBulkDelete() {
|
||||
bulkDeleteHandler = new BulkDeleteHandler(
|
||||
this,
|
||||
@@ -95,49 +96,30 @@ public class InventoryFragment extends Fragment implements InventoryAdapter.OnIn
|
||||
);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onResume() {
|
||||
super.onResume();
|
||||
viewModel.loadStores();
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onDestroyView() {
|
||||
super.onDestroyView();
|
||||
binding = null;
|
||||
}
|
||||
|
||||
/**
|
||||
* Sets up the filter toggle button to show/hide the filter layout.
|
||||
*/
|
||||
private void setupFilterToggle() {
|
||||
UIUtils.setupFilterToggle(binding.btnToggleFilter, binding.layoutFilter, binding.etSearchInventory, binding.spinnerStore);
|
||||
}
|
||||
|
||||
/**
|
||||
* Sets up the search bar for filtering.
|
||||
*/
|
||||
private void setupSearch() {
|
||||
UIUtils.attachSearch(binding.etSearchInventory, () -> loadInventory(true));
|
||||
}
|
||||
|
||||
/**
|
||||
* Configures the store filter spinner.
|
||||
*/
|
||||
private void setupStoreFilter() {
|
||||
SpinnerUtils.setupFilterSpinner(binding.spinnerStore, () -> loadInventory(true));
|
||||
}
|
||||
|
||||
/**
|
||||
* Fetches store data to populate the store filter.
|
||||
*/
|
||||
private void loadStoreData() {
|
||||
viewModel.getAllStores(0, 100).observe(getViewLifecycleOwner(), resource -> {
|
||||
if (resource.status == Resource.Status.SUCCESS && resource.data != null) {
|
||||
storeList = resource.data.getContent();
|
||||
SpinnerUtils.populateWhiteSpinner(requireContext(), binding.spinnerStore, storeList,
|
||||
StoreDTO::getStoreName, "All Stores", -1L, StoreDTO::getStoreId);
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* Initializes the RecyclerView with a layout manager, and adapter.
|
||||
*/
|
||||
private void setupRecyclerView() {
|
||||
adapter = new InventoryAdapter(inventoryList, this);
|
||||
binding.recyclerViewInventory.setLayoutManager(new LinearLayoutManager(getContext()));
|
||||
@@ -146,105 +128,45 @@ public class InventoryFragment extends Fragment implements InventoryAdapter.OnIn
|
||||
binding.recyclerViewInventory.addOnScrollListener(new RecyclerView.OnScrollListener() {
|
||||
@Override
|
||||
public void onScrolled(@NonNull RecyclerView recyclerView, int dx, int dy) {
|
||||
if (dy <= 0)
|
||||
return;
|
||||
if (dy <= 0) return;
|
||||
LinearLayoutManager lm = (LinearLayoutManager) binding.recyclerViewInventory.getLayoutManager();
|
||||
if (lm == null)
|
||||
return;
|
||||
if (lm == null) return;
|
||||
int visible = lm.getChildCount();
|
||||
int total = lm.getItemCount();
|
||||
int firstVis = lm.findFirstVisibleItemPosition();
|
||||
if (!isLoading && !isLastPage && (visible + firstVis) >= total - 3) {
|
||||
Boolean isLoading = viewModel.getIsLoading().getValue();
|
||||
if ((isLoading == null || !isLoading) && !viewModel.isLastPage() && (visible + firstVis) >= total - 3) {
|
||||
loadInventory(false);
|
||||
}
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* Sets up the SwipeRefreshLayout to reload the first page of inventory items.
|
||||
*/
|
||||
private void setupSwipeRefresh() {
|
||||
binding.swipeRefreshInventory.setOnRefreshListener(() -> loadInventory(true));
|
||||
}
|
||||
|
||||
/**
|
||||
* Fetches a page of inventory items from the API.
|
||||
*/
|
||||
private void loadInventory(boolean reset) {
|
||||
if (isLoading) return;
|
||||
|
||||
if (reset) {
|
||||
currentPage = 0;
|
||||
isLastPage = false;
|
||||
}
|
||||
|
||||
// Search text from input
|
||||
String query = binding.etSearchInventory != null ? binding.etSearchInventory.getText().toString().trim() : "";
|
||||
if (query.isEmpty()) query = null;
|
||||
|
||||
Long storeId = null;
|
||||
if (binding.spinnerStore.getSelectedItemPosition() > 0 && !storeList.isEmpty()) {
|
||||
storeId = storeList.get(binding.spinnerStore.getSelectedItemPosition() - 1).getStoreId();
|
||||
List<StoreDTO> stores = viewModel.getStores().getValue();
|
||||
if (binding.spinnerStore.getSelectedItemPosition() > 0 && stores != null && !stores.isEmpty()) {
|
||||
storeId = stores.get(binding.spinnerStore.getSelectedItemPosition() - 1).getStoreId();
|
||||
}
|
||||
|
||||
//Load all inventory items from the backend using viewModel
|
||||
viewModel.getAllInventory(query, storeId, currentPage, PAGE_SIZE, "product.prodName").observe(getViewLifecycleOwner(), resource -> {
|
||||
if (resource == null) return;
|
||||
|
||||
// Check the status to see if the resource is loaded and display the data
|
||||
switch (resource.status) {
|
||||
case LOADING:
|
||||
// Show loading indicator
|
||||
isLoading = true;
|
||||
binding.swipeRefreshInventory.setRefreshing(true);
|
||||
break;
|
||||
case SUCCESS:
|
||||
// Hide loading indicator and display data
|
||||
isLoading = false;
|
||||
binding.swipeRefreshInventory.setRefreshing(false);
|
||||
if (resource.data != null) {
|
||||
if (reset) inventoryList.clear();
|
||||
inventoryList.addAll(resource.data.getContent());
|
||||
adapter.notifyDataSetChanged();
|
||||
isLastPage = resource.data.isLast();
|
||||
if (!isLastPage) currentPage++;
|
||||
}
|
||||
break;
|
||||
case ERROR:
|
||||
// Hide loading indicator and toast error message
|
||||
isLoading = false;
|
||||
binding.swipeRefreshInventory.setRefreshing(false);
|
||||
Log.e(TAG, "Error: " + resource.message);
|
||||
Toast.makeText(getContext(), "Failed to load inventory: " + resource.message, Toast.LENGTH_SHORT).show();
|
||||
break;
|
||||
}
|
||||
});
|
||||
viewModel.loadInventory(reset, query, storeId);
|
||||
}
|
||||
|
||||
/**
|
||||
* Navigates to the inventory detail screen for a specific item or to add a new one.
|
||||
*/
|
||||
private void openDetail(InventoryDTO inv) {
|
||||
Bundle args = new Bundle();
|
||||
|
||||
if (inv != null) {
|
||||
args.putLong("inventoryId", inv.getInventoryId());
|
||||
}
|
||||
|
||||
NavHostFragment.findNavController(this).navigate(R.id.nav_inventory_detail, args);
|
||||
}
|
||||
|
||||
/**
|
||||
* Reloads inventory data when changes occur.
|
||||
*/
|
||||
public void onInventoryChanged() {
|
||||
loadInventory(true);
|
||||
}
|
||||
|
||||
/**
|
||||
* Handles item click in the inventory list.
|
||||
*/
|
||||
@Override
|
||||
public void onInventoryClick(int position) {
|
||||
if (position >= 0 && position < inventoryList.size()) {
|
||||
@@ -252,9 +174,6 @@ public class InventoryFragment extends Fragment implements InventoryAdapter.OnIn
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Updates the bulk deletion UI visibility and count when items are selected or deselected.
|
||||
*/
|
||||
@Override
|
||||
public void onSelectionChanged(int selectedCount) {
|
||||
if (bulkDeleteHandler != null) {
|
||||
|
||||
@@ -9,7 +9,6 @@ import androidx.lifecycle.ViewModelProvider;
|
||||
import androidx.navigation.fragment.NavHostFragment;
|
||||
import androidx.recyclerview.widget.LinearLayoutManager;
|
||||
|
||||
import android.util.Log;
|
||||
import android.view.LayoutInflater;
|
||||
import android.view.View;
|
||||
import android.view.ViewGroup;
|
||||
@@ -25,8 +24,7 @@ import com.example.petstoremobile.utils.BulkDeleteHandler;
|
||||
import com.example.petstoremobile.utils.Resource;
|
||||
import com.example.petstoremobile.utils.SpinnerUtils;
|
||||
import com.example.petstoremobile.utils.UIUtils;
|
||||
import com.example.petstoremobile.viewmodels.PetViewModel;
|
||||
import com.example.petstoremobile.viewmodels.StoreViewModel;
|
||||
import com.example.petstoremobile.viewmodels.PetListViewModel;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.List;
|
||||
@@ -40,28 +38,19 @@ import dagger.hilt.android.AndroidEntryPoint;
|
||||
public class PetFragment extends Fragment implements PetAdapter.OnPetClickListener {
|
||||
private FragmentPetBinding binding;
|
||||
private List<PetDTO> petList = new ArrayList<>();
|
||||
private List<StoreDTO> storeList = new ArrayList<>();
|
||||
private PetAdapter adapter;
|
||||
private PetViewModel viewModel;
|
||||
private StoreViewModel storeViewModel;
|
||||
private PetListViewModel viewModel;
|
||||
private BulkDeleteHandler bulkDeleteHandler;
|
||||
|
||||
@Inject @Named("baseUrl") String baseUrl;
|
||||
@Inject TokenManager tokenManager;
|
||||
|
||||
/**
|
||||
* Initializes the fragment and its associated ViewModels.
|
||||
*/
|
||||
@Override
|
||||
public void onCreate(@Nullable Bundle savedInstanceState) {
|
||||
super.onCreate(savedInstanceState);
|
||||
viewModel = new ViewModelProvider(this).get(PetViewModel.class);
|
||||
storeViewModel = new ViewModelProvider(this).get(StoreViewModel.class);
|
||||
viewModel = new ViewModelProvider(this).get(PetListViewModel.class);
|
||||
}
|
||||
|
||||
/**
|
||||
* Sets up the fragment's UI components, including RecyclerView, filters, and swipe-to-refresh.
|
||||
*/
|
||||
@Override
|
||||
public View onCreateView(@NonNull LayoutInflater inflater, ViewGroup container,
|
||||
Bundle savedInstanceState) {
|
||||
@@ -75,6 +64,7 @@ public class PetFragment extends Fragment implements PetAdapter.OnPetClickListen
|
||||
setupSwipeRefresh();
|
||||
setupFilterToggle();
|
||||
setupBulkDelete();
|
||||
observeViewModel();
|
||||
|
||||
binding.fabAddPet.setOnClickListener(v -> openPetDetails());
|
||||
|
||||
@@ -83,6 +73,23 @@ public class PetFragment extends Fragment implements PetAdapter.OnPetClickListen
|
||||
return binding.getRoot();
|
||||
}
|
||||
|
||||
private void observeViewModel() {
|
||||
viewModel.getPets().observe(getViewLifecycleOwner(), list -> {
|
||||
petList.clear();
|
||||
petList.addAll(list);
|
||||
adapter.notifyDataSetChanged();
|
||||
});
|
||||
|
||||
viewModel.getStores().observe(getViewLifecycleOwner(), list -> {
|
||||
SpinnerUtils.populateWhiteSpinner(requireContext(), binding.spinnerStore, list,
|
||||
StoreDTO::getStoreName, "All Stores", -1L, StoreDTO::getStoreId);
|
||||
});
|
||||
|
||||
viewModel.getIsLoading().observe(getViewLifecycleOwner(), loading -> {
|
||||
binding.swipeRefreshPet.setRefreshing(loading);
|
||||
});
|
||||
}
|
||||
|
||||
private void setupBulkDelete() {
|
||||
bulkDeleteHandler = new BulkDeleteHandler(
|
||||
this,
|
||||
@@ -96,83 +103,62 @@ public class PetFragment extends Fragment implements PetAdapter.OnPetClickListen
|
||||
);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onDestroyView() {
|
||||
super.onDestroyView();
|
||||
binding = null;
|
||||
}
|
||||
|
||||
/**
|
||||
* Reloads data every time the fragment becomes visible.
|
||||
*/
|
||||
@Override
|
||||
public void onResume() {
|
||||
super.onResume();
|
||||
loadPetData();
|
||||
loadStoreData();
|
||||
viewModel.loadStores();
|
||||
}
|
||||
|
||||
/**
|
||||
* Sets up the filter toggle button to show/hide the filter layout.
|
||||
*/
|
||||
private void setupFilterToggle() {
|
||||
UIUtils.setupFilterToggle(binding.btnToggleFilter, binding.layoutFilter, binding.etSearchPet,
|
||||
binding.spinnerStatus, binding.spinnerSpecies, binding.spinnerStore);
|
||||
}
|
||||
|
||||
/**
|
||||
* Configures the search bar.
|
||||
*/
|
||||
private void setupSearch() {
|
||||
UIUtils.attachSearch(binding.etSearchPet, this::loadPetData);
|
||||
}
|
||||
|
||||
/**
|
||||
* Configures the status filter spinner.
|
||||
*/
|
||||
private void setupStatusFilter() {
|
||||
String[] statuses = {"All Statuses", "Available", "Adopted", "Owned"};
|
||||
SpinnerUtils.setupStringFilterSpinner(requireContext(), binding.spinnerStatus, statuses, this::loadPetData);
|
||||
}
|
||||
|
||||
/**
|
||||
* Configures the species filter spinner with species.
|
||||
*/
|
||||
private void setupSpeciesFilter() {
|
||||
String[] species = {"All Species", "Dog", "Cat", "Bird", "Rabbit", "Fish", "Hamster"};
|
||||
SpinnerUtils.setupStringFilterSpinner(requireContext(), binding.spinnerSpecies, species, this::loadPetData);
|
||||
}
|
||||
|
||||
/**
|
||||
* Configures the store filter spinner.
|
||||
*/
|
||||
private void setupStoreFilter() {
|
||||
SpinnerUtils.setupFilterSpinner(binding.spinnerStore, this::loadPetData);
|
||||
}
|
||||
|
||||
/**
|
||||
* Fetches store data to populate the store filter.
|
||||
*/
|
||||
private void loadStoreData() {
|
||||
storeViewModel.getAllStores(0, 100).observe(getViewLifecycleOwner(), resource -> {
|
||||
if (resource.status == Resource.Status.SUCCESS && resource.data != null) {
|
||||
storeList = resource.data.getContent();
|
||||
SpinnerUtils.populateWhiteSpinner(requireContext(), binding.spinnerStore, storeList,
|
||||
StoreDTO::getStoreName, "All Stores", -1L, StoreDTO::getStoreId);
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* Sets up the SwipeRefreshLayout.
|
||||
*/
|
||||
private void setupSwipeRefresh() {
|
||||
binding.swipeRefreshPet.setOnRefreshListener(this::loadPetData);
|
||||
}
|
||||
|
||||
/**
|
||||
* Navigates to the pet profile screen.
|
||||
*/
|
||||
private void loadPetData() {
|
||||
String query = binding.etSearchPet.getText().toString().trim();
|
||||
String status = binding.spinnerStatus.getSelectedItem() != null ? binding.spinnerStatus.getSelectedItem().toString() : "All Statuses";
|
||||
String species = binding.spinnerSpecies.getSelectedItem() != null ? binding.spinnerSpecies.getSelectedItem().toString() : "All Species";
|
||||
|
||||
Long storeId = null;
|
||||
List<StoreDTO> stores = viewModel.getStores().getValue();
|
||||
if (binding.spinnerStore.getSelectedItemPosition() > 0 && stores != null && !stores.isEmpty()) {
|
||||
storeId = stores.get(binding.spinnerStore.getSelectedItemPosition() - 1).getStoreId();
|
||||
}
|
||||
|
||||
viewModel.loadPets(query, status, species, storeId);
|
||||
}
|
||||
|
||||
private void setupRecyclerView() {
|
||||
adapter = new PetAdapter(petList, this);
|
||||
adapter.setBaseUrl(baseUrl);
|
||||
adapter.setToken(tokenManager.getToken());
|
||||
binding.recyclerViewPets.setLayoutManager(new LinearLayoutManager(getContext()));
|
||||
binding.recyclerViewPets.setAdapter(adapter);
|
||||
}
|
||||
|
||||
private void openPetProfile(int position) {
|
||||
Bundle args = new Bundle();
|
||||
PetDTO pet = petList.get(position);
|
||||
@@ -180,9 +166,6 @@ public class PetFragment extends Fragment implements PetAdapter.OnPetClickListen
|
||||
NavHostFragment.findNavController(this).navigate(R.id.nav_pet_profile, args);
|
||||
}
|
||||
|
||||
/**
|
||||
* Navigates to the pet detail screen.
|
||||
*/
|
||||
private void openPetDetails() {
|
||||
NavHostFragment.findNavController(this).navigate(R.id.nav_pet_detail);
|
||||
}
|
||||
@@ -199,54 +182,9 @@ public class PetFragment extends Fragment implements PetAdapter.OnPetClickListen
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Fetches pet data from the server with all active filters.
|
||||
*/
|
||||
private void loadPetData() {
|
||||
String query = binding.etSearchPet.getText().toString().trim();
|
||||
String status = binding.spinnerStatus.getSelectedItem() != null ? binding.spinnerStatus.getSelectedItem().toString() : "All Statuses";
|
||||
String species = binding.spinnerSpecies.getSelectedItem() != null ? binding.spinnerSpecies.getSelectedItem().toString() : "All Species";
|
||||
|
||||
Long storeId = null;
|
||||
if (binding.spinnerStore.getSelectedItemPosition() > 0 && !storeList.isEmpty()) {
|
||||
storeId = storeList.get(binding.spinnerStore.getSelectedItemPosition() - 1).getStoreId();
|
||||
}
|
||||
|
||||
if (status.equals("All Statuses")) status = null;
|
||||
if (species.equals("All Species")) species = null;
|
||||
|
||||
viewModel.getAllPets(0, 100, query, status, species, storeId, "petName").observe(getViewLifecycleOwner(), resource -> {
|
||||
if (resource == null) return;
|
||||
|
||||
switch (resource.status) {
|
||||
case LOADING:
|
||||
binding.swipeRefreshPet.setRefreshing(true);
|
||||
break;
|
||||
case SUCCESS:
|
||||
binding.swipeRefreshPet.setRefreshing(false);
|
||||
if (resource.data != null) {
|
||||
petList.clear();
|
||||
petList.addAll(resource.data.getContent());
|
||||
adapter.notifyDataSetChanged();
|
||||
}
|
||||
break;
|
||||
case ERROR:
|
||||
binding.swipeRefreshPet.setRefreshing(false);
|
||||
Toast.makeText(getContext(), "Failed to load pets: " + resource.message, Toast.LENGTH_SHORT).show();
|
||||
Log.e("PetFragment", "Error loading pets: " + resource.message);
|
||||
break;
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* Initializes the RecyclerView.
|
||||
*/
|
||||
private void setupRecyclerView() {
|
||||
adapter = new PetAdapter(petList, this);
|
||||
adapter.setBaseUrl(baseUrl);
|
||||
adapter.setToken(tokenManager.getToken());
|
||||
binding.recyclerViewPets.setLayoutManager(new LinearLayoutManager(getContext()));
|
||||
binding.recyclerViewPets.setAdapter(adapter);
|
||||
@Override
|
||||
public void onDestroyView() {
|
||||
super.onDestroyView();
|
||||
binding = null;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -9,21 +9,18 @@ import androidx.lifecycle.ViewModelProvider;
|
||||
import androidx.navigation.fragment.NavHostFragment;
|
||||
import androidx.recyclerview.widget.LinearLayoutManager;
|
||||
|
||||
import android.util.Log;
|
||||
import android.view.LayoutInflater;
|
||||
import android.view.View;
|
||||
import android.view.ViewGroup;
|
||||
import android.widget.Toast;
|
||||
|
||||
import com.example.petstoremobile.R;
|
||||
import com.example.petstoremobile.adapters.ProductAdapter;
|
||||
import com.example.petstoremobile.databinding.FragmentProductBinding;
|
||||
import com.example.petstoremobile.dtos.CategoryDTO;
|
||||
import com.example.petstoremobile.dtos.ProductDTO;
|
||||
import com.example.petstoremobile.utils.Resource;
|
||||
import com.example.petstoremobile.utils.SpinnerUtils;
|
||||
import com.example.petstoremobile.utils.UIUtils;
|
||||
import com.example.petstoremobile.viewmodels.ProductViewModel;
|
||||
import com.example.petstoremobile.viewmodels.ProductListViewModel;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.List;
|
||||
@@ -38,24 +35,17 @@ public class ProductFragment extends Fragment implements ProductAdapter.OnProduc
|
||||
|
||||
private FragmentProductBinding binding;
|
||||
private List<ProductDTO> productList = new ArrayList<>();
|
||||
private List<CategoryDTO> categoryList = new ArrayList<>();
|
||||
private ProductAdapter adapter;
|
||||
private ProductViewModel viewModel;
|
||||
private ProductListViewModel viewModel;
|
||||
|
||||
@Inject @Named("baseUrl") String baseUrl;
|
||||
|
||||
/**
|
||||
* Initializes the fragment and its associated ProductViewModel.
|
||||
*/
|
||||
@Override
|
||||
public void onCreate(@Nullable Bundle savedInstanceState) {
|
||||
super.onCreate(savedInstanceState);
|
||||
viewModel = new ViewModelProvider(this).get(ProductViewModel.class);
|
||||
viewModel = new ViewModelProvider(this).get(ProductListViewModel.class);
|
||||
}
|
||||
|
||||
/**
|
||||
* Sets up the fragment's UI components, including RecyclerView, search, and swipe-to-refresh.
|
||||
*/
|
||||
@Override
|
||||
public View onCreateView(@NonNull LayoutInflater inflater, ViewGroup container,
|
||||
Bundle savedInstanceState) {
|
||||
@@ -66,6 +56,7 @@ public class ProductFragment extends Fragment implements ProductAdapter.OnProduc
|
||||
setupCategoryFilter();
|
||||
setupSwipeRefresh();
|
||||
setupFilterToggle();
|
||||
observeViewModel();
|
||||
|
||||
binding.fabAddProduct.setOnClickListener(v -> openProductDetails(-1));
|
||||
|
||||
@@ -74,67 +65,67 @@ public class ProductFragment extends Fragment implements ProductAdapter.OnProduc
|
||||
return binding.getRoot();
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onDestroyView() {
|
||||
super.onDestroyView();
|
||||
binding = null;
|
||||
private void observeViewModel() {
|
||||
viewModel.getProducts().observe(getViewLifecycleOwner(), list -> {
|
||||
productList.clear();
|
||||
productList.addAll(list);
|
||||
adapter.notifyDataSetChanged();
|
||||
});
|
||||
|
||||
viewModel.getCategories().observe(getViewLifecycleOwner(), list -> {
|
||||
SpinnerUtils.populateWhiteSpinner(requireContext(), binding.spinnerCategory, list,
|
||||
CategoryDTO::getCategoryName, "All Categories", -1L, CategoryDTO::getCategoryId);
|
||||
});
|
||||
|
||||
viewModel.getIsLoading().observe(getViewLifecycleOwner(), loading -> {
|
||||
binding.swipeRefreshProduct.setRefreshing(loading);
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* Reloads data every time the fragment becomes visible.
|
||||
*/
|
||||
@Override
|
||||
public void onResume() {
|
||||
super.onResume();
|
||||
loadProductData();
|
||||
loadCategoryData();
|
||||
viewModel.loadCategories();
|
||||
}
|
||||
|
||||
/**
|
||||
* Sets up the filter toggle button to show/hide the filter layout.
|
||||
*/
|
||||
private void setupFilterToggle() {
|
||||
UIUtils.setupFilterToggle(binding.btnToggleFilter, binding.layoutFilter,
|
||||
binding.etSearchProduct, binding.spinnerCategory);
|
||||
}
|
||||
|
||||
/**
|
||||
* Configures the search bar for triggering data load from backend.
|
||||
*/
|
||||
private void setupSearch() {
|
||||
UIUtils.attachSearch(binding.etSearchProduct, this::loadProductData);
|
||||
}
|
||||
|
||||
/**
|
||||
* Configures the category filter spinner.
|
||||
*/
|
||||
private void setupCategoryFilter() {
|
||||
SpinnerUtils.setupFilterSpinner(binding.spinnerCategory, this::loadProductData);
|
||||
}
|
||||
|
||||
/**
|
||||
* Fetches category data to populate the category filter.
|
||||
*/
|
||||
private void loadCategoryData() {
|
||||
viewModel.getAllCategories(0, 100).observe(getViewLifecycleOwner(), resource -> {
|
||||
if (resource.status == Resource.Status.SUCCESS && resource.data != null) {
|
||||
categoryList = resource.data.getContent();
|
||||
SpinnerUtils.populateWhiteSpinner(requireContext(), binding.spinnerCategory, categoryList,
|
||||
CategoryDTO::getCategoryName, "All Categories", -1L, CategoryDTO::getCategoryId);
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* Sets up the SwipeRefreshLayout.
|
||||
*/
|
||||
private void setupSwipeRefresh() {
|
||||
binding.swipeRefreshProduct.setOnRefreshListener(this::loadProductData);
|
||||
}
|
||||
|
||||
/**
|
||||
* Navigates to the product detail screen.
|
||||
*/
|
||||
private void loadProductData() {
|
||||
String query = binding.etSearchProduct.getText().toString().trim();
|
||||
if (query.isEmpty()) query = null;
|
||||
|
||||
Long categoryId = null;
|
||||
List<CategoryDTO> categories = viewModel.getCategories().getValue();
|
||||
if (binding.spinnerCategory.getSelectedItemPosition() > 0 && categories != null && !categories.isEmpty()) {
|
||||
categoryId = categories.get(binding.spinnerCategory.getSelectedItemPosition() - 1).getCategoryId();
|
||||
}
|
||||
|
||||
viewModel.loadProducts(query, categoryId);
|
||||
}
|
||||
|
||||
private void setupRecyclerView() {
|
||||
adapter = new ProductAdapter(productList, this);
|
||||
adapter.setBaseUrl(baseUrl);
|
||||
binding.recyclerViewProducts.setLayoutManager(new LinearLayoutManager(getContext()));
|
||||
binding.recyclerViewProducts.setAdapter(adapter);
|
||||
}
|
||||
|
||||
private void openProductDetails(int position) {
|
||||
Bundle args = new Bundle();
|
||||
if (position != -1) {
|
||||
@@ -149,51 +140,9 @@ public class ProductFragment extends Fragment implements ProductAdapter.OnProduc
|
||||
openProductDetails(position);
|
||||
}
|
||||
|
||||
/**
|
||||
* Fetches product data from the server with search query, category, and sorting.
|
||||
*/
|
||||
private void loadProductData() {
|
||||
String query = binding.etSearchProduct.getText().toString().trim();
|
||||
if (query.isEmpty()) query = null;
|
||||
|
||||
Long categoryId = null;
|
||||
if (binding.spinnerCategory.getSelectedItemPosition() > 0 && !categoryList.isEmpty()) {
|
||||
categoryId = categoryList.get(binding.spinnerCategory.getSelectedItemPosition() - 1).getCategoryId();
|
||||
}
|
||||
|
||||
viewModel.getAllProducts(query, categoryId, 0, 100, "prodName").observe(getViewLifecycleOwner(), resource -> {
|
||||
if (resource == null) return;
|
||||
|
||||
switch (resource.status) {
|
||||
case LOADING:
|
||||
binding.swipeRefreshProduct.setRefreshing(true);
|
||||
break;
|
||||
case SUCCESS:
|
||||
binding.swipeRefreshProduct.setRefreshing(false);
|
||||
if (resource.data != null) {
|
||||
productList.clear();
|
||||
productList.addAll(resource.data.getContent());
|
||||
adapter.notifyDataSetChanged();
|
||||
}
|
||||
break;
|
||||
case ERROR:
|
||||
binding.swipeRefreshProduct.setRefreshing(false);
|
||||
if (getContext() != null) {
|
||||
Toast.makeText(getContext(), "Failed to load products: " + resource.message, Toast.LENGTH_SHORT).show();
|
||||
}
|
||||
Log.e("ProductFragment", "Error loading products: " + resource.message);
|
||||
break;
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* Initializes the RecyclerView.
|
||||
*/
|
||||
private void setupRecyclerView() {
|
||||
adapter = new ProductAdapter(productList, this);
|
||||
adapter.setBaseUrl(baseUrl);
|
||||
binding.recyclerViewProducts.setLayoutManager(new LinearLayoutManager(getContext()));
|
||||
binding.recyclerViewProducts.setAdapter(adapter);
|
||||
@Override
|
||||
public void onDestroyView() {
|
||||
super.onDestroyView();
|
||||
binding = null;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,11 +1,9 @@
|
||||
package com.example.petstoremobile.fragments.listfragments;
|
||||
|
||||
import android.os.Bundle;
|
||||
import android.util.Log;
|
||||
import android.view.LayoutInflater;
|
||||
import android.view.View;
|
||||
import android.view.ViewGroup;
|
||||
import android.widget.Toast;
|
||||
|
||||
import androidx.annotation.NonNull;
|
||||
import androidx.annotation.Nullable;
|
||||
@@ -21,12 +19,9 @@ import com.example.petstoremobile.dtos.ProductDTO;
|
||||
import com.example.petstoremobile.dtos.ProductSupplierDTO;
|
||||
import com.example.petstoremobile.dtos.SupplierDTO;
|
||||
import com.example.petstoremobile.utils.BulkDeleteHandler;
|
||||
import com.example.petstoremobile.utils.Resource;
|
||||
import com.example.petstoremobile.utils.SpinnerUtils;
|
||||
import com.example.petstoremobile.utils.UIUtils;
|
||||
import com.example.petstoremobile.viewmodels.ProductSupplierViewModel;
|
||||
import com.example.petstoremobile.viewmodels.ProductViewModel;
|
||||
import com.example.petstoremobile.viewmodels.SupplierViewModel;
|
||||
import com.example.petstoremobile.viewmodels.ProductSupplierListViewModel;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.List;
|
||||
@@ -39,29 +34,17 @@ public class ProductSupplierFragment extends Fragment
|
||||
|
||||
private FragmentProductSupplierBinding binding;
|
||||
private List<ProductSupplierDTO> psList = new ArrayList<>();
|
||||
private List<ProductDTO> productList = new ArrayList<>();
|
||||
private List<SupplierDTO> supplierList = new ArrayList<>();
|
||||
|
||||
private ProductSupplierAdapter adapter;
|
||||
private ProductSupplierViewModel viewModel;
|
||||
private ProductViewModel productViewModel;
|
||||
private SupplierViewModel supplierViewModel;
|
||||
private ProductSupplierListViewModel viewModel;
|
||||
private BulkDeleteHandler bulkDeleteHandler;
|
||||
|
||||
/**
|
||||
* Initializes the fragment and its associated ViewModels.
|
||||
*/
|
||||
@Override
|
||||
public void onCreate(@Nullable Bundle savedInstanceState) {
|
||||
super.onCreate(savedInstanceState);
|
||||
viewModel = new ViewModelProvider(this).get(ProductSupplierViewModel.class);
|
||||
productViewModel = new ViewModelProvider(this).get(ProductViewModel.class);
|
||||
supplierViewModel = new ViewModelProvider(this).get(SupplierViewModel.class);
|
||||
viewModel = new ViewModelProvider(this).get(ProductSupplierListViewModel.class);
|
||||
}
|
||||
|
||||
/**
|
||||
* Sets up the fragment's UI components, including the RecyclerView, search, and swipe-to-refresh.
|
||||
*/
|
||||
@Override
|
||||
public View onCreateView(@NonNull LayoutInflater inflater, ViewGroup container,
|
||||
Bundle savedInstanceState) {
|
||||
@@ -74,6 +57,7 @@ public class ProductSupplierFragment extends Fragment
|
||||
setupSwipeRefresh();
|
||||
setupFilterToggle();
|
||||
setupBulkDelete();
|
||||
observeViewModel();
|
||||
|
||||
binding.fabAddPS.setOnClickListener(v -> openDetail(-1));
|
||||
|
||||
@@ -82,6 +66,28 @@ public class ProductSupplierFragment extends Fragment
|
||||
return binding.getRoot();
|
||||
}
|
||||
|
||||
private void observeViewModel() {
|
||||
viewModel.getProductSuppliers().observe(getViewLifecycleOwner(), list -> {
|
||||
psList.clear();
|
||||
psList.addAll(list);
|
||||
adapter.notifyDataSetChanged();
|
||||
});
|
||||
|
||||
viewModel.getProducts().observe(getViewLifecycleOwner(), list -> {
|
||||
SpinnerUtils.populateWhiteSpinner(requireContext(), binding.spinnerProduct, list,
|
||||
ProductDTO::getProdName, "All Products", -1L, ProductDTO::getProdId);
|
||||
});
|
||||
|
||||
viewModel.getSuppliers().observe(getViewLifecycleOwner(), list -> {
|
||||
SpinnerUtils.populateWhiteSpinner(requireContext(), binding.spinnerSupplier, list,
|
||||
SupplierDTO::getSupCompany, "All Suppliers", -1L, SupplierDTO::getSupId);
|
||||
});
|
||||
|
||||
viewModel.getIsLoading().observe(getViewLifecycleOwner(), loading -> {
|
||||
binding.swipeRefreshPS.setRefreshing(loading);
|
||||
});
|
||||
}
|
||||
|
||||
private void setupBulkDelete() {
|
||||
bulkDeleteHandler = new BulkDeleteHandler(
|
||||
this,
|
||||
@@ -95,136 +101,65 @@ public class ProductSupplierFragment extends Fragment
|
||||
);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onResume() {
|
||||
super.onResume();
|
||||
loadData();
|
||||
viewModel.loadFilterData();
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onDestroyView() {
|
||||
super.onDestroyView();
|
||||
binding = null;
|
||||
}
|
||||
|
||||
/**
|
||||
* Reloads data every time the fragment becomes visible.
|
||||
*/
|
||||
@Override
|
||||
public void onResume() {
|
||||
super.onResume();
|
||||
loadData();
|
||||
loadFilterData();
|
||||
}
|
||||
|
||||
/**
|
||||
* Sets up the filter toggle button to show/hide the filter layout.
|
||||
*/
|
||||
private void setupFilterToggle() {
|
||||
UIUtils.setupFilterToggle(binding.btnToggleFilter, binding.layoutFilter, binding.etSearchPS,
|
||||
binding.spinnerProduct, binding.spinnerSupplier);
|
||||
}
|
||||
|
||||
/**
|
||||
* Initializes the RecyclerView with a layout manager and adapter for product-supplier data.
|
||||
*/
|
||||
private void setupRecyclerView() {
|
||||
adapter = new ProductSupplierAdapter(psList, this);
|
||||
binding.recyclerViewPS.setLayoutManager(new LinearLayoutManager(getContext()));
|
||||
binding.recyclerViewPS.setAdapter(adapter);
|
||||
}
|
||||
|
||||
/**
|
||||
* Configures the search bar for filtering.
|
||||
*/
|
||||
private void setupSearch() {
|
||||
UIUtils.attachSearch(binding.etSearchPS, this::loadData);
|
||||
}
|
||||
|
||||
/**
|
||||
* Configures the product filter spinner.
|
||||
*/
|
||||
private void setupProductFilter() {
|
||||
SpinnerUtils.setupFilterSpinner(binding.spinnerProduct, this::loadData);
|
||||
}
|
||||
|
||||
/**
|
||||
* Configures the supplier filter spinner.
|
||||
*/
|
||||
private void setupSupplierFilter() {
|
||||
SpinnerUtils.setupFilterSpinner(binding.spinnerSupplier, this::loadData);
|
||||
}
|
||||
|
||||
/**
|
||||
* Fetches products and suppliers to populate the filters.
|
||||
*/
|
||||
private void loadFilterData() {
|
||||
productViewModel.getAllProducts(null, null, 0, 100, "prodName").observe(getViewLifecycleOwner(), resource -> {
|
||||
if (resource.status == Resource.Status.SUCCESS && resource.data != null) {
|
||||
productList = resource.data.getContent();
|
||||
SpinnerUtils.populateWhiteSpinner(requireContext(), binding.spinnerProduct, productList,
|
||||
ProductDTO::getProdName, "All Products", -1L, ProductDTO::getProdId);
|
||||
}
|
||||
});
|
||||
|
||||
supplierViewModel.getAllSuppliers(0, 100, null, "supCompany").observe(getViewLifecycleOwner(), resource -> {
|
||||
if (resource.status == Resource.Status.SUCCESS && resource.data != null) {
|
||||
supplierList = resource.data.getContent();
|
||||
SpinnerUtils.populateWhiteSpinner(requireContext(), binding.spinnerSupplier, supplierList,
|
||||
SupplierDTO::getSupCompany, "All Suppliers", -1L, SupplierDTO::getSupId);
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* Sets up the SwipeRefreshLayout to allow manual reloading of product-supplier data.
|
||||
*/
|
||||
private void setupSwipeRefresh() {
|
||||
binding.swipeRefreshPS.setOnRefreshListener(this::loadData);
|
||||
}
|
||||
|
||||
/**
|
||||
* Fetches product-supplier data from the server through the ViewModel with search query and filters.
|
||||
*/
|
||||
private void loadData() {
|
||||
String query = binding.etSearchPS.getText().toString().trim();
|
||||
if (query.isEmpty()) query = null;
|
||||
|
||||
Long productId = null;
|
||||
if (binding.spinnerProduct.getSelectedItemPosition() > 0 && !productList.isEmpty()) {
|
||||
productId = productList.get(binding.spinnerProduct.getSelectedItemPosition() - 1).getProdId();
|
||||
List<ProductDTO> products = viewModel.getProducts().getValue();
|
||||
if (binding.spinnerProduct.getSelectedItemPosition() > 0 && products != null && !products.isEmpty()) {
|
||||
productId = products.get(binding.spinnerProduct.getSelectedItemPosition() - 1).getProdId();
|
||||
}
|
||||
|
||||
Long supplierId = null;
|
||||
if (binding.spinnerSupplier.getSelectedItemPosition() > 0 && !supplierList.isEmpty()) {
|
||||
supplierId = supplierList.get(binding.spinnerSupplier.getSelectedItemPosition() - 1).getSupId();
|
||||
List<SupplierDTO> suppliers = viewModel.getSuppliers().getValue();
|
||||
if (binding.spinnerSupplier.getSelectedItemPosition() > 0 && suppliers != null && !suppliers.isEmpty()) {
|
||||
supplierId = suppliers.get(binding.spinnerSupplier.getSelectedItemPosition() - 1).getSupId();
|
||||
}
|
||||
|
||||
viewModel.getAllProductSuppliers(0, 100, query, productId, supplierId, "productName").observe(getViewLifecycleOwner(), resource -> {
|
||||
if (resource == null) return;
|
||||
|
||||
// Check the status to see if the resource is loaded and display the data
|
||||
switch (resource.status) {
|
||||
case LOADING:
|
||||
// Show loading indicator
|
||||
binding.swipeRefreshPS.setRefreshing(true);
|
||||
break;
|
||||
case SUCCESS:
|
||||
// Hide loading indicator and display data
|
||||
binding.swipeRefreshPS.setRefreshing(false);
|
||||
if (resource.data != null) {
|
||||
psList.clear();
|
||||
psList.addAll(resource.data.getContent());
|
||||
adapter.notifyDataSetChanged();
|
||||
}
|
||||
break;
|
||||
case ERROR:
|
||||
// Hide loading indicator and toast error message
|
||||
binding.swipeRefreshPS.setRefreshing(false);
|
||||
Toast.makeText(getContext(), "Failed to load: " + resource.message, Toast.LENGTH_SHORT).show();
|
||||
Log.e("PSFragment", "Error loading: " + resource.message);
|
||||
break;
|
||||
}
|
||||
});
|
||||
viewModel.loadProductSuppliers(query, productId, supplierId);
|
||||
}
|
||||
|
||||
/**
|
||||
* Navigates to the product-supplier detail screen for a specific item or to add a new record.
|
||||
*/
|
||||
private void openDetail(int position) {
|
||||
Bundle args = new Bundle();
|
||||
if (position != -1) {
|
||||
@@ -235,9 +170,6 @@ public class ProductSupplierFragment extends Fragment
|
||||
NavHostFragment.findNavController(this).navigate(R.id.nav_product_supplier_detail, args);
|
||||
}
|
||||
|
||||
/**
|
||||
* Handles item click in the product-supplier list.
|
||||
*/
|
||||
@Override
|
||||
public void onProductSupplierClick(int position) { openDetail(position); }
|
||||
|
||||
|
||||
@@ -1,11 +1,9 @@
|
||||
package com.example.petstoremobile.fragments.listfragments;
|
||||
|
||||
import android.os.Bundle;
|
||||
import android.util.Log;
|
||||
import android.view.LayoutInflater;
|
||||
import android.view.View;
|
||||
import android.view.ViewGroup;
|
||||
import android.widget.Toast;
|
||||
|
||||
import androidx.annotation.NonNull;
|
||||
import androidx.annotation.Nullable;
|
||||
@@ -19,11 +17,9 @@ import com.example.petstoremobile.adapters.PurchaseOrderAdapter;
|
||||
import com.example.petstoremobile.databinding.FragmentPurchaseOrderBinding;
|
||||
import com.example.petstoremobile.dtos.PurchaseOrderDTO;
|
||||
import com.example.petstoremobile.dtos.StoreDTO;
|
||||
import com.example.petstoremobile.utils.Resource;
|
||||
import com.example.petstoremobile.utils.SpinnerUtils;
|
||||
import com.example.petstoremobile.utils.UIUtils;
|
||||
import com.example.petstoremobile.viewmodels.PurchaseOrderViewModel;
|
||||
import com.example.petstoremobile.viewmodels.StoreViewModel;
|
||||
import com.example.petstoremobile.viewmodels.PurchaseOrderListViewModel;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.List;
|
||||
@@ -36,24 +32,15 @@ public class PurchaseOrderFragment extends Fragment
|
||||
|
||||
private FragmentPurchaseOrderBinding binding;
|
||||
private List<PurchaseOrderDTO> poList = new ArrayList<>();
|
||||
private List<StoreDTO> storeList = new ArrayList<>();
|
||||
private PurchaseOrderAdapter adapter;
|
||||
private PurchaseOrderViewModel viewModel;
|
||||
private StoreViewModel storeViewModel;
|
||||
private PurchaseOrderListViewModel viewModel;
|
||||
|
||||
/**
|
||||
* Initializes the fragment and its associated ViewModels.
|
||||
*/
|
||||
@Override
|
||||
public void onCreate(@Nullable Bundle savedInstanceState) {
|
||||
super.onCreate(savedInstanceState);
|
||||
viewModel = new ViewModelProvider(this).get(PurchaseOrderViewModel.class);
|
||||
storeViewModel = new ViewModelProvider(this).get(StoreViewModel.class);
|
||||
viewModel = new ViewModelProvider(this).get(PurchaseOrderListViewModel.class);
|
||||
}
|
||||
|
||||
/**
|
||||
* Sets up the fragment's UI components, including RecyclerView, filters, and swipe-to-refresh.
|
||||
*/
|
||||
@Override
|
||||
public View onCreateView(@NonNull LayoutInflater inflater, ViewGroup container,
|
||||
Bundle savedInstanceState) {
|
||||
@@ -64,121 +51,72 @@ public class PurchaseOrderFragment extends Fragment
|
||||
setupStoreFilter();
|
||||
setupSwipeRefresh();
|
||||
setupFilterToggle();
|
||||
observeViewModel();
|
||||
|
||||
UIUtils.setupHamburgerMenu(binding.btnHamburgerPO, this);
|
||||
|
||||
return binding.getRoot();
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onDestroyView() {
|
||||
super.onDestroyView();
|
||||
binding = null;
|
||||
private void observeViewModel() {
|
||||
viewModel.getPurchaseOrders().observe(getViewLifecycleOwner(), list -> {
|
||||
poList.clear();
|
||||
poList.addAll(list);
|
||||
adapter.notifyDataSetChanged();
|
||||
});
|
||||
|
||||
viewModel.getStores().observe(getViewLifecycleOwner(), list -> {
|
||||
SpinnerUtils.populateWhiteSpinner(requireContext(), binding.spinnerStore, list,
|
||||
StoreDTO::getStoreName, "All Stores", -1L, StoreDTO::getStoreId);
|
||||
});
|
||||
|
||||
viewModel.getIsLoading().observe(getViewLifecycleOwner(), loading -> {
|
||||
binding.swipeRefreshPO.setRefreshing(loading);
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* Reloads data every time the fragment becomes visible.
|
||||
*/
|
||||
@Override
|
||||
public void onResume() {
|
||||
super.onResume();
|
||||
loadData();
|
||||
loadStoreData();
|
||||
viewModel.loadStores();
|
||||
}
|
||||
|
||||
/**
|
||||
* Sets up the filter toggle button to show/hide the filter layout.
|
||||
*/
|
||||
private void setupFilterToggle() {
|
||||
UIUtils.setupFilterToggle(binding.btnToggleFilter, binding.layoutFilter, binding.etSearchPO, binding.spinnerStore);
|
||||
}
|
||||
|
||||
/**
|
||||
* Configures the search bar for filtering.
|
||||
*/
|
||||
private void setupSearch() {
|
||||
UIUtils.attachSearch(binding.etSearchPO, this::loadData);
|
||||
}
|
||||
|
||||
/**
|
||||
* Configures the store filter spinner.
|
||||
*/
|
||||
private void setupStoreFilter() {
|
||||
SpinnerUtils.setupFilterSpinner(binding.spinnerStore, this::loadData);
|
||||
}
|
||||
|
||||
/**
|
||||
* Fetches store data to populate the store filter.
|
||||
*/
|
||||
private void loadStoreData() {
|
||||
storeViewModel.getAllStores(0, 100).observe(getViewLifecycleOwner(), resource -> {
|
||||
if (resource.status == Resource.Status.SUCCESS && resource.data != null) {
|
||||
storeList = resource.data.getContent();
|
||||
SpinnerUtils.populateWhiteSpinner(requireContext(), binding.spinnerStore, storeList,
|
||||
StoreDTO::getStoreName, "All Stores", -1L, StoreDTO::getStoreId);
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* Initializes the RecyclerView with a layout manager and adapter.
|
||||
*/
|
||||
private void setupRecyclerView() {
|
||||
adapter = new PurchaseOrderAdapter(poList, this);
|
||||
binding.recyclerViewPO.setLayoutManager(new LinearLayoutManager(getContext()));
|
||||
binding.recyclerViewPO.setAdapter(adapter);
|
||||
}
|
||||
|
||||
/**
|
||||
* Sets up the SwipeRefreshLayout to allow manual reloading of purchase order data.
|
||||
*/
|
||||
private void setupSwipeRefresh() {
|
||||
binding.swipeRefreshPO.setOnRefreshListener(this::loadData);
|
||||
}
|
||||
|
||||
/**
|
||||
* Fetches purchase order data from the server with active filters and updates the UI.
|
||||
*/
|
||||
private void loadData() {
|
||||
String query = binding.etSearchPO != null ? binding.etSearchPO.getText().toString().trim() : "";
|
||||
if (query.isEmpty()) query = null;
|
||||
|
||||
Long storeId = null;
|
||||
if (binding.spinnerStore.getSelectedItemPosition() > 0 && !storeList.isEmpty()) {
|
||||
storeId = storeList.get(binding.spinnerStore.getSelectedItemPosition() - 1).getStoreId();
|
||||
List<StoreDTO> stores = viewModel.getStores().getValue();
|
||||
if (binding.spinnerStore.getSelectedItemPosition() > 0 && stores != null && !stores.isEmpty()) {
|
||||
storeId = stores.get(binding.spinnerStore.getSelectedItemPosition() - 1).getStoreId();
|
||||
}
|
||||
|
||||
viewModel.getAllPurchaseOrders(0, 100, query, storeId, "purchaseOrderId,desc").observe(getViewLifecycleOwner(), resource -> {
|
||||
if (resource == null) return;
|
||||
|
||||
// Check the status to see if the resource is loaded and display the data
|
||||
switch (resource.status) {
|
||||
case LOADING:
|
||||
// Show loading indicator
|
||||
binding.swipeRefreshPO.setRefreshing(true);
|
||||
break;
|
||||
case SUCCESS:
|
||||
// Hide loading indicator and display data
|
||||
binding.swipeRefreshPO.setRefreshing(false);
|
||||
if (resource.data != null) {
|
||||
poList.clear();
|
||||
poList.addAll(resource.data.getContent());
|
||||
adapter.notifyDataSetChanged();
|
||||
}
|
||||
break;
|
||||
case ERROR:
|
||||
// Hide loading indicator and toast error message
|
||||
binding.swipeRefreshPO.setRefreshing(false);
|
||||
Toast.makeText(getContext(), "Failed to load purchase orders: " + resource.message, Toast.LENGTH_SHORT).show();
|
||||
Log.e("POFragment", "Error loading purchase orders: " + resource.message);
|
||||
break;
|
||||
}
|
||||
});
|
||||
viewModel.loadPurchaseOrders(query, storeId);
|
||||
}
|
||||
|
||||
/**
|
||||
* Navigates to the purchase order detail screen for a specific record.
|
||||
*/
|
||||
private void openDetail(int position) {
|
||||
Bundle args = new Bundle();
|
||||
PurchaseOrderDTO po = poList.get(position);
|
||||
@@ -186,11 +124,14 @@ public class PurchaseOrderFragment extends Fragment
|
||||
NavHostFragment.findNavController(this).navigate(R.id.nav_purchase_order_detail, args);
|
||||
}
|
||||
|
||||
/**
|
||||
* Handles item click in the purchase order list.
|
||||
*/
|
||||
@Override
|
||||
public void onPurchaseOrderClick(int position) {
|
||||
openDetail(position);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onDestroyView() {
|
||||
super.onDestroyView();
|
||||
binding = null;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -8,7 +8,6 @@ import androidx.lifecycle.ViewModelProvider;
|
||||
import androidx.navigation.fragment.NavHostFragment;
|
||||
import androidx.recyclerview.widget.LinearLayoutManager;
|
||||
import androidx.recyclerview.widget.RecyclerView;
|
||||
import android.util.Log;
|
||||
import android.view.LayoutInflater;
|
||||
import android.view.View;
|
||||
import android.view.ViewGroup;
|
||||
@@ -19,11 +18,9 @@ import com.example.petstoremobile.adapters.SaleAdapter;
|
||||
import com.example.petstoremobile.databinding.FragmentSaleBinding;
|
||||
import com.example.petstoremobile.dtos.SaleDTO;
|
||||
import com.example.petstoremobile.dtos.StoreDTO;
|
||||
import com.example.petstoremobile.utils.Resource;
|
||||
import com.example.petstoremobile.utils.SpinnerUtils;
|
||||
import com.example.petstoremobile.utils.UIUtils;
|
||||
import com.example.petstoremobile.viewmodels.SaleViewModel;
|
||||
import com.example.petstoremobile.viewmodels.StoreViewModel;
|
||||
import com.example.petstoremobile.viewmodels.SaleListViewModel;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.List;
|
||||
@@ -33,20 +30,10 @@ import dagger.hilt.android.AndroidEntryPoint;
|
||||
@AndroidEntryPoint
|
||||
public class SaleFragment extends Fragment implements SaleAdapter.OnSaleClickListener {
|
||||
|
||||
private static final String TAG = "SaleFragment";
|
||||
private static final int PAGE_SIZE = 200;
|
||||
|
||||
private FragmentSaleBinding binding;
|
||||
private final List<SaleDTO> saleList = new ArrayList<>();
|
||||
private final List<StoreDTO> storeList = new ArrayList<>();
|
||||
private SaleAdapter adapter;
|
||||
private SaleViewModel saleViewModel;
|
||||
private StoreViewModel storeViewModel;
|
||||
|
||||
// Pagination
|
||||
private int currentPage = 0;
|
||||
private boolean isLastPage = false;
|
||||
private boolean isLoading = false;
|
||||
private SaleListViewModel viewModel;
|
||||
|
||||
@Override
|
||||
public View onCreateView(@NonNull LayoutInflater inflater, ViewGroup container,
|
||||
@@ -58,8 +45,7 @@ public class SaleFragment extends Fragment implements SaleAdapter.OnSaleClickLis
|
||||
@Override
|
||||
public void onViewCreated(@NonNull View view, @Nullable Bundle savedInstanceState) {
|
||||
super.onViewCreated(view, savedInstanceState);
|
||||
saleViewModel = new ViewModelProvider(this).get(SaleViewModel.class);
|
||||
storeViewModel = new ViewModelProvider(this).get(StoreViewModel.class);
|
||||
viewModel = new ViewModelProvider(this).get(SaleListViewModel.class);
|
||||
|
||||
setupRecyclerView();
|
||||
setupSearch();
|
||||
@@ -67,6 +53,8 @@ public class SaleFragment extends Fragment implements SaleAdapter.OnSaleClickLis
|
||||
setupPaymentMethodFilter();
|
||||
setupSwipeRefresh();
|
||||
setupFilterToggle();
|
||||
observeViewModel();
|
||||
|
||||
loadSales(true);
|
||||
|
||||
UIUtils.setupHamburgerMenu(binding.btnHamburger, this);
|
||||
@@ -78,10 +66,27 @@ public class SaleFragment extends Fragment implements SaleAdapter.OnSaleClickLis
|
||||
NavHostFragment.findNavController(this).navigate(R.id.nav_refund));
|
||||
}
|
||||
|
||||
private void observeViewModel() {
|
||||
viewModel.getSales().observe(getViewLifecycleOwner(), list -> {
|
||||
saleList.clear();
|
||||
saleList.addAll(list);
|
||||
adapter.notifyDataSetChanged();
|
||||
});
|
||||
|
||||
viewModel.getStores().observe(getViewLifecycleOwner(), list -> {
|
||||
SpinnerUtils.populateWhiteSpinner(requireContext(), binding.spinnerStore, list,
|
||||
StoreDTO::getStoreName, "Stores", null, StoreDTO::getStoreId);
|
||||
});
|
||||
|
||||
viewModel.getIsLoading().observe(getViewLifecycleOwner(), loading -> {
|
||||
binding.swipeRefreshSale.setRefreshing(loading);
|
||||
});
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onResume() {
|
||||
super.onResume();
|
||||
loadStoreData();
|
||||
viewModel.loadStores();
|
||||
}
|
||||
|
||||
private void setupFilterToggle() {
|
||||
@@ -93,28 +98,11 @@ public class SaleFragment extends Fragment implements SaleAdapter.OnSaleClickLis
|
||||
SpinnerUtils.setupFilterSpinner(binding.spinnerStore, () -> loadSales(true));
|
||||
}
|
||||
|
||||
private void loadStoreData() {
|
||||
storeViewModel.getAllStores(0, 100).observe(getViewLifecycleOwner(), resource -> {
|
||||
if (resource != null && resource.status == Resource.Status.SUCCESS && resource.data != null) {
|
||||
storeList.clear();
|
||||
storeList.addAll(resource.data.getContent());
|
||||
SpinnerUtils.populateWhiteSpinner(requireContext(), binding.spinnerStore, storeList,
|
||||
StoreDTO::getStoreName, "Stores", null, StoreDTO::getStoreId);
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
private void setupPaymentMethodFilter() {
|
||||
String[] paymentMethods = {"Payments", "Cash", "Card"};
|
||||
SpinnerUtils.setupStringFilterSpinner(requireContext(), binding.spinnerPaymentMethod, paymentMethods, () -> loadSales(true));
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onDestroyView() {
|
||||
super.onDestroyView();
|
||||
binding = null;
|
||||
}
|
||||
|
||||
private void setupRecyclerView() {
|
||||
adapter = new SaleAdapter(saleList, this);
|
||||
binding.recyclerViewSales.setLayoutManager(new LinearLayoutManager(getContext()));
|
||||
@@ -129,7 +117,8 @@ public class SaleFragment extends Fragment implements SaleAdapter.OnSaleClickLis
|
||||
int visible = lm.getChildCount();
|
||||
int total = lm.getItemCount();
|
||||
int firstVis = lm.findFirstVisibleItemPosition();
|
||||
if (!isLoading && !isLastPage && (visible + firstVis) >= total - 3) {
|
||||
Boolean isLoading = viewModel.getIsLoading().getValue();
|
||||
if ((isLoading == null || !isLoading) && !viewModel.isLastPage() && (visible + firstVis) >= total - 3) {
|
||||
loadSales(false);
|
||||
}
|
||||
}
|
||||
@@ -146,13 +135,6 @@ public class SaleFragment extends Fragment implements SaleAdapter.OnSaleClickLis
|
||||
}
|
||||
|
||||
private void loadSales(boolean reset) {
|
||||
if (isLoading) return;
|
||||
|
||||
if (reset) {
|
||||
currentPage = 0;
|
||||
isLastPage = false;
|
||||
}
|
||||
|
||||
String query = binding.etSearchSale != null ? binding.etSearchSale.getText().toString().trim() : "";
|
||||
if (query.isEmpty()) query = null;
|
||||
|
||||
@@ -162,39 +144,12 @@ public class SaleFragment extends Fragment implements SaleAdapter.OnSaleClickLis
|
||||
}
|
||||
|
||||
Long storeId = null;
|
||||
if (binding.spinnerStore.getSelectedItemPosition() > 0 && !storeList.isEmpty()) {
|
||||
storeId = storeList.get(binding.spinnerStore.getSelectedItemPosition() - 1).getStoreId();
|
||||
List<StoreDTO> stores = viewModel.getStores().getValue();
|
||||
if (binding.spinnerStore.getSelectedItemPosition() > 0 && stores != null && !stores.isEmpty()) {
|
||||
storeId = stores.get(binding.spinnerStore.getSelectedItemPosition() - 1).getStoreId();
|
||||
}
|
||||
|
||||
saleViewModel.getAllSales(currentPage, PAGE_SIZE, query, paymentMethod, storeId, "saleDate,desc").observe(getViewLifecycleOwner(), resource -> {
|
||||
if (resource == null) return;
|
||||
|
||||
switch (resource.status) {
|
||||
case LOADING:
|
||||
isLoading = true;
|
||||
binding.swipeRefreshSale.setRefreshing(true);
|
||||
break;
|
||||
case SUCCESS:
|
||||
isLoading = false;
|
||||
binding.swipeRefreshSale.setRefreshing(false);
|
||||
if (resource.data != null) {
|
||||
if (reset) saleList.clear();
|
||||
saleList.addAll(resource.data.getContent());
|
||||
adapter.notifyDataSetChanged();
|
||||
isLastPage = resource.data.isLast();
|
||||
if (!isLastPage) currentPage++;
|
||||
}
|
||||
break;
|
||||
case ERROR:
|
||||
isLoading = false;
|
||||
binding.swipeRefreshSale.setRefreshing(false);
|
||||
Log.e(TAG, "Error loading sales: " + resource.message);
|
||||
if (getContext() != null) {
|
||||
Toast.makeText(getContext(), "Failed to load sales: " + resource.message, Toast.LENGTH_SHORT).show();
|
||||
}
|
||||
break;
|
||||
}
|
||||
});
|
||||
viewModel.loadSales(reset, query, paymentMethod, storeId);
|
||||
}
|
||||
|
||||
@Override
|
||||
@@ -210,4 +165,10 @@ public class SaleFragment extends Fragment implements SaleAdapter.OnSaleClickLis
|
||||
}
|
||||
NavHostFragment.findNavController(this).navigate(R.id.nav_sale_detail, args);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onDestroyView() {
|
||||
super.onDestroyView();
|
||||
binding = null;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,7 +1,6 @@
|
||||
package com.example.petstoremobile.fragments.listfragments;
|
||||
|
||||
import android.os.Bundle;
|
||||
import android.util.Log;
|
||||
import android.view.LayoutInflater;
|
||||
import android.view.View;
|
||||
import android.view.ViewGroup;
|
||||
@@ -21,7 +20,7 @@ import com.example.petstoremobile.databinding.FragmentServiceBinding;
|
||||
import com.example.petstoremobile.dtos.ServiceDTO;
|
||||
import com.example.petstoremobile.utils.BulkDeleteHandler;
|
||||
import com.example.petstoremobile.utils.UIUtils;
|
||||
import com.example.petstoremobile.viewmodels.ServiceViewModel;
|
||||
import com.example.petstoremobile.viewmodels.ServiceListViewModel;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.List;
|
||||
@@ -34,32 +33,18 @@ import dagger.hilt.android.AndroidEntryPoint;
|
||||
@AndroidEntryPoint
|
||||
public class ServiceFragment extends Fragment implements ServiceAdapter.OnServiceClickListener {
|
||||
|
||||
private static final String TAG = "ServiceFragment";
|
||||
private static final int PAGE_SIZE = 20;
|
||||
|
||||
private FragmentServiceBinding binding;
|
||||
private final List<ServiceDTO> serviceList = new ArrayList<>();
|
||||
private ServiceAdapter adapter;
|
||||
private ServiceViewModel viewModel;
|
||||
private ServiceListViewModel viewModel;
|
||||
private BulkDeleteHandler bulkDeleteHandler;
|
||||
|
||||
// Pagination
|
||||
private int currentPage = 0;
|
||||
private boolean isLastPage = false;
|
||||
private boolean isLoading = false;
|
||||
|
||||
/**
|
||||
* Initializes the fragment and its associated ViewModel.
|
||||
*/
|
||||
@Override
|
||||
public void onCreate(@Nullable Bundle savedInstanceState) {
|
||||
super.onCreate(savedInstanceState);
|
||||
viewModel = new ViewModelProvider(this).get(ServiceViewModel.class);
|
||||
viewModel = new ViewModelProvider(this).get(ServiceListViewModel.class);
|
||||
}
|
||||
|
||||
/**
|
||||
* Sets up the fragment's UI components, including RecyclerView, search, and swipe-to-refresh.
|
||||
*/
|
||||
@Override
|
||||
public View onCreateView(@NonNull LayoutInflater inflater, ViewGroup container,
|
||||
Bundle savedInstanceState) {
|
||||
@@ -70,15 +55,27 @@ public class ServiceFragment extends Fragment implements ServiceAdapter.OnServic
|
||||
setupSwipeRefresh();
|
||||
setupFilterToggle();
|
||||
setupBulkDelete();
|
||||
observeViewModel();
|
||||
|
||||
loadServices(true);
|
||||
|
||||
binding.fabAddService.setOnClickListener(v -> openDetail(null));
|
||||
|
||||
UIUtils.setupHamburgerMenu(binding.btnHamburger, this);
|
||||
|
||||
return binding.getRoot();
|
||||
}
|
||||
|
||||
private void observeViewModel() {
|
||||
viewModel.getServices().observe(getViewLifecycleOwner(), list -> {
|
||||
serviceList.clear();
|
||||
serviceList.addAll(list);
|
||||
adapter.notifyDataSetChanged();
|
||||
});
|
||||
|
||||
viewModel.getIsLoading().observe(getViewLifecycleOwner(), loading -> {
|
||||
binding.swipeRefreshService.setRefreshing(loading);
|
||||
});
|
||||
}
|
||||
|
||||
private void setupBulkDelete() {
|
||||
bulkDeleteHandler = new BulkDeleteHandler(
|
||||
this,
|
||||
@@ -98,23 +95,14 @@ public class ServiceFragment extends Fragment implements ServiceAdapter.OnServic
|
||||
binding = null;
|
||||
}
|
||||
|
||||
/**
|
||||
* Sets up the filter toggle button to show/hide the filter layout.
|
||||
*/
|
||||
private void setupFilterToggle() {
|
||||
UIUtils.setupFilterToggle(binding.btnToggleFilter, binding.layoutFilter, binding.etSearchService);
|
||||
}
|
||||
|
||||
/**
|
||||
* Sets up the search bar for filtering.
|
||||
*/
|
||||
private void setupSearch() {
|
||||
UIUtils.attachSearch(binding.etSearchService, () -> loadServices(true));
|
||||
}
|
||||
|
||||
/**
|
||||
* Initializes the RecyclerView with a layout manager and adapter.
|
||||
*/
|
||||
private void setupRecyclerView() {
|
||||
adapter = new ServiceAdapter(serviceList, this);
|
||||
binding.recyclerViewServices.setLayoutManager(new LinearLayoutManager(getContext()));
|
||||
@@ -129,66 +117,24 @@ public class ServiceFragment extends Fragment implements ServiceAdapter.OnServic
|
||||
int visible = lm.getChildCount();
|
||||
int total = lm.getItemCount();
|
||||
int firstVis = lm.findFirstVisibleItemPosition();
|
||||
if (!isLoading && !isLastPage && (visible + firstVis) >= total - 3) {
|
||||
Boolean isLoading = viewModel.getIsLoading().getValue();
|
||||
if ((isLoading == null || !isLoading) && !viewModel.isLastPage() && (visible + firstVis) >= total - 3) {
|
||||
loadServices(false);
|
||||
}
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* Sets up the SwipeRefreshLayout.
|
||||
*/
|
||||
private void setupSwipeRefresh() {
|
||||
binding.swipeRefreshService.setOnRefreshListener(() -> loadServices(true));
|
||||
}
|
||||
|
||||
/**
|
||||
* Fetches a page of services from the API.
|
||||
*/
|
||||
private void loadServices(boolean reset) {
|
||||
if (isLoading) return;
|
||||
|
||||
if (reset) {
|
||||
currentPage = 0;
|
||||
isLastPage = false;
|
||||
}
|
||||
|
||||
String query = binding.etSearchService.getText().toString().trim();
|
||||
if (query.isEmpty()) query = null;
|
||||
|
||||
viewModel.getAllServices(currentPage, PAGE_SIZE, query, "serviceName").observe(getViewLifecycleOwner(), resource -> {
|
||||
if (resource == null) return;
|
||||
|
||||
switch (resource.status) {
|
||||
case LOADING:
|
||||
isLoading = true;
|
||||
binding.swipeRefreshService.setRefreshing(true);
|
||||
break;
|
||||
case SUCCESS:
|
||||
isLoading = false;
|
||||
binding.swipeRefreshService.setRefreshing(false);
|
||||
if (resource.data != null) {
|
||||
if (reset) serviceList.clear();
|
||||
serviceList.addAll(resource.data.getContent());
|
||||
adapter.notifyDataSetChanged();
|
||||
isLastPage = resource.data.isLast();
|
||||
if (!isLastPage) currentPage++;
|
||||
}
|
||||
break;
|
||||
case ERROR:
|
||||
isLoading = false;
|
||||
binding.swipeRefreshService.setRefreshing(false);
|
||||
Log.e(TAG, "Error: " + resource.message);
|
||||
Toast.makeText(getContext(), "Failed to load services: " + resource.message, Toast.LENGTH_SHORT).show();
|
||||
break;
|
||||
}
|
||||
});
|
||||
viewModel.loadServices(reset, query);
|
||||
}
|
||||
|
||||
/**
|
||||
* Navigates to the service detail screen.
|
||||
*/
|
||||
private void openDetail(ServiceDTO service) {
|
||||
Bundle args = new Bundle();
|
||||
if (service != null) {
|
||||
|
||||
@@ -1,7 +1,6 @@
|
||||
package com.example.petstoremobile.fragments.listfragments;
|
||||
|
||||
import android.os.Bundle;
|
||||
import android.util.Log;
|
||||
import android.view.*;
|
||||
import android.widget.*;
|
||||
import androidx.annotation.NonNull;
|
||||
@@ -14,7 +13,7 @@ import com.example.petstoremobile.adapters.EmployeeAdapter;
|
||||
import com.example.petstoremobile.databinding.FragmentStaffBinding;
|
||||
import com.example.petstoremobile.dtos.EmployeeDTO;
|
||||
import com.example.petstoremobile.utils.UIUtils;
|
||||
import com.example.petstoremobile.viewmodels.EmployeeViewModel;
|
||||
import com.example.petstoremobile.viewmodels.StaffListViewModel;
|
||||
import dagger.hilt.android.AndroidEntryPoint;
|
||||
import java.util.*;
|
||||
|
||||
@@ -22,21 +21,22 @@ import java.util.*;
|
||||
public class StaffFragment extends Fragment implements EmployeeAdapter.OnEmployeeClickListener {
|
||||
|
||||
private FragmentStaffBinding binding;
|
||||
private EmployeeViewModel employeeViewModel;
|
||||
private List<EmployeeDTO> employeeList = new ArrayList<>();
|
||||
private List<EmployeeDTO> filteredList = new ArrayList<>();
|
||||
private StaffListViewModel viewModel;
|
||||
private List<EmployeeDTO> staffList = new ArrayList<>();
|
||||
private EmployeeAdapter adapter;
|
||||
|
||||
@Override
|
||||
public View onCreateView(@NonNull LayoutInflater inflater, ViewGroup container,
|
||||
Bundle savedInstanceState) {
|
||||
binding = FragmentStaffBinding.inflate(inflater, container, false);
|
||||
employeeViewModel = new ViewModelProvider(this).get(EmployeeViewModel.class);
|
||||
viewModel = new ViewModelProvider(this).get(StaffListViewModel.class);
|
||||
|
||||
setupRecyclerView();
|
||||
setupSearch();
|
||||
setupSwipeRefresh();
|
||||
loadStaff();
|
||||
observeViewModel();
|
||||
|
||||
viewModel.loadStaff();
|
||||
|
||||
binding.fabAddStaff.setOnClickListener(v -> openDetail(-1));
|
||||
|
||||
@@ -46,70 +46,36 @@ public class StaffFragment extends Fragment implements EmployeeAdapter.OnEmploye
|
||||
return binding.getRoot();
|
||||
}
|
||||
|
||||
private void observeViewModel() {
|
||||
viewModel.getFilteredEmployees().observe(getViewLifecycleOwner(), list -> {
|
||||
staffList.clear();
|
||||
staffList.addAll(list);
|
||||
adapter.notifyDataSetChanged();
|
||||
});
|
||||
|
||||
viewModel.getIsLoading().observe(getViewLifecycleOwner(), loading -> {
|
||||
binding.swipeRefreshStaff.setRefreshing(loading);
|
||||
});
|
||||
}
|
||||
|
||||
private void setupRecyclerView() {
|
||||
adapter = new EmployeeAdapter(filteredList, this);
|
||||
adapter = new EmployeeAdapter(staffList, this);
|
||||
binding.recyclerViewStaff.setLayoutManager(new LinearLayoutManager(getContext()));
|
||||
binding.recyclerViewStaff.setAdapter(adapter);
|
||||
}
|
||||
|
||||
private void setupSearch() {
|
||||
UIUtils.attachSearch(binding.etSearchStaff, () -> filter(binding.etSearchStaff.getText().toString()));
|
||||
UIUtils.attachSearch(binding.etSearchStaff, () -> viewModel.filter(binding.etSearchStaff.getText().toString()));
|
||||
}
|
||||
|
||||
private void setupSwipeRefresh() {
|
||||
binding.swipeRefreshStaff.setOnRefreshListener(this::loadStaff);
|
||||
}
|
||||
|
||||
private void filter(String query) {
|
||||
filteredList.clear();
|
||||
if (query.isEmpty()) {
|
||||
filteredList.addAll(employeeList);
|
||||
} else {
|
||||
String lower = query.toLowerCase();
|
||||
for (EmployeeDTO e : employeeList) {
|
||||
if ((e.getFullName() != null && e.getFullName().toLowerCase().contains(lower))
|
||||
|| (e.getUsername() != null && e.getUsername().toLowerCase().contains(lower))
|
||||
|| (e.getEmail() != null && e.getEmail().toLowerCase().contains(lower))
|
||||
|| (e.getPhone() != null && e.getPhone().toLowerCase().contains(lower))) {
|
||||
filteredList.add(e);
|
||||
}
|
||||
}
|
||||
}
|
||||
adapter.notifyDataSetChanged();
|
||||
}
|
||||
|
||||
private void loadStaff() {
|
||||
binding.swipeRefreshStaff.setRefreshing(true);
|
||||
employeeViewModel.getAllEmployees(0, 100).observe(getViewLifecycleOwner(), resource -> {
|
||||
if (resource != null) {
|
||||
switch (resource.status) {
|
||||
case SUCCESS:
|
||||
binding.swipeRefreshStaff.setRefreshing(false);
|
||||
if (resource.data != null) {
|
||||
employeeList.clear();
|
||||
employeeList.addAll(resource.data.getContent());
|
||||
filter(binding != null ? binding.etSearchStaff.getText().toString() : "");
|
||||
}
|
||||
break;
|
||||
case ERROR:
|
||||
binding.swipeRefreshStaff.setRefreshing(false);
|
||||
if (getContext() != null) {
|
||||
Toast.makeText(getContext(), resource.message != null ? resource.message : "Failed to load staff",
|
||||
Toast.LENGTH_SHORT).show();
|
||||
}
|
||||
break;
|
||||
case LOADING:
|
||||
binding.swipeRefreshStaff.setRefreshing(true);
|
||||
break;
|
||||
}
|
||||
}
|
||||
});
|
||||
binding.swipeRefreshStaff.setOnRefreshListener(viewModel::loadStaff);
|
||||
}
|
||||
|
||||
private void openDetail(int position) {
|
||||
Bundle args = new Bundle();
|
||||
if (position != -1) {
|
||||
EmployeeDTO e = filteredList.get(position);
|
||||
EmployeeDTO e = staffList.get(position);
|
||||
args.putLong("employeeId", e.getEmployeeId());
|
||||
args.putString("username", e.getUsername() != null ? e.getUsername() : "");
|
||||
args.putString("firstName", e.getFirstName() != null ? e.getFirstName() : "");
|
||||
|
||||
@@ -9,20 +9,17 @@ import androidx.lifecycle.ViewModelProvider;
|
||||
import androidx.navigation.fragment.NavHostFragment;
|
||||
import androidx.recyclerview.widget.LinearLayoutManager;
|
||||
|
||||
import android.util.Log;
|
||||
import android.view.LayoutInflater;
|
||||
import android.view.View;
|
||||
import android.view.ViewGroup;
|
||||
import android.widget.Toast;
|
||||
|
||||
import com.example.petstoremobile.R;
|
||||
import com.example.petstoremobile.adapters.SupplierAdapter;
|
||||
import com.example.petstoremobile.databinding.FragmentSupplierBinding;
|
||||
import com.example.petstoremobile.dtos.SupplierDTO;
|
||||
import com.example.petstoremobile.utils.BulkDeleteHandler;
|
||||
import com.example.petstoremobile.utils.Resource;
|
||||
import com.example.petstoremobile.utils.UIUtils;
|
||||
import com.example.petstoremobile.viewmodels.SupplierViewModel;
|
||||
import com.example.petstoremobile.viewmodels.SupplierListViewModel;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.List;
|
||||
@@ -35,21 +32,15 @@ public class SupplierFragment extends Fragment implements SupplierAdapter.OnSupp
|
||||
private FragmentSupplierBinding binding;
|
||||
private List<SupplierDTO> supplierList = new ArrayList<>();
|
||||
private SupplierAdapter adapter;
|
||||
private SupplierViewModel viewModel;
|
||||
private SupplierListViewModel viewModel;
|
||||
private BulkDeleteHandler bulkDeleteHandler;
|
||||
|
||||
/**
|
||||
* Initializes the fragment and its associated SupplierViewModel.
|
||||
*/
|
||||
@Override
|
||||
public void onCreate(@Nullable Bundle savedInstanceState) {
|
||||
super.onCreate(savedInstanceState);
|
||||
viewModel = new ViewModelProvider(this).get(SupplierViewModel.class);
|
||||
viewModel = new ViewModelProvider(this).get(SupplierListViewModel.class);
|
||||
}
|
||||
|
||||
/**
|
||||
* Sets up the fragment's UI components, including RecyclerView, search, and swipe-to-refresh.
|
||||
*/
|
||||
@Override
|
||||
public View onCreateView(@NonNull LayoutInflater inflater, ViewGroup container,
|
||||
Bundle savedInstanceState) {
|
||||
@@ -60,9 +51,10 @@ public class SupplierFragment extends Fragment implements SupplierAdapter.OnSupp
|
||||
setupSwipeRefresh();
|
||||
setupFilterToggle();
|
||||
setupBulkDelete();
|
||||
observeViewModel();
|
||||
|
||||
loadSupplierData();
|
||||
|
||||
//Add button to opens the add dialog
|
||||
binding.fabAddSupplier.setOnClickListener(v -> openSupplierDetails(-1));
|
||||
|
||||
UIUtils.setupHamburgerMenu(binding.btnHamburger, this);
|
||||
@@ -70,6 +62,18 @@ public class SupplierFragment extends Fragment implements SupplierAdapter.OnSupp
|
||||
return binding.getRoot();
|
||||
}
|
||||
|
||||
private void observeViewModel() {
|
||||
viewModel.getSuppliers().observe(getViewLifecycleOwner(), list -> {
|
||||
supplierList.clear();
|
||||
supplierList.addAll(list);
|
||||
adapter.notifyDataSetChanged();
|
||||
});
|
||||
|
||||
viewModel.getIsLoading().observe(getViewLifecycleOwner(), loading -> {
|
||||
binding.swipeRefreshSupplier.setRefreshing(loading);
|
||||
});
|
||||
}
|
||||
|
||||
private void setupBulkDelete() {
|
||||
bulkDeleteHandler = new BulkDeleteHandler(
|
||||
this,
|
||||
@@ -89,47 +93,27 @@ public class SupplierFragment extends Fragment implements SupplierAdapter.OnSupp
|
||||
binding = null;
|
||||
}
|
||||
|
||||
/**
|
||||
* Sets up the filter toggle button to show/hide the filter layout.
|
||||
*/
|
||||
private void setupFilterToggle() {
|
||||
UIUtils.setupFilterToggle(binding.btnToggleFilter, binding.layoutFilter, binding.etSearchSupplier);
|
||||
}
|
||||
|
||||
/**
|
||||
* Configures the search bar for filtering.
|
||||
*/
|
||||
private void setupSearch() {
|
||||
UIUtils.attachSearch(binding.etSearchSupplier, this::loadSupplierData);
|
||||
}
|
||||
|
||||
/**
|
||||
* Sets up the SwipeRefreshLayout to allow manual reloading of supplier data.
|
||||
*/
|
||||
private void setupSwipeRefresh() {
|
||||
binding.swipeRefreshSupplier.setOnRefreshListener(this::loadSupplierData);
|
||||
}
|
||||
|
||||
/**
|
||||
* Navigates to the supplier detail screen for editing an existing record or adding a new one.
|
||||
*/
|
||||
private void openSupplierDetails(int position) {
|
||||
//Make a bundle to pass data to the detail fragment
|
||||
Bundle args = new Bundle();
|
||||
|
||||
//if editing a supplier, add the supplier id to the bundle
|
||||
if (position != -1) {
|
||||
SupplierDTO supplier = supplierList.get(position);
|
||||
args.putLong("supId", supplier.getSupId());
|
||||
}
|
||||
|
||||
NavHostFragment.findNavController(this).navigate(R.id.nav_supplier_detail, args);
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Handles item click in the supplier list.
|
||||
*/
|
||||
@Override
|
||||
public void onSupplierClick(int position) {
|
||||
openSupplierDetails(position);
|
||||
@@ -142,47 +126,12 @@ public class SupplierFragment extends Fragment implements SupplierAdapter.OnSupp
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Fetches all supplier data from the server through the ViewModel and updates the UI.
|
||||
*/
|
||||
private void loadSupplierData() {
|
||||
String query = binding.etSearchSupplier != null ? binding.etSearchSupplier.getText().toString().trim() : "";
|
||||
if (query.isEmpty()) query = null;
|
||||
|
||||
//Load suppliers from the backend with query and default sort
|
||||
viewModel.getAllSuppliers(0, 100, query, "supCompany").observe(getViewLifecycleOwner(), resource -> {
|
||||
if (resource == null) return;
|
||||
|
||||
// Check the status to see if the resource is loaded and display the data
|
||||
switch (resource.status) {
|
||||
case LOADING:
|
||||
// Show loading indicator
|
||||
binding.swipeRefreshSupplier.setRefreshing(true);
|
||||
break;
|
||||
case SUCCESS:
|
||||
// Hide loading indicator and display data
|
||||
binding.swipeRefreshSupplier.setRefreshing(false);
|
||||
if (resource.data != null) {
|
||||
supplierList.clear();
|
||||
supplierList.addAll(resource.data.getContent());
|
||||
adapter.notifyDataSetChanged();
|
||||
}
|
||||
break;
|
||||
case ERROR:
|
||||
// Hide loading indicator and toast error message
|
||||
binding.swipeRefreshSupplier.setRefreshing(false);
|
||||
if (getContext() != null) {
|
||||
Toast.makeText(getContext(), "Failed to load suppliers: " + resource.message, Toast.LENGTH_SHORT).show();
|
||||
}
|
||||
Log.e("SupplierFragment", "Error loading suppliers: " + resource.message);
|
||||
break;
|
||||
}
|
||||
});
|
||||
viewModel.loadSuppliers(query);
|
||||
}
|
||||
|
||||
/**
|
||||
* Initializes the RecyclerView with a layout manager and adapter for displaying suppliers.
|
||||
*/
|
||||
private void setupRecyclerView() {
|
||||
adapter = new SupplierAdapter(supplierList, this);
|
||||
binding.recyclerViewSuppliers.setLayoutManager(new LinearLayoutManager(getContext()));
|
||||
|
||||
@@ -1,6 +1,5 @@
|
||||
package com.example.petstoremobile.fragments.listfragments.detailfragments;
|
||||
|
||||
import android.app.DatePickerDialog;
|
||||
import android.os.Bundle;
|
||||
import android.view.*;
|
||||
import android.widget.*;
|
||||
@@ -12,14 +11,13 @@ import androidx.navigation.fragment.NavHostFragment;
|
||||
|
||||
import com.example.petstoremobile.databinding.FragmentAdoptionDetailBinding;
|
||||
import com.example.petstoremobile.dtos.*;
|
||||
import com.example.petstoremobile.utils.DateTimeUtils;
|
||||
import com.example.petstoremobile.utils.DialogUtils;
|
||||
import com.example.petstoremobile.utils.InputValidator;
|
||||
import com.example.petstoremobile.utils.Resource;
|
||||
import com.example.petstoremobile.utils.SpinnerUtils;
|
||||
import com.example.petstoremobile.viewmodels.AdoptionViewModel;
|
||||
import com.example.petstoremobile.viewmodels.CustomerViewModel;
|
||||
import com.example.petstoremobile.viewmodels.PetViewModel;
|
||||
import com.example.petstoremobile.viewmodels.StoreViewModel;
|
||||
import com.example.petstoremobile.viewmodels.UserViewModel;
|
||||
import com.example.petstoremobile.utils.UIUtils;
|
||||
import com.example.petstoremobile.viewmodels.AdoptionDetailViewModel;
|
||||
|
||||
import java.math.BigDecimal;
|
||||
import java.util.*;
|
||||
@@ -33,35 +31,19 @@ import dagger.hilt.android.AndroidEntryPoint;
|
||||
public class AdoptionDetailFragment extends Fragment {
|
||||
|
||||
private FragmentAdoptionDetailBinding binding;
|
||||
private AdoptionDetailViewModel viewModel;
|
||||
|
||||
private long adoptionId = -1;
|
||||
private boolean isEditing = false;
|
||||
private long preselectedPetId = -1;
|
||||
private long preselectedCustomerId = -1;
|
||||
private long preselectedStoreId = -1;
|
||||
private long preselectedEmployeeId = -1;
|
||||
|
||||
private List<PetDTO> petList = new ArrayList<>();
|
||||
private List<CustomerDTO> customerList = new ArrayList<>();
|
||||
private List<StoreDTO> storeList = new ArrayList<>();
|
||||
private List<UserDTO> employeeList = new ArrayList<>();
|
||||
|
||||
private final String[] STATUSES = {"Pending", "Completed", "Cancelled"};
|
||||
|
||||
private AdoptionViewModel adoptionViewModel;
|
||||
private PetViewModel petViewModel;
|
||||
private CustomerViewModel customerViewModel;
|
||||
private StoreViewModel storeViewModel;
|
||||
private UserViewModel userViewModel;
|
||||
|
||||
@Override
|
||||
public void onCreate(Bundle savedInstanceState) {
|
||||
super.onCreate(savedInstanceState);
|
||||
adoptionViewModel = new ViewModelProvider(this).get(AdoptionViewModel.class);
|
||||
petViewModel = new ViewModelProvider(this).get(PetViewModel.class);
|
||||
customerViewModel = new ViewModelProvider(this).get(CustomerViewModel.class);
|
||||
storeViewModel = new ViewModelProvider(this).get(StoreViewModel.class);
|
||||
userViewModel = new ViewModelProvider(this).get(UserViewModel.class);
|
||||
viewModel = new ViewModelProvider(this).get(AdoptionDetailViewModel.class);
|
||||
}
|
||||
|
||||
@Override
|
||||
@@ -76,6 +58,7 @@ public class AdoptionDetailFragment extends Fragment {
|
||||
super.onViewCreated(view, savedInstanceState);
|
||||
setupSpinners();
|
||||
setupDatePicker();
|
||||
observeViewModel();
|
||||
loadSpinnersData();
|
||||
handleArguments();
|
||||
|
||||
@@ -84,155 +67,146 @@ public class AdoptionDetailFragment extends Fragment {
|
||||
binding.btnDeleteAdoption.setOnClickListener(v -> confirmDelete());
|
||||
}
|
||||
|
||||
private void observeViewModel() {
|
||||
viewModel.getPetList().observe(getViewLifecycleOwner(), list -> refreshPetSpinner());
|
||||
viewModel.getCustomerList().observe(getViewLifecycleOwner(), list -> refreshCustomerSpinner());
|
||||
viewModel.getStoreList().observe(getViewLifecycleOwner(), list -> refreshStoreSpinner());
|
||||
viewModel.getEmployeeList().observe(getViewLifecycleOwner(), list -> refreshEmployeeSpinner());
|
||||
}
|
||||
|
||||
private void setLoading(boolean loading) {
|
||||
if (binding != null && binding.progressBar != null) {
|
||||
binding.progressBar.setVisibility(loading ? View.VISIBLE : View.GONE);
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onDestroyView() {
|
||||
super.onDestroyView();
|
||||
binding = null;
|
||||
}
|
||||
|
||||
/**
|
||||
* Configures the spinner for adoption status.
|
||||
*/
|
||||
private void setupSpinners() {
|
||||
SpinnerUtils.setupStringSpinner(requireContext(), binding.spinnerAdoptionStatus, STATUSES);
|
||||
|
||||
UIUtils.setViewsEnabled(false, binding.spinnerAdoptionPet);
|
||||
|
||||
binding.spinnerAdoptionCustomer.setOnItemSelectedListener(new AdapterView.OnItemSelectedListener() {
|
||||
@Override
|
||||
public void onItemSelected(AdapterView<?> parent, View view, int position, long id) {
|
||||
if (position > 0) {
|
||||
UIUtils.setViewsEnabled(true, binding.spinnerAdoptionPet);
|
||||
} else {
|
||||
if (!viewModel.isEditing()) {
|
||||
binding.spinnerAdoptionPet.setSelection(0);
|
||||
UIUtils.setViewsEnabled(false, binding.spinnerAdoptionPet);
|
||||
}
|
||||
}
|
||||
}
|
||||
@Override
|
||||
public void onNothingSelected(AdapterView<?> parent) {}
|
||||
});
|
||||
|
||||
binding.spinnerAdoptionStore.setOnItemSelectedListener(new AdapterView.OnItemSelectedListener() {
|
||||
@Override
|
||||
public void onItemSelected(AdapterView<?> parent, View view, int position, long id) {
|
||||
if (position > 0 && viewModel.getStoreList().getValue() != null && position <= viewModel.getStoreList().getValue().size()) {
|
||||
DropdownDTO selectedStore = viewModel.getStoreList().getValue().get(position - 1);
|
||||
loadEmployees(selectedStore.getId());
|
||||
} else {
|
||||
viewModel.setEmployeeList(new ArrayList<>());
|
||||
}
|
||||
}
|
||||
@Override
|
||||
public void onNothingSelected(AdapterView<?> parent) {}
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* Configures the date picker dialog for the adoption date field.
|
||||
*/
|
||||
private void setupDatePicker() {
|
||||
binding.etAdoptionDate.setOnClickListener(v -> {
|
||||
Calendar c = Calendar.getInstance();
|
||||
new DatePickerDialog(requireContext(),
|
||||
(dp, y, m, d) -> binding.etAdoptionDate.setText(
|
||||
String.format("%04d-%02d-%02d", y, m + 1, d)),
|
||||
c.get(Calendar.YEAR),
|
||||
c.get(Calendar.MONTH),
|
||||
c.get(Calendar.DAY_OF_MONTH)).show();
|
||||
});
|
||||
binding.etAdoptionDate.setOnClickListener(v -> UIUtils.showDatePicker(requireContext(), binding.etAdoptionDate, null));
|
||||
}
|
||||
|
||||
/**
|
||||
* Fetches required data for spinners from the backend.
|
||||
*/
|
||||
private void loadSpinnersData() {
|
||||
loadPets();
|
||||
loadCustomers();
|
||||
loadStores();
|
||||
loadEmployees();
|
||||
}
|
||||
|
||||
/**
|
||||
* Loads the list of pets from the API.
|
||||
*/
|
||||
private void loadPets() {
|
||||
petViewModel.getAllPets(0, 200, null, null, null, null, "petName").observe(getViewLifecycleOwner(), resource -> {
|
||||
viewModel.loadPets().observe(getViewLifecycleOwner(), resource -> {
|
||||
if (resource == null) return;
|
||||
setLoading(resource.status == Resource.Status.LOADING);
|
||||
if (resource.status == Resource.Status.SUCCESS && resource.data != null) {
|
||||
petList = resource.data.getContent();
|
||||
refreshPetSpinner();
|
||||
viewModel.setPetList(resource.data);
|
||||
}
|
||||
});
|
||||
viewModel.loadCustomers().observe(getViewLifecycleOwner(), resource -> {
|
||||
if (resource == null) return;
|
||||
setLoading(resource.status == Resource.Status.LOADING);
|
||||
if (resource.status == Resource.Status.SUCCESS && resource.data != null) {
|
||||
viewModel.setCustomerList(resource.data);
|
||||
}
|
||||
});
|
||||
viewModel.loadStores().observe(getViewLifecycleOwner(), resource -> {
|
||||
if (resource == null) return;
|
||||
setLoading(resource.status == Resource.Status.LOADING);
|
||||
if (resource.status == Resource.Status.SUCCESS && resource.data != null) {
|
||||
viewModel.setStoreList(resource.data);
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* Populates the pet selection spinner with data.
|
||||
*/
|
||||
private void refreshPetSpinner() {
|
||||
SpinnerUtils.populateSpinner(requireContext(), binding.spinnerAdoptionPet, petList,
|
||||
PetDTO::getPetName, "-- Select Pet --",
|
||||
preselectedPetId, PetDTO::getPetId);
|
||||
SpinnerUtils.populateSpinner(requireContext(), binding.spinnerAdoptionPet, viewModel.getPetList().getValue(),
|
||||
DropdownDTO::getLabel, "-- Select Pet --",
|
||||
preselectedPetId, DropdownDTO::getId);
|
||||
}
|
||||
|
||||
/**
|
||||
* Loads the list of customers from the API.
|
||||
*/
|
||||
private void loadCustomers() {
|
||||
customerViewModel.getAllCustomers(0, 200).observe(getViewLifecycleOwner(), resource -> {
|
||||
if (resource.status == Resource.Status.SUCCESS && resource.data != null) {
|
||||
customerList = resource.data.getContent();
|
||||
refreshCustomerSpinner();
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* Populates the customer selection spinner with data.
|
||||
*/
|
||||
private void refreshCustomerSpinner() {
|
||||
SpinnerUtils.populateSpinner(requireContext(), binding.spinnerAdoptionCustomer, customerList,
|
||||
item -> item.getFirstName() + " " + item.getLastName(),
|
||||
"-- Select Customer --",
|
||||
preselectedCustomerId, CustomerDTO::getCustomerId);
|
||||
SpinnerUtils.populateSpinner(requireContext(), binding.spinnerAdoptionCustomer, viewModel.getCustomerList().getValue(),
|
||||
DropdownDTO::getLabel, "-- Select Customer --",
|
||||
preselectedCustomerId, DropdownDTO::getId);
|
||||
}
|
||||
|
||||
/**
|
||||
* Loads the list of stores from the API.
|
||||
*/
|
||||
private void loadStores() {
|
||||
storeViewModel.getAllStores(0, 200).observe(getViewLifecycleOwner(), resource -> {
|
||||
if (resource.status == Resource.Status.SUCCESS && resource.data != null) {
|
||||
storeList = resource.data.getContent();
|
||||
refreshStoreSpinner();
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* Populates the store selection spinner with data.
|
||||
*/
|
||||
private void refreshStoreSpinner() {
|
||||
SpinnerUtils.populateSpinner(requireContext(), binding.spinnerAdoptionStore, storeList,
|
||||
StoreDTO::getStoreName, "-- Select Store --",
|
||||
preselectedStoreId, StoreDTO::getStoreId);
|
||||
SpinnerUtils.populateSpinner(requireContext(), binding.spinnerAdoptionStore, viewModel.getStoreList().getValue(),
|
||||
DropdownDTO::getLabel, "-- Select Store --",
|
||||
preselectedStoreId, DropdownDTO::getId);
|
||||
}
|
||||
|
||||
/**
|
||||
* Loads the list of employees from the API.
|
||||
*/
|
||||
private void loadEmployees() {
|
||||
userViewModel.getUsers("STAFF", 0, 100).observe(getViewLifecycleOwner(), resource -> {
|
||||
private void loadEmployees(Long storeId) {
|
||||
viewModel.loadEmployees(storeId).observe(getViewLifecycleOwner(), resource -> {
|
||||
if (resource == null) return;
|
||||
setLoading(resource.status == Resource.Status.LOADING);
|
||||
if (resource.status == Resource.Status.SUCCESS && resource.data != null) {
|
||||
employeeList = resource.data.getContent();
|
||||
refreshEmployeeSpinner();
|
||||
viewModel.setEmployeeList(resource.data);
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* Populates the employee selection spinner with data.
|
||||
*/
|
||||
private void refreshEmployeeSpinner() {
|
||||
SpinnerUtils.populateSpinner(requireContext(), binding.spinnerAdoptionEmployee, employeeList,
|
||||
UserDTO::getFullName, "-- Select Staff --",
|
||||
preselectedEmployeeId, UserDTO::getId);
|
||||
SpinnerUtils.populateSpinner(requireContext(), binding.spinnerAdoptionEmployee, viewModel.getEmployeeList().getValue(),
|
||||
DropdownDTO::getLabel, "-- Select Staff --",
|
||||
preselectedEmployeeId, DropdownDTO::getId);
|
||||
}
|
||||
|
||||
/**
|
||||
* Handles arguments to determine if the fragment is in edit or add mode.
|
||||
*/
|
||||
private void handleArguments() {
|
||||
Bundle a = getArguments();
|
||||
if (a != null && a.containsKey("adoptionId")) {
|
||||
isEditing = true;
|
||||
adoptionId = a.getLong("adoptionId");
|
||||
long adoptionId = a.getLong("adoptionId");
|
||||
viewModel.setAdoptionId(adoptionId);
|
||||
binding.tvAdoptionMode.setText("Edit Adoption");
|
||||
binding.tvAdoptionId.setText("ID: " + adoptionId);
|
||||
binding.tvAdoptionId.setText(DateTimeUtils.formatId(adoptionId));
|
||||
binding.tvAdoptionId.setVisibility(View.VISIBLE);
|
||||
binding.btnDeleteAdoption.setVisibility(View.VISIBLE);
|
||||
loadAdoptionData();
|
||||
} else {
|
||||
viewModel.setAdoptionId(-1);
|
||||
binding.tvAdoptionMode.setText("Add Adoption");
|
||||
binding.btnDeleteAdoption.setVisibility(View.GONE);
|
||||
binding.tvAdoptionId.setVisibility(View.GONE);
|
||||
UIUtils.setViewsEnabled(false, binding.spinnerAdoptionPet);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Fetches specific adoption details from the backend using the ID.
|
||||
*/
|
||||
private void loadAdoptionData() {
|
||||
adoptionViewModel.getAdoptionById(adoptionId).observe(getViewLifecycleOwner(), resource -> {
|
||||
viewModel.loadAdoption().observe(getViewLifecycleOwner(), resource -> {
|
||||
if (resource == null) return;
|
||||
setLoading(resource.status == Resource.Status.LOADING);
|
||||
if (resource.status == Resource.Status.SUCCESS && resource.data != null) {
|
||||
AdoptionDTO a = resource.data;
|
||||
preselectedPetId = a.getPetId() != null ? a.getPetId() : -1;
|
||||
@@ -247,90 +221,68 @@ public class AdoptionDetailFragment extends Fragment {
|
||||
refreshPetSpinner();
|
||||
refreshCustomerSpinner();
|
||||
refreshStoreSpinner();
|
||||
refreshEmployeeSpinner();
|
||||
|
||||
if (preselectedCustomerId != -1) {
|
||||
UIUtils.setViewsEnabled(true, binding.spinnerAdoptionPet);
|
||||
}
|
||||
} else if (resource.status == Resource.Status.ERROR) {
|
||||
Toast.makeText(getContext(), "Failed to load adoption: " + resource.message, Toast.LENGTH_SHORT).show();
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* Validates input and saves the adoption request to the backend.
|
||||
*/
|
||||
private void saveAdoption() {
|
||||
if (binding.spinnerAdoptionCustomer.getSelectedItemPosition() == 0) {
|
||||
Toast.makeText(getContext(), "Select a customer", Toast.LENGTH_SHORT).show(); return;
|
||||
}
|
||||
if (binding.spinnerAdoptionPet.getSelectedItemPosition() == 0) {
|
||||
Toast.makeText(getContext(), "Select a pet", Toast.LENGTH_SHORT).show(); return;
|
||||
}
|
||||
if (binding.spinnerAdoptionStore.getSelectedItemPosition() == 0) {
|
||||
Toast.makeText(getContext(), "Select a store", Toast.LENGTH_SHORT).show(); return;
|
||||
}
|
||||
String date = binding.etAdoptionDate.getText().toString().trim();
|
||||
if (date.isEmpty()) {
|
||||
Toast.makeText(getContext(), "Select a date", Toast.LENGTH_SHORT).show(); return;
|
||||
}
|
||||
if (!InputValidator.isSpinnerSelected(binding.spinnerAdoptionCustomer, "Customer")) return;
|
||||
if (!InputValidator.isSpinnerSelected(binding.spinnerAdoptionPet, "Pet")) return;
|
||||
if (!InputValidator.isSpinnerSelected(binding.spinnerAdoptionStore, "Store")) return;
|
||||
if (!InputValidator.isNotEmpty(binding.etAdoptionDate, "Adoption Date")) return;
|
||||
|
||||
BigDecimal fee = BigDecimal.ZERO;
|
||||
String feeStr = binding.etAdoptionFee.getText().toString().trim();
|
||||
if (!feeStr.isEmpty()) {
|
||||
try {
|
||||
fee = new BigDecimal(feeStr);
|
||||
} catch (NumberFormatException e) {
|
||||
Toast.makeText(getContext(), "Invalid fee format", Toast.LENGTH_SHORT).show();
|
||||
return;
|
||||
}
|
||||
if (!InputValidator.isPositiveDecimal(binding.etAdoptionFee, "Adoption Fee")) return;
|
||||
fee = new BigDecimal(feeStr);
|
||||
}
|
||||
|
||||
CustomerDTO customer = customerList.get(binding.spinnerAdoptionCustomer.getSelectedItemPosition() - 1);
|
||||
PetDTO pet = petList.get(binding.spinnerAdoptionPet.getSelectedItemPosition() - 1);
|
||||
StoreDTO store = storeList.get(binding.spinnerAdoptionStore.getSelectedItemPosition() - 1);
|
||||
DropdownDTO customer = viewModel.getCustomerList().getValue().get(binding.spinnerAdoptionCustomer.getSelectedItemPosition() - 1);
|
||||
DropdownDTO pet = viewModel.getPetList().getValue().get(binding.spinnerAdoptionPet.getSelectedItemPosition() - 1);
|
||||
DropdownDTO store = viewModel.getStoreList().getValue().get(binding.spinnerAdoptionStore.getSelectedItemPosition() - 1);
|
||||
|
||||
Long employeeId = null;
|
||||
if (binding.spinnerAdoptionEmployee.getSelectedItemPosition() > 0) {
|
||||
employeeId = employeeList.get(binding.spinnerAdoptionEmployee.getSelectedItemPosition() - 1).getId();
|
||||
employeeId = viewModel.getEmployeeList().getValue().get(binding.spinnerAdoptionEmployee.getSelectedItemPosition() - 1).getId();
|
||||
}
|
||||
|
||||
String adoptionDate = binding.etAdoptionDate.getText().toString().trim();
|
||||
String status = STATUSES[binding.spinnerAdoptionStatus.getSelectedItemPosition()];
|
||||
|
||||
AdoptionDTO dto = new AdoptionDTO(
|
||||
pet.getPetId(),
|
||||
customer.getCustomerId(),
|
||||
pet.getId(),
|
||||
customer.getId(),
|
||||
employeeId,
|
||||
store.getStoreId(),
|
||||
date,
|
||||
store.getId(),
|
||||
adoptionDate,
|
||||
status,
|
||||
fee
|
||||
);
|
||||
|
||||
if (isEditing) {
|
||||
adoptionViewModel.updateAdoption(adoptionId, dto).observe(getViewLifecycleOwner(), resource -> {
|
||||
if (resource.status == Resource.Status.SUCCESS) {
|
||||
Toast.makeText(getContext(), "Updated", Toast.LENGTH_SHORT).show();
|
||||
navigateBack();
|
||||
} else if (resource.status == Resource.Status.ERROR) {
|
||||
Toast.makeText(getContext(), "Error: " + resource.message, Toast.LENGTH_SHORT).show();
|
||||
}
|
||||
});
|
||||
} else {
|
||||
adoptionViewModel.createAdoption(dto).observe(getViewLifecycleOwner(), resource -> {
|
||||
if (resource.status == Resource.Status.SUCCESS) {
|
||||
Toast.makeText(getContext(), "Saved", Toast.LENGTH_SHORT).show();
|
||||
navigateBack();
|
||||
} else if (resource.status == Resource.Status.ERROR) {
|
||||
Toast.makeText(getContext(), "Error: " + resource.message, Toast.LENGTH_SHORT).show();
|
||||
}
|
||||
});
|
||||
}
|
||||
viewModel.saveAdoption(dto).observe(getViewLifecycleOwner(), resource -> {
|
||||
if (resource == null) return;
|
||||
setLoading(resource.status == Resource.Status.LOADING);
|
||||
if (resource.status == Resource.Status.SUCCESS) {
|
||||
Toast.makeText(getContext(), viewModel.isEditing() ? "Updated" : "Saved", Toast.LENGTH_SHORT).show();
|
||||
navigateBack();
|
||||
} else if (resource.status == Resource.Status.ERROR) {
|
||||
Toast.makeText(getContext(), "Error: " + resource.message, Toast.LENGTH_SHORT).show();
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* Shows a confirmation dialog before deleting an adoption request.
|
||||
*/
|
||||
private void confirmDelete() {
|
||||
DialogUtils.showDeleteConfirmDialog(requireContext(), "Adoption", () ->
|
||||
adoptionViewModel.deleteAdoption(adoptionId).observe(getViewLifecycleOwner(), resource -> {
|
||||
DialogUtils.showDeleteConfirmDialog(requireContext(), "Adoption Record", () ->
|
||||
viewModel.deleteAdoption().observe(getViewLifecycleOwner(), resource -> {
|
||||
if (resource == null) return;
|
||||
setLoading(resource.status == Resource.Status.LOADING);
|
||||
if (resource.status == Resource.Status.SUCCESS) {
|
||||
Toast.makeText(getContext(), "Deleted", Toast.LENGTH_SHORT).show();
|
||||
navigateBack();
|
||||
@@ -340,9 +292,6 @@ public class AdoptionDetailFragment extends Fragment {
|
||||
}));
|
||||
}
|
||||
|
||||
/**
|
||||
* Navigates back to the previous fragment.
|
||||
*/
|
||||
private void navigateBack() {
|
||||
NavHostFragment.findNavController(this).popBackStack();
|
||||
}
|
||||
|
||||
@@ -1,10 +1,12 @@
|
||||
package com.example.petstoremobile.fragments.listfragments.detailfragments;
|
||||
|
||||
import android.app.DatePickerDialog;
|
||||
import android.os.Bundle;
|
||||
import android.util.Log;
|
||||
import android.view.*;
|
||||
import android.widget.*;
|
||||
import android.view.LayoutInflater;
|
||||
import android.view.View;
|
||||
import android.view.ViewGroup;
|
||||
import android.widget.AdapterView;
|
||||
import android.widget.Toast;
|
||||
|
||||
import androidx.annotation.NonNull;
|
||||
import androidx.annotation.Nullable;
|
||||
import androidx.fragment.app.Fragment;
|
||||
@@ -12,18 +14,16 @@ import androidx.lifecycle.ViewModelProvider;
|
||||
import androidx.navigation.fragment.NavHostFragment;
|
||||
|
||||
import com.example.petstoremobile.databinding.FragmentAppointmentDetailBinding;
|
||||
import com.example.petstoremobile.dtos.*;
|
||||
import com.example.petstoremobile.dtos.AppointmentDTO;
|
||||
import com.example.petstoremobile.dtos.DropdownDTO;
|
||||
import com.example.petstoremobile.dtos.ServiceDTO;
|
||||
import com.example.petstoremobile.utils.DateTimeUtils;
|
||||
import com.example.petstoremobile.utils.DialogUtils;
|
||||
import com.example.petstoremobile.utils.InputValidator;
|
||||
import com.example.petstoremobile.utils.Resource;
|
||||
import com.example.petstoremobile.utils.SpinnerUtils;
|
||||
import com.example.petstoremobile.viewmodels.AppointmentViewModel;
|
||||
import com.example.petstoremobile.viewmodels.CustomerViewModel;
|
||||
import com.example.petstoremobile.viewmodels.PetViewModel;
|
||||
import com.example.petstoremobile.viewmodels.ServiceViewModel;
|
||||
import com.example.petstoremobile.viewmodels.StoreViewModel;
|
||||
import com.example.petstoremobile.viewmodels.UserViewModel;
|
||||
|
||||
import java.util.*;
|
||||
import com.example.petstoremobile.utils.UIUtils;
|
||||
import com.example.petstoremobile.viewmodels.AppointmentDetailViewModel;
|
||||
|
||||
import dagger.hilt.android.AndroidEntryPoint;
|
||||
|
||||
@@ -35,39 +35,22 @@ public class AppointmentDetailFragment extends Fragment {
|
||||
|
||||
private FragmentAppointmentDetailBinding binding;
|
||||
|
||||
private long appointmentId = -1;
|
||||
private boolean isEditing = false;
|
||||
private long preselectedPetId = -1;
|
||||
private long preselectedServiceId = -1;
|
||||
private long preselectedCustomerId = -1;
|
||||
private long preselectedStoreId = -1;
|
||||
private long preselectedStaffId = -1;
|
||||
|
||||
private List<PetDTO> petList = new ArrayList<>();
|
||||
private List<ServiceDTO> serviceList = new ArrayList<>();
|
||||
private List<CustomerDTO> customerList = new ArrayList<>();
|
||||
private List<StoreDTO> storeList = new ArrayList<>();
|
||||
private List<UserDTO> staffList = new ArrayList<>();
|
||||
private final Integer[] HOURS = {9, 10, 11, 12, 13, 14, 15, 16, 17};
|
||||
private final Integer[] MINUTES = {0, 15, 30, 45};
|
||||
|
||||
private final Integer[] HOURS = {9,10,11,12,13,14,15,16,17};
|
||||
private final Integer[] MINUTES = {0,15,30,45};
|
||||
|
||||
private AppointmentViewModel appointmentViewModel;
|
||||
private PetViewModel petViewModel;
|
||||
private ServiceViewModel serviceViewModel;
|
||||
private StoreViewModel storeViewModel;
|
||||
private CustomerViewModel customerViewModel;
|
||||
private UserViewModel userViewModel;
|
||||
private AppointmentDetailViewModel viewModel;
|
||||
private boolean isUpdatingUI = false;
|
||||
|
||||
@Override
|
||||
public void onCreate(Bundle savedInstanceState) {
|
||||
super.onCreate(savedInstanceState);
|
||||
appointmentViewModel = new ViewModelProvider(this).get(AppointmentViewModel.class);
|
||||
petViewModel = new ViewModelProvider(this).get(PetViewModel.class);
|
||||
serviceViewModel = new ViewModelProvider(this).get(ServiceViewModel.class);
|
||||
storeViewModel = new ViewModelProvider(this).get(StoreViewModel.class);
|
||||
customerViewModel = new ViewModelProvider(this).get(CustomerViewModel.class);
|
||||
userViewModel = new ViewModelProvider(this).get(UserViewModel.class);
|
||||
viewModel = new ViewModelProvider(this).get(AppointmentDetailViewModel.class);
|
||||
}
|
||||
|
||||
@Override
|
||||
@@ -81,7 +64,8 @@ public class AppointmentDetailFragment extends Fragment {
|
||||
super.onViewCreated(view, savedInstanceState);
|
||||
setupSpinners();
|
||||
setupDatePicker();
|
||||
loadSpinnersData();
|
||||
observeViewModel();
|
||||
viewModel.loadInitialFormData();
|
||||
handleArguments();
|
||||
|
||||
binding.btnApptBack.setOnClickListener(v -> navigateBack());
|
||||
@@ -95,365 +79,214 @@ public class AppointmentDetailFragment extends Fragment {
|
||||
binding = null;
|
||||
}
|
||||
|
||||
/**
|
||||
* Configures the adapters for spinners.
|
||||
*/
|
||||
private void setupSpinners() {
|
||||
SpinnerUtils.setupStringSpinner(requireContext(), binding.spinnerAppointmentStatus,
|
||||
new String[]{"Booked", "Completed", "Cancelled", "Missed"});
|
||||
SpinnerUtils.setupStringSpinner(requireContext(), binding.spinnerAppointmentStatus, new String[]{});
|
||||
|
||||
String[] hours = new String[HOURS.length];
|
||||
for (int i = 0; i < HOURS.length; i++)
|
||||
hours[i] = String.format("%02d:00", HOURS[i]);
|
||||
hours[i] = DateTimeUtils.formatTime(HOURS[i], 0);
|
||||
SpinnerUtils.setupStringSpinner(requireContext(), binding.spinnerHour, hours);
|
||||
SpinnerUtils.setupStringSpinner(requireContext(), binding.spinnerMinute, new String[]{"00","15","30","45"});
|
||||
SpinnerUtils.setupStringSpinner(requireContext(), binding.spinnerMinute, new String[]{"00", "15", "30", "45"});
|
||||
|
||||
UIUtils.setViewsEnabled(false, binding.spinnerPet, binding.spinnerStaff);
|
||||
|
||||
binding.spinnerCustomer.setOnItemSelectedListener(new AdapterView.OnItemSelectedListener() {
|
||||
@Override
|
||||
public void onItemSelected(AdapterView<?> parent, View view, int position, long id) {
|
||||
viewModel.onCustomerSelected(position);
|
||||
}
|
||||
@Override
|
||||
public void onNothingSelected(AdapterView<?> parent) {}
|
||||
});
|
||||
|
||||
binding.spinnerStore.setOnItemSelectedListener(new AdapterView.OnItemSelectedListener() {
|
||||
@Override
|
||||
public void onItemSelected(AdapterView<?> parent, View view, int position, long id) {
|
||||
viewModel.onStoreSelected(position);
|
||||
}
|
||||
@Override
|
||||
public void onNothingSelected(AdapterView<?> parent) {}
|
||||
});
|
||||
|
||||
SpinnerUtils.setOnIndexSelectedListener(binding.spinnerService, p -> viewModel.onServiceSelected(p));
|
||||
SpinnerUtils.setOnIndexSelectedListener(binding.spinnerPet, p -> viewModel.onPetSelected(p));
|
||||
SpinnerUtils.setOnIndexSelectedListener(binding.spinnerStaff, p -> viewModel.onStaffSelected(p));
|
||||
|
||||
SpinnerUtils.setOnIndexSelectedListener(binding.spinnerHour, p -> notifyDateTimeStatusChange());
|
||||
SpinnerUtils.setOnIndexSelectedListener(binding.spinnerMinute, p -> notifyDateTimeStatusChange());
|
||||
SpinnerUtils.setOnIndexSelectedListener(binding.spinnerAppointmentStatus, p -> notifyDateTimeStatusChange());
|
||||
}
|
||||
|
||||
/**
|
||||
* Configures the date picker dialog for the appointment date field.
|
||||
*/
|
||||
private void setupDatePicker() {
|
||||
binding.etAppointmentDate.setOnClickListener(v -> {
|
||||
Calendar c = Calendar.getInstance();
|
||||
DatePickerDialog d = new DatePickerDialog(requireContext(),
|
||||
(dp,y,m,d1) -> binding.etAppointmentDate.setText(
|
||||
String.format("%04d-%02d-%02d", y, m+1, d1)),
|
||||
c.get(Calendar.YEAR), c.get(Calendar.MONTH),
|
||||
c.get(Calendar.DAY_OF_MONTH));
|
||||
d.getDatePicker().setMinDate(System.currentTimeMillis() - 1000);
|
||||
d.show();
|
||||
});
|
||||
binding.etAppointmentDate.setOnClickListener(v ->
|
||||
UIUtils.showDatePicker(requireContext(), binding.etAppointmentDate, this::notifyDateTimeStatusChange));
|
||||
}
|
||||
|
||||
/**
|
||||
* Fetches all required data for spinners from the backend.
|
||||
*/
|
||||
private void loadSpinnersData() {
|
||||
loadPets();
|
||||
loadServices();
|
||||
loadCustomers();
|
||||
loadStores();
|
||||
loadStaff();
|
||||
private void observeViewModel() {
|
||||
viewModel.getViewState().observe(getViewLifecycleOwner(), this::applyViewState);
|
||||
|
||||
viewModel.getCustomers().observe(getViewLifecycleOwner(), list ->
|
||||
SpinnerUtils.populateSpinner(requireContext(), binding.spinnerCustomer, list, DropdownDTO::getLabel, "-- Select Customer --", preselectedCustomerId, DropdownDTO::getId));
|
||||
|
||||
viewModel.getStores().observe(getViewLifecycleOwner(), list ->
|
||||
SpinnerUtils.populateSpinner(requireContext(), binding.spinnerStore, list, DropdownDTO::getLabel, "-- Select Store --", preselectedStoreId, DropdownDTO::getId));
|
||||
|
||||
viewModel.getServices().observe(getViewLifecycleOwner(), list ->
|
||||
SpinnerUtils.populateSpinner(requireContext(), binding.spinnerService, list, ServiceDTO::getServiceName, "-- Select Service --", preselectedServiceId, ServiceDTO::getServiceId));
|
||||
|
||||
viewModel.getCustomerPets().observe(getViewLifecycleOwner(), list ->
|
||||
SpinnerUtils.populateSpinner(requireContext(), binding.spinnerPet, list, DropdownDTO::getLabel, "-- Select Pet --", preselectedPetId, DropdownDTO::getId));
|
||||
|
||||
viewModel.getStoreEmployees().observe(getViewLifecycleOwner(), list ->
|
||||
SpinnerUtils.populateSpinner(requireContext(), binding.spinnerStaff, list, DropdownDTO::getLabel, "-- Select Staff --", preselectedStaffId, DropdownDTO::getId));
|
||||
}
|
||||
|
||||
/**
|
||||
* Loads the list of pets from the ViewModel.
|
||||
*/
|
||||
private void loadPets() {
|
||||
petViewModel.getAllPets(0, 200, null, null, null, null, "petName").observe(getViewLifecycleOwner(), resource -> {
|
||||
if (resource.status == Resource.Status.SUCCESS && resource.data != null) {
|
||||
petList = resource.data.getContent();
|
||||
refreshPetSpinner();
|
||||
}
|
||||
});
|
||||
private void setLoading(boolean loading) {
|
||||
if (binding != null && binding.progressBar != null) {
|
||||
binding.progressBar.setVisibility(loading ? View.VISIBLE : View.GONE);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Populates the pet selection spinner.
|
||||
*/
|
||||
private void refreshPetSpinner() {
|
||||
SpinnerUtils.populateSpinner(requireContext(), binding.spinnerPet, petList,
|
||||
PetDTO::getPetName, "-- Select Pet --",
|
||||
preselectedPetId, PetDTO::getPetId);
|
||||
private void applyViewState(AppointmentDetailViewModel.ViewState state) {
|
||||
isUpdatingUI = true;
|
||||
|
||||
binding.tvApptMode.setText(state.isEditing ? "Edit Appointment" : "Add Appointment");
|
||||
binding.tvAppointmentId.setText(DateTimeUtils.formatId(viewModel.getAppointmentId()));
|
||||
binding.tvAppointmentId.setVisibility(state.isEditing ? View.VISIBLE : View.GONE);
|
||||
binding.btnDeleteAppointment.setVisibility(state.isDeleteVisible ? View.VISIBLE : View.GONE);
|
||||
binding.btnSaveAppointment.setVisibility(state.isSaveVisible ? View.VISIBLE : View.GONE);
|
||||
|
||||
UIUtils.setFieldEnabled(state.isCustomerEnabled, binding.spinnerCustomer, binding.tvLabelCustomer);
|
||||
UIUtils.setFieldEnabled(state.isStoreEnabled, binding.spinnerStore, binding.tvLabelStore);
|
||||
UIUtils.setFieldEnabled(state.isPetEnabled, binding.spinnerPet, binding.tvLabelPet);
|
||||
UIUtils.setFieldEnabled(state.isServiceEnabled, binding.spinnerService, binding.tvLabelService);
|
||||
UIUtils.setFieldEnabled(state.isStaffEnabled, binding.spinnerStaff, binding.tvLabelStaff);
|
||||
UIUtils.setFieldEnabled(state.isDateEnabled, binding.etAppointmentDate, binding.tvLabelDate);
|
||||
UIUtils.setFieldEnabled(state.isTimeEnabled, binding.spinnerHour, binding.tvLabelTime);
|
||||
UIUtils.setViewsEnabled(state.isTimeEnabled, binding.spinnerMinute);
|
||||
UIUtils.setViewsEnabled(state.isStatusEnabled, binding.spinnerAppointmentStatus);
|
||||
|
||||
Object selected = binding.spinnerAppointmentStatus.getSelectedItem();
|
||||
String current = selected != null ? selected.toString() : "";
|
||||
SpinnerUtils.setupStringSpinner(requireContext(), binding.spinnerAppointmentStatus, state.availableStatuses);
|
||||
SpinnerUtils.setSelectionByValue(binding.spinnerAppointmentStatus, current);
|
||||
|
||||
isUpdatingUI = false;
|
||||
}
|
||||
|
||||
/**
|
||||
* Loads the list of services from the API.
|
||||
*/
|
||||
private void loadServices() {
|
||||
serviceViewModel.getAllServices(0, 200, null, "serviceName").observe(getViewLifecycleOwner(), resource -> {
|
||||
if (resource.status == Resource.Status.SUCCESS && resource.data != null) {
|
||||
serviceList = resource.data.getContent();
|
||||
refreshServiceSpinner();
|
||||
}
|
||||
});
|
||||
private void notifyDateTimeStatusChange() {
|
||||
if (isUpdatingUI) return;
|
||||
|
||||
String date = binding.etAppointmentDate.getText().toString();
|
||||
String time = buildTimeString();
|
||||
Object selected = binding.spinnerAppointmentStatus.getSelectedItem();
|
||||
String status = selected != null ? selected.toString() : "";
|
||||
viewModel.onDateOrTimeChanged(date, time, status);
|
||||
}
|
||||
|
||||
/**
|
||||
* Populates the service selection spinner.
|
||||
*/
|
||||
private void refreshServiceSpinner() {
|
||||
SpinnerUtils.populateSpinner(requireContext(), binding.spinnerService, serviceList,
|
||||
ServiceDTO::getServiceName, "-- Select Service --",
|
||||
preselectedServiceId, ServiceDTO::getServiceId);
|
||||
}
|
||||
|
||||
/**
|
||||
* Loads the list of customers from the API.
|
||||
*/
|
||||
private void loadCustomers() {
|
||||
customerViewModel.getAllCustomers(0, 200).observe(getViewLifecycleOwner(), resource -> {
|
||||
if (resource.status == Resource.Status.SUCCESS && resource.data != null) {
|
||||
customerList = resource.data.getContent();
|
||||
refreshCustomerSpinner();
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* Populates the customer selection spinner.
|
||||
*/
|
||||
private void refreshCustomerSpinner() {
|
||||
SpinnerUtils.populateSpinner(requireContext(), binding.spinnerCustomer, customerList,
|
||||
item -> item.getFirstName() + " " + item.getLastName(),
|
||||
"-- Select Customer --",
|
||||
preselectedCustomerId, CustomerDTO::getCustomerId);
|
||||
}
|
||||
|
||||
/**
|
||||
* Loads the list of stores from the API.
|
||||
*/
|
||||
private void loadStores() {
|
||||
storeViewModel.getAllStores(0, 50).observe(getViewLifecycleOwner(), resource -> {
|
||||
if (resource.status == Resource.Status.SUCCESS && resource.data != null) {
|
||||
storeList = resource.data.getContent();
|
||||
refreshStoreSpinner();
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* Populates the store selection spinner.
|
||||
*/
|
||||
private void refreshStoreSpinner() {
|
||||
SpinnerUtils.populateSpinner(requireContext(), binding.spinnerStore, storeList,
|
||||
StoreDTO::getStoreName, "-- Select Store --",
|
||||
preselectedStoreId, StoreDTO::getStoreId);
|
||||
}
|
||||
|
||||
/**
|
||||
* Loads the list of staff from the API.
|
||||
*/
|
||||
private void loadStaff() {
|
||||
userViewModel.getUsers("STAFF", 0, 100).observe(getViewLifecycleOwner(), resource -> {
|
||||
if (resource.status == Resource.Status.SUCCESS && resource.data != null) {
|
||||
staffList = resource.data.getContent();
|
||||
refreshStaffSpinner();
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* Populates the staff selection spinner.
|
||||
*/
|
||||
private void refreshStaffSpinner() {
|
||||
SpinnerUtils.populateSpinner(requireContext(), binding.spinnerStaff, staffList,
|
||||
UserDTO::getFullName, "-- Select Staff --",
|
||||
preselectedStaffId, UserDTO::getId);
|
||||
}
|
||||
|
||||
/**
|
||||
* Handles arguments to determine if the fragment is in edit or add mode.
|
||||
*/
|
||||
private void handleArguments() {
|
||||
Bundle a = getArguments();
|
||||
if (a != null && a.containsKey("appointmentId")) {
|
||||
isEditing = true;
|
||||
appointmentId = a.getLong("appointmentId");
|
||||
binding.tvApptMode.setText("Edit Appointment");
|
||||
binding.tvAppointmentId.setText("ID: " + appointmentId);
|
||||
binding.tvAppointmentId.setVisibility(View.VISIBLE);
|
||||
binding.btnDeleteAppointment.setVisibility(View.VISIBLE);
|
||||
viewModel.setAppointmentId(a.getLong("appointmentId"));
|
||||
loadAppointmentData();
|
||||
} else {
|
||||
binding.tvApptMode.setText("Add Appointment");
|
||||
binding.btnDeleteAppointment.setVisibility(View.GONE);
|
||||
binding.tvAppointmentId.setVisibility(View.GONE);
|
||||
viewModel.setAppointmentId(-1);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Fetches specific appointment details from the backend using the ID.
|
||||
*/
|
||||
private void loadAppointmentData() {
|
||||
appointmentViewModel.getAppointmentById(appointmentId).observe(getViewLifecycleOwner(), resource -> {
|
||||
viewModel.loadAppointment().observe(getViewLifecycleOwner(), resource -> {
|
||||
if (resource == null) return;
|
||||
setLoading(resource.status == Resource.Status.LOADING);
|
||||
if (resource.status == Resource.Status.SUCCESS && resource.data != null) {
|
||||
AppointmentDTO a = resource.data;
|
||||
preselectedPetId = (a.getPetId() != null) ? a.getPetId() : -1;
|
||||
preselectedServiceId = (a.getServiceId() != null) ? a.getServiceId() : -1;
|
||||
preselectedCustomerId = (a.getCustomerId() != null) ? a.getCustomerId() : -1;
|
||||
preselectedStoreId = (a.getStoreId() != null) ? a.getStoreId() : -1;
|
||||
preselectedStaffId = (a.getEmployeeId() != null) ? a.getEmployeeId() : -1;
|
||||
preselectedPetId = a.getPetId() != null ? a.getPetId() : -1;
|
||||
preselectedServiceId = a.getServiceId() != null ? a.getServiceId() : -1;
|
||||
preselectedCustomerId = a.getCustomerId() != null ? a.getCustomerId() : -1;
|
||||
preselectedStoreId = a.getStoreId() != null ? a.getStoreId() : -1;
|
||||
preselectedStaffId = a.getEmployeeId() != null ? a.getEmployeeId() : -1;
|
||||
|
||||
binding.etAppointmentDate.setText(a.getAppointmentDate());
|
||||
|
||||
// Pre-fill time spinners
|
||||
String time = a.getAppointmentTime() != null ? a.getAppointmentTime() : "09:00";
|
||||
if (time.length() > 5) time = time.substring(0, 5);
|
||||
String[] parts = time.split(":");
|
||||
if (parts.length == 2) {
|
||||
try {
|
||||
int hour = Integer.parseInt(parts[0]);
|
||||
int min = Integer.parseInt(parts[1]);
|
||||
for (int i = 0; i < HOURS.length; i++)
|
||||
if (HOURS[i] == hour) { binding.spinnerHour.setSelection(i); break; }
|
||||
for (int i = 0; i < MINUTES.length; i++)
|
||||
if (MINUTES[i] == min) { binding.spinnerMinute.setSelection(i); break; }
|
||||
} catch (NumberFormatException ignored) {}
|
||||
}
|
||||
|
||||
// Match Title labels with backend values
|
||||
parseAndSetTimeSpinners(a.getAppointmentTime() != null ? a.getAppointmentTime() : "09:00");
|
||||
|
||||
String status = a.getAppointmentStatus();
|
||||
if (status != null && !status.isEmpty()) {
|
||||
String formattedStatus = status.substring(0, 1).toUpperCase() + status.substring(1).toLowerCase();
|
||||
SpinnerUtils.setSelectionByValue(binding.spinnerAppointmentStatus, formattedStatus);
|
||||
SpinnerUtils.setSelectionByValue(binding.spinnerAppointmentStatus, DateTimeUtils.formatStatusFromBackend(status));
|
||||
}
|
||||
|
||||
refreshPetSpinner();
|
||||
refreshServiceSpinner();
|
||||
refreshCustomerSpinner();
|
||||
refreshStoreSpinner();
|
||||
refreshStaffSpinner();
|
||||
} else if (resource.status == Resource.Status.ERROR) {
|
||||
Toast.makeText(getContext(), "Failed to load appointment: " + resource.message, Toast.LENGTH_SHORT).show();
|
||||
notifyDateTimeStatusChange();
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* Validates input and saves the appointment to the backend.
|
||||
*/
|
||||
private void saveAppointment() {
|
||||
if (binding.spinnerCustomer.getSelectedItemPosition() == 0) {
|
||||
Toast.makeText(getContext(), "Select a customer", Toast.LENGTH_SHORT).show(); return;
|
||||
}
|
||||
if (binding.spinnerStore.getSelectedItemPosition() == 0) {
|
||||
Toast.makeText(getContext(), "Select a store", Toast.LENGTH_SHORT).show(); return;
|
||||
}
|
||||
if (binding.spinnerPet.getSelectedItemPosition() == 0) {
|
||||
Toast.makeText(getContext(), "Select a pet", Toast.LENGTH_SHORT).show(); return;
|
||||
}
|
||||
if (binding.spinnerService.getSelectedItemPosition() == 0) {
|
||||
Toast.makeText(getContext(), "Select a service", Toast.LENGTH_SHORT).show(); return;
|
||||
}
|
||||
if (!validateRequiredFields()) return;
|
||||
|
||||
String date = binding.etAppointmentDate.getText().toString().trim();
|
||||
if (date.isEmpty()) {
|
||||
Toast.makeText(getContext(), "Select a date", Toast.LENGTH_SHORT).show(); return;
|
||||
}
|
||||
|
||||
CustomerDTO customer = customerList.get(binding.spinnerCustomer.getSelectedItemPosition() - 1);
|
||||
StoreDTO store = storeList.get(binding.spinnerStore.getSelectedItemPosition() - 1);
|
||||
PetDTO pet = petList.get(binding.spinnerPet.getSelectedItemPosition() - 1);
|
||||
ServiceDTO service = serviceList.get(binding.spinnerService.getSelectedItemPosition() - 1);
|
||||
|
||||
Long employeeId = null;
|
||||
if (binding.spinnerStaff.getSelectedItemPosition() > 0) {
|
||||
employeeId = staffList.get(binding.spinnerStaff.getSelectedItemPosition() - 1).getId();
|
||||
}
|
||||
|
||||
String time = String.format("%02d:%02d",
|
||||
HOURS[binding.spinnerHour.getSelectedItemPosition()],
|
||||
MINUTES[binding.spinnerMinute.getSelectedItemPosition()]);
|
||||
|
||||
// Get status and convert to uppercase for backend
|
||||
String time = buildTimeString();
|
||||
String status = binding.spinnerAppointmentStatus.getSelectedItem().toString().toUpperCase();
|
||||
|
||||
|
||||
// Validate future date+time if status is BOOKED
|
||||
if ("BOOKED".equalsIgnoreCase(status)) {
|
||||
try {
|
||||
String[] dateParts = date.split("-");
|
||||
String[] timeParts = time.split(":");
|
||||
Calendar selected = Calendar.getInstance();
|
||||
selected.set(
|
||||
Integer.parseInt(dateParts[0]),
|
||||
Integer.parseInt(dateParts[1]) - 1,
|
||||
Integer.parseInt(dateParts[2]),
|
||||
Integer.parseInt(timeParts[0]),
|
||||
Integer.parseInt(timeParts[1]),
|
||||
0
|
||||
);
|
||||
if (selected.before(Calendar.getInstance())) {
|
||||
DialogUtils.showInfoDialog(requireContext(), "Invalid Time",
|
||||
"Booked appointments must be in the future. " +
|
||||
"Please select a future date and time.");
|
||||
return;
|
||||
}
|
||||
} catch (Exception e) {
|
||||
Log.e("APPT_SAVE", "Date parse error: " + e.getMessage());
|
||||
}
|
||||
if (!viewModel.isValidFutureBooking(status, date, time)) {
|
||||
DialogUtils.showInfoDialog(requireContext(), "Invalid Time", "Booked appointments must be in the future.");
|
||||
return;
|
||||
}
|
||||
|
||||
// Build DTO with all required IDs
|
||||
AppointmentDTO dto = new AppointmentDTO(
|
||||
customer.getCustomerId(),
|
||||
store.getStoreId(),
|
||||
service.getServiceId(),
|
||||
employeeId,
|
||||
date,
|
||||
time,
|
||||
status,
|
||||
pet.getPetId()
|
||||
);
|
||||
|
||||
androidx.lifecycle.Observer<Resource<AppointmentDTO>> observer = resource -> {
|
||||
viewModel.saveAppointment(date, time, status).observe(getViewLifecycleOwner(), resource -> {
|
||||
if (resource == null) return;
|
||||
setLoading(resource.status == Resource.Status.LOADING);
|
||||
if (resource.status == Resource.Status.SUCCESS) {
|
||||
Toast.makeText(getContext(), isEditing ? "Updated" : "Saved", Toast.LENGTH_SHORT).show();
|
||||
AppointmentDetailViewModel.ViewState state = viewModel.getViewState().getValue();
|
||||
String message = (state != null && state.isEditing) ? "Updated" : "Saved";
|
||||
Toast.makeText(getContext(), message, Toast.LENGTH_SHORT).show();
|
||||
navigateBack();
|
||||
} else if (resource.status == Resource.Status.ERROR) {
|
||||
handleSaveError(resource.message);
|
||||
}
|
||||
};
|
||||
|
||||
if (isEditing) {
|
||||
appointmentViewModel.updateAppointment(appointmentId, dto).observe(getViewLifecycleOwner(), observer);
|
||||
} else {
|
||||
appointmentViewModel.createAppointment(dto).observe(getViewLifecycleOwner(), observer);
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
private boolean validateRequiredFields() {
|
||||
if (!InputValidator.isSpinnerSelected(binding.spinnerCustomer, "Customer")) return false;
|
||||
if (!InputValidator.isSpinnerSelected(binding.spinnerStore, "Store")) return false;
|
||||
if (!InputValidator.isSpinnerSelected(binding.spinnerPet, "Pet")) return false;
|
||||
if (!InputValidator.isSpinnerSelected(binding.spinnerService, "Service")) return false;
|
||||
if (!InputValidator.isNotEmpty(binding.etAppointmentDate, "Appointment Date")) return false;
|
||||
return true;
|
||||
}
|
||||
|
||||
private String buildTimeString() {
|
||||
return DateTimeUtils.formatTime(HOURS[binding.spinnerHour.getSelectedItemPosition()], MINUTES[binding.spinnerMinute.getSelectedItemPosition()]);
|
||||
}
|
||||
|
||||
/**
|
||||
* Handles errors that occur during the saving process.
|
||||
*/
|
||||
private void handleSaveError(String errorMessage) {
|
||||
if (errorMessage != null) {
|
||||
Log.e("APPT_SAVE", "Error: " + errorMessage);
|
||||
if (errorMessage.toLowerCase().contains("future")) {
|
||||
DialogUtils.showInfoDialog(requireContext(), "Invalid Date/Time",
|
||||
"Booked appointments must be scheduled in the future.");
|
||||
} else if (errorMessage.toLowerCase().contains("not available")) {
|
||||
showNoAvailabilityDialog();
|
||||
} else {
|
||||
Toast.makeText(getContext(), errorMessage, Toast.LENGTH_SHORT).show();
|
||||
}
|
||||
} else {
|
||||
Toast.makeText(getContext(), "Something went wrong", Toast.LENGTH_SHORT).show();
|
||||
}
|
||||
if (errorMessage != null && errorMessage.toLowerCase().contains("not available")) showNoAvailabilityDialog();
|
||||
else Toast.makeText(getContext(), errorMessage != null ? errorMessage : "Error saving", Toast.LENGTH_SHORT).show();
|
||||
}
|
||||
|
||||
/**
|
||||
* Shows a specialized dialog when a time slot is not available.
|
||||
*/
|
||||
private void showNoAvailabilityDialog() {
|
||||
new androidx.appcompat.app.AlertDialog.Builder(requireContext())
|
||||
.setTitle("No Availability")
|
||||
.setMessage("This time slot is already booked. Please choose a different time or date.")
|
||||
.setMessage("This time slot is already booked.")
|
||||
.setPositiveButton("Change Time", (d, w) -> d.dismiss())
|
||||
.setNegativeButton("Cancel Booking", (d, w) -> navigateBack())
|
||||
.setCancelable(false)
|
||||
.show();
|
||||
.setNegativeButton("Cancel Booking", (d, w) -> navigateBack()).show();
|
||||
}
|
||||
|
||||
/**
|
||||
* Shows a confirmation dialog and handles the deletion of an appointment.
|
||||
*/
|
||||
private void confirmDelete() {
|
||||
DialogUtils.showDeleteConfirmDialog(requireContext(), "Appointment", () ->
|
||||
appointmentViewModel.deleteAppointment(appointmentId).observe(getViewLifecycleOwner(), resource -> {
|
||||
if (resource.status == Resource.Status.SUCCESS) {
|
||||
Toast.makeText(getContext(), "Deleted", Toast.LENGTH_SHORT).show();
|
||||
navigateBack();
|
||||
} else if (resource.status == Resource.Status.ERROR) {
|
||||
Toast.makeText(getContext(), "Delete failed", Toast.LENGTH_SHORT).show();
|
||||
}
|
||||
viewModel.deleteAppointment().observe(getViewLifecycleOwner(), resource -> {
|
||||
if (resource == null) return;
|
||||
setLoading(resource.status == Resource.Status.LOADING);
|
||||
if (resource.status == Resource.Status.SUCCESS) navigateBack();
|
||||
}));
|
||||
}
|
||||
|
||||
/**
|
||||
* Navigates back to the previous screen.
|
||||
*/
|
||||
private void navigateBack() {
|
||||
NavHostFragment.findNavController(this).popBackStack();
|
||||
}
|
||||
|
||||
private void parseAndSetTimeSpinners(String time) {
|
||||
int[] parsedTime = DateTimeUtils.parseTimeString(time);
|
||||
if (parsedTime == null) return;
|
||||
SpinnerUtils.setSelectionByValueArray(binding.spinnerHour, HOURS, parsedTime[0]);
|
||||
SpinnerUtils.setSelectionByValueArray(binding.spinnerMinute, MINUTES, parsedTime[1]);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -8,24 +8,20 @@ import android.widget.Toast;
|
||||
|
||||
import androidx.annotation.NonNull;
|
||||
import androidx.annotation.Nullable;
|
||||
import androidx.appcompat.app.AlertDialog;
|
||||
import androidx.fragment.app.Fragment;
|
||||
import androidx.lifecycle.ViewModelProvider;
|
||||
import androidx.navigation.fragment.NavHostFragment;
|
||||
|
||||
import com.example.petstoremobile.databinding.FragmentInventoryDetailBinding;
|
||||
import com.example.petstoremobile.dtos.DropdownDTO;
|
||||
import com.example.petstoremobile.dtos.InventoryDTO;
|
||||
import com.example.petstoremobile.dtos.ProductDTO;
|
||||
import com.example.petstoremobile.dtos.StoreDTO;
|
||||
import com.example.petstoremobile.utils.DialogUtils;
|
||||
import com.example.petstoremobile.utils.InputValidator;
|
||||
import com.example.petstoremobile.utils.Resource;
|
||||
import com.example.petstoremobile.utils.SpinnerUtils;
|
||||
import com.example.petstoremobile.viewmodels.InventoryViewModel;
|
||||
import com.example.petstoremobile.viewmodels.ProductViewModel;
|
||||
import com.example.petstoremobile.viewmodels.StoreViewModel;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.List;
|
||||
import com.example.petstoremobile.utils.UIUtils;
|
||||
import com.example.petstoremobile.viewmodels.InventoryDetailViewModel;
|
||||
|
||||
import dagger.hilt.android.AndroidEntryPoint;
|
||||
|
||||
@@ -36,33 +32,17 @@ import dagger.hilt.android.AndroidEntryPoint;
|
||||
public class InventoryDetailFragment extends Fragment {
|
||||
|
||||
private FragmentInventoryDetailBinding binding;
|
||||
private InventoryDetailViewModel viewModel;
|
||||
|
||||
private InventoryViewModel inventoryViewModel;
|
||||
private ProductViewModel productViewModel;
|
||||
private StoreViewModel storeViewModel;
|
||||
|
||||
private boolean isEditing = false;
|
||||
private long inventoryId = -1;
|
||||
private long preselectedStoreId = -1;
|
||||
private long preselectedProductId = -1;
|
||||
|
||||
private List<StoreDTO> storeList = new ArrayList<>();
|
||||
private List<ProductDTO> productList = new ArrayList<>();
|
||||
|
||||
/**
|
||||
* Initializes the view models.
|
||||
*/
|
||||
@Override
|
||||
public void onCreate(Bundle savedInstanceState) {
|
||||
super.onCreate(savedInstanceState);
|
||||
inventoryViewModel = new ViewModelProvider(this).get(InventoryViewModel.class);
|
||||
productViewModel = new ViewModelProvider(this).get(ProductViewModel.class);
|
||||
storeViewModel = new ViewModelProvider(this).get(StoreViewModel.class);
|
||||
viewModel = new ViewModelProvider(this).get(InventoryDetailViewModel.class);
|
||||
}
|
||||
|
||||
/**
|
||||
* Inflates the layout.
|
||||
*/
|
||||
@Override
|
||||
public View onCreateView(@NonNull LayoutInflater inflater, ViewGroup container,
|
||||
Bundle savedInstanceState) {
|
||||
@@ -70,13 +50,11 @@ public class InventoryDetailFragment extends Fragment {
|
||||
return binding.getRoot();
|
||||
}
|
||||
|
||||
/**
|
||||
* Sets up UI components after the view is created.
|
||||
*/
|
||||
@Override
|
||||
public void onViewCreated(@NonNull View view, @Nullable Bundle savedInstanceState) {
|
||||
super.onViewCreated(view, savedInstanceState);
|
||||
|
||||
observeViewModel();
|
||||
loadSpinnersData();
|
||||
handleArguments();
|
||||
|
||||
@@ -85,64 +63,57 @@ public class InventoryDetailFragment extends Fragment {
|
||||
binding.btnDeleteInventory.setOnClickListener(v -> confirmDelete());
|
||||
}
|
||||
|
||||
private void observeViewModel() {
|
||||
viewModel.getStoreList().observe(getViewLifecycleOwner(), list -> refreshStoreSpinner());
|
||||
viewModel.getProductList().observe(getViewLifecycleOwner(), list -> refreshProductSpinner());
|
||||
}
|
||||
|
||||
private void setLoading(boolean loading) {
|
||||
if (binding != null && binding.progressBar != null) {
|
||||
binding.progressBar.setVisibility(loading ? View.VISIBLE : View.GONE);
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onDestroyView() {
|
||||
super.onDestroyView();
|
||||
binding = null;
|
||||
}
|
||||
|
||||
/**
|
||||
* Fetches required data for spinners from the backend.
|
||||
*/
|
||||
private void loadSpinnersData() {
|
||||
loadStores();
|
||||
loadProducts();
|
||||
}
|
||||
|
||||
/**
|
||||
* Loads the list of stores for the spinner.
|
||||
*/
|
||||
private void loadStores() {
|
||||
storeViewModel.getAllStores(0, 100).observe(getViewLifecycleOwner(), resource -> {
|
||||
viewModel.loadStores().observe(getViewLifecycleOwner(), resource -> {
|
||||
if (resource == null) return;
|
||||
setLoading(resource.status == Resource.Status.LOADING);
|
||||
if (resource.status == Resource.Status.SUCCESS && resource.data != null) {
|
||||
storeList = resource.data.getContent();
|
||||
refreshStoreSpinner();
|
||||
viewModel.setStoreList(resource.data);
|
||||
}
|
||||
});
|
||||
viewModel.loadProducts().observe(getViewLifecycleOwner(), resource -> {
|
||||
if (resource == null) return;
|
||||
setLoading(resource.status == Resource.Status.LOADING);
|
||||
if (resource.status == Resource.Status.SUCCESS && resource.data != null) {
|
||||
viewModel.setProductList(resource.data.getContent());
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
private void refreshStoreSpinner() {
|
||||
SpinnerUtils.populateSpinner(requireContext(), binding.spinnerInventoryStore, storeList,
|
||||
StoreDTO::getStoreName, "-- Select Store --",
|
||||
preselectedStoreId, StoreDTO::getStoreId);
|
||||
}
|
||||
|
||||
/**
|
||||
* Loads the list of products for the spinner.
|
||||
*/
|
||||
private void loadProducts() {
|
||||
productViewModel.getAllProducts(null, null, 0, 500, "prodName").observe(getViewLifecycleOwner(), resource -> {
|
||||
if (resource.status == Resource.Status.SUCCESS && resource.data != null) {
|
||||
productList = resource.data.getContent();
|
||||
refreshProductSpinner();
|
||||
}
|
||||
});
|
||||
SpinnerUtils.populateSpinner(requireContext(), binding.spinnerInventoryStore, viewModel.getStoreList().getValue(),
|
||||
DropdownDTO::getLabel, "-- Select Store --",
|
||||
preselectedStoreId, DropdownDTO::getId);
|
||||
}
|
||||
|
||||
private void refreshProductSpinner() {
|
||||
SpinnerUtils.populateSpinner(requireContext(), binding.spinnerInventoryProduct, productList,
|
||||
SpinnerUtils.populateSpinner(requireContext(), binding.spinnerInventoryProduct, viewModel.getProductList().getValue(),
|
||||
ProductDTO::getProdName, "-- Select Product --",
|
||||
preselectedProductId, ProductDTO::getProdId);
|
||||
}
|
||||
|
||||
/**
|
||||
* Handles fragment arguments to determine if we are in edit or add mode.
|
||||
*/
|
||||
private void handleArguments() {
|
||||
Bundle args = getArguments();
|
||||
if (args != null && args.containsKey("inventoryId")) {
|
||||
isEditing = true;
|
||||
inventoryId = args.getLong("inventoryId");
|
||||
long inventoryId = args.getLong("inventoryId");
|
||||
viewModel.setInventoryId(inventoryId);
|
||||
|
||||
binding.tvInventoryMode.setText("Edit Inventory");
|
||||
binding.tvInventoryId.setText("Inventory ID: " + inventoryId);
|
||||
@@ -152,7 +123,7 @@ public class InventoryDetailFragment extends Fragment {
|
||||
|
||||
loadInventoryData();
|
||||
} else {
|
||||
isEditing = false;
|
||||
viewModel.setInventoryId(-1);
|
||||
binding.tvInventoryMode.setText("Add Inventory");
|
||||
binding.tvInventoryId.setVisibility(View.GONE);
|
||||
binding.btnDeleteInventory.setVisibility(View.GONE);
|
||||
@@ -160,12 +131,10 @@ public class InventoryDetailFragment extends Fragment {
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Loads existing inventory data from the backend.
|
||||
*/
|
||||
private void loadInventoryData() {
|
||||
inventoryViewModel.getInventoryById(inventoryId).observe(getViewLifecycleOwner(), resource -> {
|
||||
viewModel.loadInventory().observe(getViewLifecycleOwner(), resource -> {
|
||||
if (resource == null) return;
|
||||
setLoading(resource.status == Resource.Status.LOADING);
|
||||
if (resource.status == Resource.Status.SUCCESS && resource.data != null) {
|
||||
InventoryDTO inv = resource.data;
|
||||
binding.etQuantity.setText(String.valueOf(inv.getQuantity()));
|
||||
@@ -180,95 +149,59 @@ public class InventoryDetailFragment extends Fragment {
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* Validates input and saves the current inventory item details to the backend.
|
||||
*/
|
||||
private void saveInventory() {
|
||||
if (binding.spinnerInventoryStore.getSelectedItemPosition() == 0) {
|
||||
Toast.makeText(getContext(), "Please select a store", Toast.LENGTH_SHORT).show();
|
||||
return;
|
||||
}
|
||||
if (binding.spinnerInventoryProduct.getSelectedItemPosition() == 0) {
|
||||
Toast.makeText(getContext(), "Please select a product", Toast.LENGTH_SHORT).show();
|
||||
return;
|
||||
}
|
||||
|
||||
if (!InputValidator.isNotEmpty(binding.etQuantity, "Quantity") ||
|
||||
!InputValidator.isPositiveInteger(binding.etQuantity, "Quantity")) {
|
||||
return;
|
||||
}
|
||||
if (!InputValidator.isSpinnerSelected(binding.spinnerInventoryStore, "Store")) return;
|
||||
if (!InputValidator.isSpinnerSelected(binding.spinnerInventoryProduct, "Product")) return;
|
||||
if (!InputValidator.isPositiveInteger(binding.etQuantity, "Quantity")) return;
|
||||
|
||||
int quantity = Integer.parseInt(binding.etQuantity.getText().toString().trim());
|
||||
StoreDTO store = storeList.get(binding.spinnerInventoryStore.getSelectedItemPosition() - 1);
|
||||
ProductDTO product = productList.get(binding.spinnerInventoryProduct.getSelectedItemPosition() - 1);
|
||||
DropdownDTO store = viewModel.getStoreList().getValue().get(binding.spinnerInventoryStore.getSelectedItemPosition() - 1);
|
||||
ProductDTO product = viewModel.getProductList().getValue().get(binding.spinnerInventoryProduct.getSelectedItemPosition() - 1);
|
||||
|
||||
InventoryDTO request = new InventoryDTO(product.getProdId(), store.getStoreId(), quantity);
|
||||
InventoryDTO request = new InventoryDTO(product.getProdId(), store.getId(), quantity);
|
||||
setButtonsEnabled(false);
|
||||
|
||||
if (isEditing) {
|
||||
inventoryViewModel.updateInventory(inventoryId, request).observe(getViewLifecycleOwner(), resource -> {
|
||||
viewModel.saveInventory(request).observe(getViewLifecycleOwner(), resource -> {
|
||||
if (resource == null) return;
|
||||
setLoading(resource.status == Resource.Status.LOADING);
|
||||
if (resource.status != Resource.Status.LOADING) {
|
||||
setButtonsEnabled(true);
|
||||
if (resource.status == Resource.Status.SUCCESS) {
|
||||
Toast.makeText(getContext(), "Inventory updated", Toast.LENGTH_SHORT).show();
|
||||
Toast.makeText(getContext(), viewModel.isEditing() ? "Inventory updated" : "Inventory created", Toast.LENGTH_SHORT).show();
|
||||
navigateBack();
|
||||
} else if (resource.status == Resource.Status.ERROR) {
|
||||
Toast.makeText(getContext(), "Error: " + resource.message, Toast.LENGTH_SHORT).show();
|
||||
}
|
||||
});
|
||||
} else {
|
||||
inventoryViewModel.createInventory(request).observe(getViewLifecycleOwner(), resource -> {
|
||||
setButtonsEnabled(true);
|
||||
if (resource.status == Resource.Status.SUCCESS) {
|
||||
Toast.makeText(getContext(), "Inventory created", Toast.LENGTH_SHORT).show();
|
||||
navigateBack();
|
||||
} else if (resource.status == Resource.Status.ERROR) {
|
||||
Toast.makeText(getContext(), "Error: " + resource.message, Toast.LENGTH_SHORT).show();
|
||||
}
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Shows a confirmation dialog before deleting an inventory item.
|
||||
*/
|
||||
private void confirmDelete() {
|
||||
new AlertDialog.Builder(requireContext())
|
||||
.setTitle("Delete inventory item?")
|
||||
.setMessage("This cannot be undone.")
|
||||
.setPositiveButton("Delete", (d, w) -> deleteInventory())
|
||||
.setNegativeButton("Cancel", null)
|
||||
.show();
|
||||
}
|
||||
|
||||
/**
|
||||
* Sends a request to the API to delete the inventory item.
|
||||
*/
|
||||
private void deleteInventory() {
|
||||
setButtonsEnabled(false);
|
||||
inventoryViewModel.deleteInventory(inventoryId).observe(getViewLifecycleOwner(), resource -> {
|
||||
setButtonsEnabled(true);
|
||||
if (resource.status == Resource.Status.SUCCESS) {
|
||||
Toast.makeText(getContext(), "Inventory deleted", Toast.LENGTH_SHORT).show();
|
||||
navigateBack();
|
||||
} else if (resource.status == Resource.Status.ERROR) {
|
||||
Toast.makeText(getContext(), "Delete failed: " + resource.message, Toast.LENGTH_SHORT).show();
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* Navigates back to the previous fragment.
|
||||
*/
|
||||
private void confirmDelete() {
|
||||
DialogUtils.showDeleteConfirmDialog(requireContext(), "Inventory Item", this::deleteInventory);
|
||||
}
|
||||
|
||||
private void deleteInventory() {
|
||||
setButtonsEnabled(false);
|
||||
viewModel.deleteInventory().observe(getViewLifecycleOwner(), resource -> {
|
||||
if (resource == null) return;
|
||||
setLoading(resource.status == Resource.Status.LOADING);
|
||||
if (resource.status != Resource.Status.LOADING) {
|
||||
setButtonsEnabled(true);
|
||||
if (resource.status == Resource.Status.SUCCESS) {
|
||||
Toast.makeText(getContext(), "Inventory deleted", Toast.LENGTH_SHORT).show();
|
||||
navigateBack();
|
||||
} else if (resource.status == Resource.Status.ERROR) {
|
||||
Toast.makeText(getContext(), "Delete failed: " + resource.message, Toast.LENGTH_SHORT).show();
|
||||
}
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
private void navigateBack() {
|
||||
NavHostFragment.findNavController(this).popBackStack();
|
||||
}
|
||||
|
||||
/**
|
||||
* Enables or disables action buttons.
|
||||
*/
|
||||
private void setButtonsEnabled(boolean enabled) {
|
||||
binding.btnSaveInventory.setEnabled(enabled);
|
||||
binding.btnDeleteInventory.setEnabled(enabled);
|
||||
binding.btnInventoryBack.setEnabled(enabled);
|
||||
UIUtils.setViewsEnabled(enabled, binding.btnSaveInventory, binding.btnDeleteInventory, binding.btnInventoryBack);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -18,20 +18,17 @@ import android.widget.Toast;
|
||||
|
||||
import com.example.petstoremobile.R;
|
||||
import com.example.petstoremobile.databinding.FragmentPetDetailBinding;
|
||||
import com.example.petstoremobile.dtos.CustomerDTO;
|
||||
import com.example.petstoremobile.dtos.DropdownDTO;
|
||||
import com.example.petstoremobile.dtos.PetDTO;
|
||||
import com.example.petstoremobile.dtos.StoreDTO;
|
||||
import com.example.petstoremobile.utils.ActivityLogger;
|
||||
import com.example.petstoremobile.utils.DateTimeUtils;
|
||||
import com.example.petstoremobile.utils.DialogUtils;
|
||||
import com.example.petstoremobile.utils.InputValidator;
|
||||
import com.example.petstoremobile.utils.Resource;
|
||||
import com.example.petstoremobile.utils.SpinnerUtils;
|
||||
import com.example.petstoremobile.viewmodels.CustomerViewModel;
|
||||
import com.example.petstoremobile.viewmodels.PetViewModel;
|
||||
import com.example.petstoremobile.viewmodels.StoreViewModel;
|
||||
import com.example.petstoremobile.utils.UIUtils;
|
||||
import com.example.petstoremobile.viewmodels.PetDetailViewModel;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.List;
|
||||
import java.util.Locale;
|
||||
|
||||
import dagger.hilt.android.AndroidEntryPoint;
|
||||
@@ -43,23 +40,15 @@ import dagger.hilt.android.AndroidEntryPoint;
|
||||
public class PetDetailFragment extends Fragment {
|
||||
|
||||
private FragmentPetDetailBinding binding;
|
||||
private long petId;
|
||||
private boolean isEditing = false;
|
||||
|
||||
private PetViewModel viewModel;
|
||||
private CustomerViewModel customerViewModel;
|
||||
private StoreViewModel storeViewModel;
|
||||
private List<CustomerDTO> customerList = new ArrayList<>();
|
||||
private List<StoreDTO> storeList = new ArrayList<>();
|
||||
private PetDetailViewModel viewModel;
|
||||
|
||||
private Long selectedCustomerId = null;
|
||||
private Long selectedStoreId = null;
|
||||
|
||||
@Override
|
||||
public void onCreate(Bundle savedInstanceState) {
|
||||
super.onCreate(savedInstanceState);
|
||||
viewModel = new ViewModelProvider(this).get(PetViewModel.class);
|
||||
customerViewModel = new ViewModelProvider(this).get(CustomerViewModel.class);
|
||||
storeViewModel = new ViewModelProvider(this).get(StoreViewModel.class);
|
||||
viewModel = new ViewModelProvider(this).get(PetDetailViewModel.class);
|
||||
}
|
||||
|
||||
@Override
|
||||
@@ -74,34 +63,54 @@ public class PetDetailFragment extends Fragment {
|
||||
super.onViewCreated(view, savedInstanceState);
|
||||
|
||||
setupSpinner();
|
||||
loadCustomers();
|
||||
loadStores();
|
||||
observeViewModel();
|
||||
handleArguments();
|
||||
|
||||
//set button click listeners
|
||||
binding.btnBack.setOnClickListener(v -> navigateBack());
|
||||
binding.btnSavePet.setOnClickListener(v -> savePet());
|
||||
binding.btnDeletePet.setOnClickListener(v -> deletePet());
|
||||
}
|
||||
|
||||
private void observeViewModel() {
|
||||
viewModel.getCustomerList().observe(getViewLifecycleOwner(), list -> updateCustomerSpinnerSelection());
|
||||
viewModel.getStoreList().observe(getViewLifecycleOwner(), list -> updateStoreSpinnerSelection());
|
||||
|
||||
viewModel.loadCustomers().observe(getViewLifecycleOwner(), resource -> {
|
||||
if (resource == null) return;
|
||||
setLoading(resource.status == Resource.Status.LOADING);
|
||||
if (resource.status == Resource.Status.SUCCESS && resource.data != null) {
|
||||
viewModel.setCustomerList(resource.data);
|
||||
}
|
||||
});
|
||||
|
||||
viewModel.loadStores().observe(getViewLifecycleOwner(), resource -> {
|
||||
if (resource == null) return;
|
||||
setLoading(resource.status == Resource.Status.LOADING);
|
||||
if (resource.status == Resource.Status.SUCCESS && resource.data != null) {
|
||||
viewModel.setStoreList(resource.data);
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
private void setLoading(boolean loading) {
|
||||
if (binding != null && binding.progressBar != null) {
|
||||
binding.progressBar.setVisibility(loading ? View.VISIBLE : View.GONE);
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onDestroyView() {
|
||||
super.onDestroyView();
|
||||
binding = null;
|
||||
}
|
||||
|
||||
/**
|
||||
* Handles the saving of pet data (adding/updating).
|
||||
*/
|
||||
private void savePet() {
|
||||
// Validates all fields using InputValidator
|
||||
if (!InputValidator.isNotEmpty(binding.etPetName, "Pet Name")) return;
|
||||
if (!InputValidator.isNotEmpty(binding.etPetSpecies, "Species")) return;
|
||||
if (!InputValidator.isNotEmpty(binding.etPetBreed, "Breed")) return;
|
||||
if (!InputValidator.isPositiveInteger(binding.etPetAge, "Age")) return;
|
||||
if (!InputValidator.isPositiveDecimal(binding.etPetPrice, "Price")) return;
|
||||
|
||||
//get all the values from the fields
|
||||
String name = binding.etPetName.getText().toString().trim();
|
||||
String species = binding.etPetSpecies.getText().toString().trim();
|
||||
String breed = binding.etPetBreed.getText().toString().trim();
|
||||
@@ -109,37 +118,27 @@ public class PetDetailFragment extends Fragment {
|
||||
double price = Double.parseDouble(binding.etPetPrice.getText().toString().trim());
|
||||
String status = binding.spinnerPetStatus.getSelectedItem().toString();
|
||||
|
||||
// Get selected customer
|
||||
Long customerId = null;
|
||||
int customerPos = binding.spinnerCustomer.getSelectedItemPosition();
|
||||
if (customerPos > 0) { // 0 means no customer for pet
|
||||
customerId = customerList.get(customerPos - 1).getCustomerId();
|
||||
if (binding.spinnerCustomer.getSelectedItemPosition() > 0) {
|
||||
customerId = viewModel.getCustomerList().getValue().get(binding.spinnerCustomer.getSelectedItemPosition() - 1).getId();
|
||||
}
|
||||
|
||||
// Get selected store
|
||||
Long storeId = null;
|
||||
int storePos = binding.spinnerStore.getSelectedItemPosition();
|
||||
if (storePos > 0) {
|
||||
storeId = storeList.get(storePos - 1).getStoreId();
|
||||
if (binding.spinnerStore.getSelectedItemPosition() > 0) {
|
||||
storeId = viewModel.getStoreList().getValue().get(binding.spinnerStore.getSelectedItemPosition() - 1).getId();
|
||||
}
|
||||
|
||||
// Validation: If status is Available, a store must be selected
|
||||
if ("Available".equalsIgnoreCase(status)) {
|
||||
if (!InputValidator.isSpinnerSelected(binding.spinnerStore, "Store")) return;
|
||||
}
|
||||
|
||||
// Validation: If status is Owned, an owner must be selected
|
||||
if ("Owned".equalsIgnoreCase(status)) {
|
||||
if (!InputValidator.isSpinnerSelected(binding.spinnerCustomer, "Owner")) return;
|
||||
}
|
||||
|
||||
// Validation: If status is Adopted, an owner and store must be selected
|
||||
if ("Adopted".equalsIgnoreCase(status)) {
|
||||
if (!InputValidator.isSpinnerSelected(binding.spinnerCustomer, "Owner")) return;
|
||||
if (!InputValidator.isSpinnerSelected(binding.spinnerStore, "Store")) return;
|
||||
}
|
||||
|
||||
//create a pet object to send to the API
|
||||
PetDTO petDTO = new PetDTO();
|
||||
petDTO.setPetName(name);
|
||||
petDTO.setPetSpecies(species);
|
||||
@@ -150,107 +149,74 @@ public class PetDetailFragment extends Fragment {
|
||||
petDTO.setCustomerId(customerId);
|
||||
petDTO.setStoreId(storeId);
|
||||
|
||||
//check if the pet is being edited or added
|
||||
if (isEditing) {
|
||||
// Update existing pet
|
||||
petDTO.setPetId(petId);
|
||||
viewModel.updatePet(petId, petDTO).observe(getViewLifecycleOwner(), resource -> {
|
||||
if (resource.status == Resource.Status.SUCCESS) {
|
||||
ActivityLogger.logChange(requireContext(), "Pet", "UPDATED", (int) petId);
|
||||
viewModel.savePet(petDTO).observe(getViewLifecycleOwner(), resource -> {
|
||||
if (resource == null) return;
|
||||
setLoading(resource.status == Resource.Status.LOADING);
|
||||
if (resource.status == Resource.Status.SUCCESS) {
|
||||
if (viewModel.isEditing()) {
|
||||
ActivityLogger.logChange(requireContext(), "Pet", "UPDATED", (int) viewModel.getPetId());
|
||||
Toast.makeText(getContext(), "Pet updated successfully!", Toast.LENGTH_SHORT).show();
|
||||
navigateToPetList();
|
||||
} else if (resource.status == Resource.Status.ERROR) {
|
||||
Toast.makeText(getContext(), "Error: " + resource.message, Toast.LENGTH_SHORT).show();
|
||||
}
|
||||
});
|
||||
} else {
|
||||
// Add new pet
|
||||
viewModel.createPet(petDTO).observe(getViewLifecycleOwner(), resource -> {
|
||||
if (resource.status == Resource.Status.SUCCESS) {
|
||||
} else {
|
||||
ActivityLogger.log(requireContext(), "Added new Pet: " + name);
|
||||
Toast.makeText(getContext(), "Pet added successfully!", Toast.LENGTH_SHORT).show();
|
||||
navigateToPetList();
|
||||
} else if (resource.status == Resource.Status.ERROR) {
|
||||
Toast.makeText(getContext(), "Error: " + resource.message, Toast.LENGTH_SHORT).show();
|
||||
}
|
||||
});
|
||||
}
|
||||
navigateToPetList();
|
||||
} else if (resource.status == Resource.Status.ERROR) {
|
||||
Toast.makeText(getContext(), "Error: " + resource.message, Toast.LENGTH_SHORT).show();
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* Displays a confirmation dialog and handles the deletion of a pet.
|
||||
*/
|
||||
private void deletePet() {
|
||||
DialogUtils.showDeleteConfirmDialog(requireContext(), "Pet", () ->
|
||||
viewModel.deletePet(petId).observe(getViewLifecycleOwner(), resource -> {
|
||||
DialogUtils.showDeleteConfirmDialog(requireContext(), "Pet", () -> {
|
||||
viewModel.deletePet().observe(getViewLifecycleOwner(), resource -> {
|
||||
if (resource == null) return;
|
||||
setLoading(resource.status == Resource.Status.LOADING);
|
||||
if (resource.status == Resource.Status.SUCCESS) {
|
||||
ActivityLogger.logChange(requireContext(), "Pet", "DELETED", (int) petId);
|
||||
ActivityLogger.logChange(requireContext(), "Pet", "DELETED", (int) viewModel.getPetId());
|
||||
Toast.makeText(getContext(), "Pet deleted successfully!", Toast.LENGTH_SHORT).show();
|
||||
navigateToPetList();
|
||||
} else if (resource.status == Resource.Status.ERROR) {
|
||||
Toast.makeText(getContext(), "Delete failed: " + resource.message, Toast.LENGTH_SHORT).show();
|
||||
}
|
||||
}));
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* Navigates back to the pet list screen.
|
||||
*/
|
||||
private void navigateToPetList() {
|
||||
NavHostFragment.findNavController(this).popBackStack(R.id.nav_pet, false);
|
||||
}
|
||||
|
||||
/**
|
||||
* Navigates back to the previous screen.
|
||||
*/
|
||||
private void navigateBack() {
|
||||
NavHostFragment.findNavController(this).popBackStack();
|
||||
}
|
||||
|
||||
/**
|
||||
* Handles arguments passed to the fragment to determine if it's in edit or add mode.
|
||||
*/
|
||||
private void handleArguments() {
|
||||
// Pet is being edited if the bundle contains a petId
|
||||
if (getArguments() != null && getArguments().containsKey("petId")) {
|
||||
// Get pet data from arguments and populate fields
|
||||
isEditing = true;
|
||||
petId = getArguments().getLong("petId");
|
||||
long petId = getArguments().getLong("petId");
|
||||
viewModel.setPetId(petId);
|
||||
binding.tvMode.setText("Edit Pet");
|
||||
binding.tvPetId.setText("ID: " + petId);
|
||||
binding.tvPetId.setText(DateTimeUtils.formatId(petId));
|
||||
binding.tvPetId.setVisibility(View.VISIBLE);
|
||||
binding.btnDeletePet.setVisibility(View.VISIBLE);
|
||||
|
||||
// Disable species and breed fields in edit mode
|
||||
binding.etPetSpecies.setEnabled(false);
|
||||
binding.etPetBreed.setEnabled(false);
|
||||
binding.etPetSpecies.setAlpha(0.5f);
|
||||
binding.etPetBreed.setAlpha(0.5f);
|
||||
|
||||
UIUtils.setViewsEnabled(false, binding.etPetSpecies, binding.etPetBreed);
|
||||
loadPetData();
|
||||
} else {
|
||||
// Pet is being added
|
||||
// Set default values for add a new pet
|
||||
isEditing = false;
|
||||
viewModel.setPetId(-1);
|
||||
binding.tvMode.setText("Add Pet");
|
||||
binding.tvPetId.setVisibility(View.GONE);
|
||||
binding.btnDeletePet.setVisibility(View.GONE);
|
||||
binding.btnSavePet.setText("Add");
|
||||
|
||||
// Enable species and breed fields in edit mode
|
||||
binding.etPetSpecies.setEnabled(true);
|
||||
binding.etPetBreed.setEnabled(true);
|
||||
binding.etPetSpecies.setAlpha(1.0f);
|
||||
binding.etPetBreed.setAlpha(1.0f);
|
||||
UIUtils.setViewsEnabled(true, binding.etPetSpecies, binding.etPetBreed);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Fetches specific pet details from the backend using the ID.
|
||||
*/
|
||||
private void loadPetData() {
|
||||
viewModel.getPetById(petId).observe(getViewLifecycleOwner(), resource -> {
|
||||
viewModel.loadPet().observe(getViewLifecycleOwner(), resource -> {
|
||||
if (resource == null) return;
|
||||
setLoading(resource.status == Resource.Status.LOADING);
|
||||
if (resource.status == Resource.Status.SUCCESS && resource.data != null) {
|
||||
PetDTO p = resource.data;
|
||||
binding.etPetName.setText(p.getPetName());
|
||||
@@ -273,63 +239,30 @@ public class PetDetailFragment extends Fragment {
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* Fetches the list of customers and populates the spinner.
|
||||
*/
|
||||
private void loadCustomers() {
|
||||
customerViewModel.getAllCustomers(0, 1000).observe(getViewLifecycleOwner(), resource -> {
|
||||
if (resource != null && resource.status == Resource.Status.SUCCESS && resource.data != null) {
|
||||
customerList = resource.data.getContent();
|
||||
updateCustomerSpinnerSelection();
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* Fetches the list of stores and populates the spinner.
|
||||
*/
|
||||
private void loadStores() {
|
||||
storeViewModel.getAllStores(0, 1000).observe(getViewLifecycleOwner(), resource -> {
|
||||
if (resource != null && resource.status == Resource.Status.SUCCESS && resource.data != null) {
|
||||
storeList = resource.data.getContent();
|
||||
updateStoreSpinnerSelection();
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* Updates the customer spinner with the current list and sets the selection if needed.
|
||||
*/
|
||||
private void updateCustomerSpinnerSelection() {
|
||||
SpinnerUtils.populateSpinner(
|
||||
requireContext(),
|
||||
binding.spinnerCustomer,
|
||||
customerList,
|
||||
CustomerDTO::getFullName,
|
||||
viewModel.getCustomerList().getValue(),
|
||||
DropdownDTO::getLabel,
|
||||
"No Owner",
|
||||
selectedCustomerId,
|
||||
CustomerDTO::getCustomerId
|
||||
DropdownDTO::getId
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* Updates the store spinner with the current list and sets the selection if needed.
|
||||
*/
|
||||
private void updateStoreSpinnerSelection() {
|
||||
SpinnerUtils.populateSpinner(
|
||||
requireContext(),
|
||||
binding.spinnerStore,
|
||||
storeList,
|
||||
StoreDTO::getStoreName,
|
||||
viewModel.getStoreList().getValue(),
|
||||
DropdownDTO::getLabel,
|
||||
"None",
|
||||
selectedStoreId,
|
||||
StoreDTO::getStoreId
|
||||
DropdownDTO::getId
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* Initializes the spinner for pet status selection.
|
||||
*/
|
||||
private void setupSpinner() {
|
||||
SpinnerUtils.setupStringSpinner(requireContext(), binding.spinnerPetStatus,
|
||||
new String[]{"Available", "Adopted", "Owned"});
|
||||
@@ -339,28 +272,21 @@ public class PetDetailFragment extends Fragment {
|
||||
public void onItemSelected(AdapterView<?> parent, View view, int position, long id) {
|
||||
String status = parent.getItemAtPosition(position).toString();
|
||||
|
||||
// Clear any existing error icons when status changes
|
||||
clearSpinnerError(binding.spinnerCustomer);
|
||||
clearSpinnerError(binding.spinnerStore);
|
||||
|
||||
//Disable the customer spinner if the status is "Available"
|
||||
if ("Available".equalsIgnoreCase(status)) {
|
||||
binding.spinnerCustomer.setSelection(0);
|
||||
binding.spinnerCustomer.setEnabled(false);
|
||||
binding.spinnerCustomer.setAlpha(0.5f);
|
||||
UIUtils.setViewsEnabled(false, binding.spinnerCustomer);
|
||||
} else {
|
||||
binding.spinnerCustomer.setEnabled(true);
|
||||
binding.spinnerCustomer.setAlpha(1.0f);
|
||||
UIUtils.setViewsEnabled(true, binding.spinnerCustomer);
|
||||
}
|
||||
|
||||
//Disable the store spinner if the status is "Owned"
|
||||
if ("Owned".equalsIgnoreCase(status)) {
|
||||
binding.spinnerStore.setSelection(0);
|
||||
binding.spinnerStore.setEnabled(false);
|
||||
binding.spinnerStore.setAlpha(0.5f);
|
||||
UIUtils.setViewsEnabled(false, binding.spinnerStore);
|
||||
} else {
|
||||
binding.spinnerStore.setEnabled(true);
|
||||
binding.spinnerStore.setAlpha(1.0f);
|
||||
UIUtils.setViewsEnabled(true, binding.spinnerStore);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -370,9 +296,6 @@ public class PetDetailFragment extends Fragment {
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* Clears error messages from a Spinner's selected view.
|
||||
*/
|
||||
private void clearSpinnerError(Spinner spinner) {
|
||||
View selectedView = spinner.getSelectedView();
|
||||
if (selectedView instanceof TextView) {
|
||||
|
||||
@@ -17,7 +17,8 @@ import com.example.petstoremobile.api.*;
|
||||
import com.example.petstoremobile.api.auth.TokenManager;
|
||||
import com.example.petstoremobile.databinding.FragmentProductDetailBinding;
|
||||
import com.example.petstoremobile.dtos.*;
|
||||
import com.example.petstoremobile.viewmodels.ProductViewModel;
|
||||
import com.example.petstoremobile.viewmodels.ProductDetailViewModel;
|
||||
import com.example.petstoremobile.utils.DateTimeUtils;
|
||||
import com.example.petstoremobile.utils.DialogUtils;
|
||||
import com.example.petstoremobile.utils.FileUtils;
|
||||
import com.example.petstoremobile.utils.GlideUtils;
|
||||
@@ -31,7 +32,6 @@ import java.math.BigDecimal;
|
||||
import java.util.*;
|
||||
|
||||
import javax.inject.Inject;
|
||||
|
||||
import javax.inject.Named;
|
||||
|
||||
import dagger.hilt.android.AndroidEntryPoint;
|
||||
@@ -46,29 +46,22 @@ import okhttp3.RequestBody;
|
||||
public class ProductDetailFragment extends Fragment {
|
||||
|
||||
private FragmentProductDetailBinding binding;
|
||||
private ProductDetailViewModel viewModel;
|
||||
private ImagePickerHelper imagePickerHelper;
|
||||
|
||||
private long prodId = -1;
|
||||
private boolean isEditing = false;
|
||||
private long preselectedCategoryId = -1;
|
||||
private boolean hasImage = false;
|
||||
private boolean isImageChanged = false;
|
||||
private boolean isImageRemoved = false;
|
||||
|
||||
private List<CategoryDTO> categoryList = new ArrayList<>();
|
||||
private Uri photoUri;
|
||||
private ProductViewModel viewModel;
|
||||
private ImagePickerHelper imagePickerHelper;
|
||||
|
||||
@Inject @Named("baseUrl") String baseUrl;
|
||||
@Inject TokenManager tokenManager;
|
||||
|
||||
/**
|
||||
* Initializes activity launchers and the ImagePickerHelper.
|
||||
*/
|
||||
@Override
|
||||
public void onCreate(Bundle savedInstanceState) {
|
||||
super.onCreate(savedInstanceState);
|
||||
viewModel = new ViewModelProvider(this).get(ProductViewModel.class);
|
||||
viewModel = new ViewModelProvider(this).get(ProductDetailViewModel.class);
|
||||
|
||||
imagePickerHelper = new ImagePickerHelper(this, "product_photo.jpg", new ImagePickerHelper.ImagePickerListener() {
|
||||
@Override
|
||||
@@ -95,9 +88,6 @@ public class ProductDetailFragment extends Fragment {
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* Inflates the layout.
|
||||
*/
|
||||
@Override
|
||||
public View onCreateView(@NonNull LayoutInflater inflater, ViewGroup container,
|
||||
Bundle savedInstanceState) {
|
||||
@@ -105,14 +95,11 @@ public class ProductDetailFragment extends Fragment {
|
||||
return binding.getRoot();
|
||||
}
|
||||
|
||||
/**
|
||||
* Sets up UI components and listeners after the view is created.
|
||||
*/
|
||||
@Override
|
||||
public void onViewCreated(@NonNull View view, @Nullable Bundle savedInstanceState) {
|
||||
super.onViewCreated(view, savedInstanceState);
|
||||
|
||||
loadCategories();
|
||||
observeViewModel();
|
||||
handleArguments();
|
||||
|
||||
binding.btnProductBack.setOnClickListener(v -> navigateBack());
|
||||
@@ -121,41 +108,49 @@ public class ProductDetailFragment extends Fragment {
|
||||
binding.ivProductImage.setOnClickListener(v -> imagePickerHelper.showImagePickerDialog("Select Product Image", hasImage));
|
||||
}
|
||||
|
||||
private void observeViewModel() {
|
||||
viewModel.getCategoryList().observe(getViewLifecycleOwner(), list -> updateCategorySpinner());
|
||||
|
||||
viewModel.loadCategories().observe(getViewLifecycleOwner(), resource -> {
|
||||
if (resource == null) return;
|
||||
setLoading(resource.status == Resource.Status.LOADING);
|
||||
if (resource.status == Resource.Status.SUCCESS && resource.data != null) {
|
||||
viewModel.setCategoryList(resource.data.getContent());
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
private void setLoading(boolean loading) {
|
||||
if (binding != null && binding.progressBar != null) {
|
||||
binding.progressBar.setVisibility(loading ? View.VISIBLE : View.GONE);
|
||||
}
|
||||
}
|
||||
|
||||
private void updateCategorySpinner() {
|
||||
SpinnerUtils.populateSpinner(requireContext(), binding.spinnerProductCategory, viewModel.getCategoryList().getValue(),
|
||||
CategoryDTO::getCategoryName, "-- Select Category --",
|
||||
preselectedCategoryId, CategoryDTO::getCategoryId);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onDestroyView() {
|
||||
super.onDestroyView();
|
||||
binding = null;
|
||||
}
|
||||
|
||||
/**
|
||||
* Fetches all product categories for the selection spinner.
|
||||
*/
|
||||
private void loadCategories() {
|
||||
viewModel.getAllCategories(0, 100).observe(getViewLifecycleOwner(), resource -> {
|
||||
if (resource != null && resource.status == Resource.Status.SUCCESS && resource.data != null) {
|
||||
categoryList = resource.data.getContent();
|
||||
SpinnerUtils.populateSpinner(requireContext(), binding.spinnerProductCategory, categoryList,
|
||||
CategoryDTO::getCategoryName, "-- Select Category --",
|
||||
preselectedCategoryId, CategoryDTO::getCategoryId);
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* Checks if the fragment was opened with existing product data for editing.
|
||||
*/
|
||||
private void handleArguments() {
|
||||
Bundle a = getArguments();
|
||||
if (a != null && a.containsKey("prodId")) {
|
||||
isEditing = true;
|
||||
prodId = a.getLong("prodId");
|
||||
long prodId = a.getLong("prodId");
|
||||
viewModel.setProdId(prodId);
|
||||
binding.tvProductMode.setText("Edit Product");
|
||||
binding.tvProductId.setText("ID: " + prodId);
|
||||
binding.tvProductId.setText(DateTimeUtils.formatId(prodId));
|
||||
binding.tvProductId.setVisibility(View.VISIBLE);
|
||||
binding.btnDeleteProduct.setVisibility(View.VISIBLE);
|
||||
loadProductData();
|
||||
loadProductImage();
|
||||
} else {
|
||||
viewModel.setProdId(-1);
|
||||
binding.tvProductMode.setText("Add Product");
|
||||
binding.btnDeleteProduct.setVisibility(View.GONE);
|
||||
binding.tvProductId.setVisibility(View.GONE);
|
||||
@@ -163,36 +158,25 @@ public class ProductDetailFragment extends Fragment {
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Loads the product data from the backend.
|
||||
*/
|
||||
private void loadProductData() {
|
||||
viewModel.getProductById(prodId).observe(getViewLifecycleOwner(), resource -> {
|
||||
viewModel.loadProduct().observe(getViewLifecycleOwner(), resource -> {
|
||||
if (resource == null) return;
|
||||
setLoading(resource.status == Resource.Status.LOADING);
|
||||
if (resource.status == Resource.Status.SUCCESS && resource.data != null) {
|
||||
ProductDTO p = resource.data;
|
||||
binding.etProductName.setText(p.getProdName());
|
||||
binding.etProductDesc.setText(p.getProdDesc());
|
||||
binding.etProductPrice.setText(p.getProdPrice() != null ? p.getProdPrice().toString() : "");
|
||||
preselectedCategoryId = p.getCategoryId() != null ? p.getCategoryId() : -1;
|
||||
|
||||
// Refresh spinner selection once data is loaded
|
||||
if (!categoryList.isEmpty()) {
|
||||
SpinnerUtils.populateSpinner(requireContext(), binding.spinnerProductCategory, categoryList,
|
||||
CategoryDTO::getCategoryName, "-- Select Category --",
|
||||
preselectedCategoryId, CategoryDTO::getCategoryId);
|
||||
}
|
||||
updateCategorySpinner();
|
||||
} else if (resource.status == Resource.Status.ERROR) {
|
||||
Toast.makeText(getContext(), "Failed to load product: " + resource.message, Toast.LENGTH_SHORT).show();
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* Loads the product image from the backend.
|
||||
*/
|
||||
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, viewModel.getProdId());
|
||||
String token = tokenManager.getToken();
|
||||
|
||||
GlideUtils.loadImageWithToken(requireContext(), binding.ivProductImage, imageUrl, token, R.drawable.placeholder2, new GlideUtils.ImageLoadListener() {
|
||||
@@ -208,13 +192,12 @@ public class ProductDetailFragment extends Fragment {
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* Performs image related actions (upload/delete) after product details are saved.
|
||||
*/
|
||||
private void performPendingImageActions(String successMsg) {
|
||||
if (isImageRemoved) {
|
||||
viewModel.deleteProductImage(prodId).observe(getViewLifecycleOwner(), resource -> {
|
||||
if (resource != null && resource.status != Resource.Status.LOADING) {
|
||||
viewModel.deleteProductImage().observe(getViewLifecycleOwner(), resource -> {
|
||||
if (resource == null) return;
|
||||
setLoading(resource.status == Resource.Status.LOADING);
|
||||
if (resource.status != Resource.Status.LOADING) {
|
||||
if (resource.status == Resource.Status.SUCCESS) {
|
||||
Toast.makeText(getContext(), successMsg, Toast.LENGTH_SHORT).show();
|
||||
} else {
|
||||
@@ -231,9 +214,6 @@ public class ProductDetailFragment extends Fragment {
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Uploads the selected image file to the server.
|
||||
*/
|
||||
private void uploadProductImageAndNavigate(Uri uri, String successMsg) {
|
||||
File file = FileUtils.getFileFromUri(requireContext(), uri);
|
||||
if (file == null) {
|
||||
@@ -245,8 +225,10 @@ public class ProductDetailFragment extends Fragment {
|
||||
RequestBody requestFile = RequestBody.create(file, MediaType.parse(requireContext().getContentResolver().getType(uri)));
|
||||
MultipartBody.Part body = MultipartBody.Part.createFormData("image", file.getName(), requestFile);
|
||||
|
||||
viewModel.uploadProductImage(prodId, body).observe(getViewLifecycleOwner(), resource -> {
|
||||
if (resource != null && resource.status != Resource.Status.LOADING) {
|
||||
viewModel.uploadProductImage(body).observe(getViewLifecycleOwner(), resource -> {
|
||||
if (resource == null) return;
|
||||
setLoading(resource.status == Resource.Status.LOADING);
|
||||
if (resource.status != Resource.Status.LOADING) {
|
||||
if (resource.status == Resource.Status.SUCCESS) {
|
||||
Toast.makeText(getContext(), successMsg, Toast.LENGTH_SHORT).show();
|
||||
} else {
|
||||
@@ -257,69 +239,47 @@ public class ProductDetailFragment extends Fragment {
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* Validates input fields and saves product information to the backend.
|
||||
*/
|
||||
private void saveProduct() {
|
||||
if (!InputValidator.isNotEmpty(binding.etProductName, "Product Name")) return;
|
||||
|
||||
if (binding.spinnerProductCategory.getSelectedItemPosition() == 0) {
|
||||
Toast.makeText(getContext(), "Select a category", Toast.LENGTH_SHORT).show(); return;
|
||||
}
|
||||
|
||||
if (!InputValidator.isNotEmpty(binding.etProductPrice, "Price") ||
|
||||
!InputValidator.isPositiveDecimal(binding.etProductPrice, "Price")) {
|
||||
return;
|
||||
}
|
||||
if (!InputValidator.isSpinnerSelected(binding.spinnerProductCategory, "Category")) return;
|
||||
if (!InputValidator.isPositiveDecimal(binding.etProductPrice, "Price")) return;
|
||||
|
||||
String name = binding.etProductName.getText().toString().trim();
|
||||
String desc = binding.etProductDesc.getText().toString().trim();
|
||||
BigDecimal price = new BigDecimal(binding.etProductPrice.getText().toString().trim());
|
||||
|
||||
CategoryDTO category = categoryList.get(binding.spinnerProductCategory.getSelectedItemPosition() - 1);
|
||||
CategoryDTO category = viewModel.getCategoryList().getValue().get(binding.spinnerProductCategory.getSelectedItemPosition() - 1);
|
||||
ProductDTO dto = new ProductDTO(name, category.getCategoryId(), desc, price);
|
||||
|
||||
if (isEditing) {
|
||||
viewModel.updateProduct(prodId, dto).observe(getViewLifecycleOwner(), resource -> {
|
||||
if (resource != null && resource.status != Resource.Status.LOADING) {
|
||||
if (resource.status == Resource.Status.SUCCESS) {
|
||||
performPendingImageActions("Updated");
|
||||
} else {
|
||||
Toast.makeText(getContext(), "Error: " + resource.message, Toast.LENGTH_SHORT).show();
|
||||
viewModel.saveProduct(dto).observe(getViewLifecycleOwner(), resource -> {
|
||||
if (resource == null) return;
|
||||
setLoading(resource.status == Resource.Status.LOADING);
|
||||
if (resource.status != Resource.Status.LOADING) {
|
||||
if (resource.status == Resource.Status.SUCCESS) {
|
||||
if (resource.data != null) {
|
||||
viewModel.setProdId(resource.data.getProdId());
|
||||
}
|
||||
performPendingImageActions(viewModel.isEditing() ? "Updated" : "Saved");
|
||||
} else {
|
||||
Toast.makeText(getContext(), "Error: " + resource.message, Toast.LENGTH_SHORT).show();
|
||||
}
|
||||
});
|
||||
} else {
|
||||
viewModel.createProduct(dto).observe(getViewLifecycleOwner(), resource -> {
|
||||
if (resource != null && resource.status != Resource.Status.LOADING) {
|
||||
if (resource.status == Resource.Status.SUCCESS && resource.data != null) {
|
||||
prodId = resource.data.getProdId();
|
||||
performPendingImageActions("Saved");
|
||||
} else {
|
||||
Toast.makeText(getContext(), "Error saving: " + resource.message, Toast.LENGTH_SHORT).show();
|
||||
}
|
||||
}
|
||||
});
|
||||
}
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* Displays a confirmation dialog before deleting the product.
|
||||
*/
|
||||
private void confirmDelete() {
|
||||
DialogUtils.showDeleteConfirmDialog(requireContext(), "Product", () ->
|
||||
viewModel.deleteProduct(prodId).observe(getViewLifecycleOwner(), resource -> {
|
||||
if (resource != null && resource.status == Resource.Status.SUCCESS) {
|
||||
viewModel.deleteProduct().observe(getViewLifecycleOwner(), resource -> {
|
||||
if (resource == null) return;
|
||||
setLoading(resource.status == Resource.Status.LOADING);
|
||||
if (resource.status == Resource.Status.SUCCESS) {
|
||||
navigateBack();
|
||||
} else if (resource != null && resource.status == Resource.Status.ERROR) {
|
||||
} else if (resource.status == Resource.Status.ERROR) {
|
||||
Toast.makeText(getContext(), "Delete failed: " + resource.message, Toast.LENGTH_SHORT).show();
|
||||
}
|
||||
}));
|
||||
}
|
||||
|
||||
/**
|
||||
* Navigates back to the previous fragment.
|
||||
*/
|
||||
private void navigateBack() {
|
||||
NavHostFragment.findNavController(this).popBackStack();
|
||||
}
|
||||
|
||||
@@ -15,9 +15,8 @@ import com.example.petstoremobile.utils.DialogUtils;
|
||||
import com.example.petstoremobile.utils.InputValidator;
|
||||
import com.example.petstoremobile.utils.Resource;
|
||||
import com.example.petstoremobile.utils.SpinnerUtils;
|
||||
import com.example.petstoremobile.viewmodels.ProductSupplierViewModel;
|
||||
import com.example.petstoremobile.viewmodels.ProductViewModel;
|
||||
import com.example.petstoremobile.viewmodels.SupplierViewModel;
|
||||
import com.example.petstoremobile.utils.UIUtils;
|
||||
import com.example.petstoremobile.viewmodels.ProductSupplierDetailViewModel;
|
||||
|
||||
import java.math.BigDecimal;
|
||||
import java.util.*;
|
||||
@@ -31,26 +30,15 @@ import dagger.hilt.android.AndroidEntryPoint;
|
||||
public class ProductSupplierDetailFragment extends Fragment {
|
||||
|
||||
private FragmentProductSupplierDetailBinding binding;
|
||||
private ProductSupplierDetailViewModel viewModel;
|
||||
|
||||
private boolean isEditing = false;
|
||||
private long editProductId = -1;
|
||||
private long editSupplierId = -1;
|
||||
private long preselectedProductId = -1;
|
||||
private long preselectedSupplierId = -1;
|
||||
|
||||
private List<ProductDTO> productList = new ArrayList<>();
|
||||
private List<SupplierDTO> supplierList = new ArrayList<>();
|
||||
|
||||
private ProductSupplierViewModel psViewModel;
|
||||
private ProductViewModel productViewModel;
|
||||
private SupplierViewModel supplierViewModel;
|
||||
|
||||
@Override
|
||||
public void onCreate(Bundle savedInstanceState) {
|
||||
super.onCreate(savedInstanceState);
|
||||
psViewModel = new ViewModelProvider(this).get(ProductSupplierViewModel.class);
|
||||
productViewModel = new ViewModelProvider(this).get(ProductViewModel.class);
|
||||
supplierViewModel = new ViewModelProvider(this).get(SupplierViewModel.class);
|
||||
viewModel = new ViewModelProvider(this).get(ProductSupplierDetailViewModel.class);
|
||||
}
|
||||
|
||||
@Override
|
||||
@@ -63,6 +51,7 @@ public class ProductSupplierDetailFragment extends Fragment {
|
||||
@Override
|
||||
public void onViewCreated(@NonNull View view, @Nullable Bundle savedInstanceState) {
|
||||
super.onViewCreated(view, savedInstanceState);
|
||||
observeViewModel();
|
||||
loadSpinnersData();
|
||||
handleArguments();
|
||||
|
||||
@@ -71,128 +60,97 @@ public class ProductSupplierDetailFragment extends Fragment {
|
||||
binding.btnDeletePS.setOnClickListener(v -> confirmDelete());
|
||||
}
|
||||
|
||||
private void observeViewModel() {
|
||||
viewModel.getProductList().observe(getViewLifecycleOwner(), list -> refreshProductSpinner());
|
||||
viewModel.getSupplierList().observe(getViewLifecycleOwner(), list -> refreshSupplierSpinner());
|
||||
}
|
||||
|
||||
private void setLoading(boolean loading) {
|
||||
if (binding != null && binding.progressBar != null) {
|
||||
binding.progressBar.setVisibility(loading ? View.VISIBLE : View.GONE);
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onDestroyView() {
|
||||
super.onDestroyView();
|
||||
binding = null;
|
||||
}
|
||||
|
||||
/**
|
||||
* Fetches products and suppliers to populate the spinners.
|
||||
*/
|
||||
private void loadSpinnersData() {
|
||||
loadProducts();
|
||||
loadSuppliers();
|
||||
}
|
||||
|
||||
/**
|
||||
* Loads the list of products from the API.
|
||||
*/
|
||||
private void loadProducts() {
|
||||
productViewModel.getAllProducts(null, null, 0, 200, "prodName").observe(getViewLifecycleOwner(), resource -> {
|
||||
viewModel.loadProducts().observe(getViewLifecycleOwner(), resource -> {
|
||||
if (resource == null) return;
|
||||
setLoading(resource.status == Resource.Status.LOADING);
|
||||
if (resource.status == Resource.Status.SUCCESS && resource.data != null) {
|
||||
productList = resource.data.getContent();
|
||||
refreshProductSpinner();
|
||||
viewModel.setProductList(resource.data.getContent());
|
||||
}
|
||||
});
|
||||
viewModel.loadSuppliers().observe(getViewLifecycleOwner(), resource -> {
|
||||
if (resource == null) return;
|
||||
setLoading(resource.status == Resource.Status.LOADING);
|
||||
if (resource.status == Resource.Status.SUCCESS && resource.data != null) {
|
||||
viewModel.setSupplierList(resource.data.getContent());
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
private void refreshProductSpinner() {
|
||||
SpinnerUtils.populateSpinner(requireContext(), binding.spinnerPSProduct, productList,
|
||||
SpinnerUtils.populateSpinner(requireContext(), binding.spinnerPSProduct, viewModel.getProductList().getValue(),
|
||||
ProductDTO::getProdName, "-- Select Product --",
|
||||
preselectedProductId, ProductDTO::getProdId);
|
||||
}
|
||||
|
||||
/**
|
||||
* Loads the list of suppliers from the API.
|
||||
*/
|
||||
private void loadSuppliers() {
|
||||
supplierViewModel.getAllSuppliers(0, 200, null, "supCompany").observe(getViewLifecycleOwner(), resource -> {
|
||||
if (resource.status == Resource.Status.SUCCESS && resource.data != null) {
|
||||
supplierList = resource.data.getContent();
|
||||
refreshSupplierSpinner();
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
private void refreshSupplierSpinner() {
|
||||
SpinnerUtils.populateSpinner(requireContext(), binding.spinnerPSSupplier, supplierList,
|
||||
SpinnerUtils.populateSpinner(requireContext(), binding.spinnerPSSupplier, viewModel.getSupplierList().getValue(),
|
||||
SupplierDTO::getSupCompany, "-- Select Supplier --",
|
||||
preselectedSupplierId, SupplierDTO::getSupId);
|
||||
}
|
||||
|
||||
/**
|
||||
* Handles arguments to determine if the fragment is in edit or add mode.
|
||||
*/
|
||||
private void handleArguments() {
|
||||
Bundle a = getArguments();
|
||||
if (a != null && a.containsKey("productId") && a.containsKey("supplierId")) {
|
||||
isEditing = true;
|
||||
editProductId = a.getLong("productId");
|
||||
editSupplierId = a.getLong("supplierId");
|
||||
preselectedProductId = editProductId;
|
||||
preselectedSupplierId = editSupplierId;
|
||||
long productId = a.getLong("productId");
|
||||
long supplierId = a.getLong("supplierId");
|
||||
viewModel.setEditMode(productId, supplierId);
|
||||
preselectedProductId = productId;
|
||||
preselectedSupplierId = supplierId;
|
||||
|
||||
binding.tvPSMode.setText("Edit Product Supplier");
|
||||
binding.btnDeletePS.setVisibility(View.VISIBLE);
|
||||
|
||||
} else {
|
||||
binding.tvPSMode.setText("Add Product Supplier");
|
||||
binding.btnDeletePS.setVisibility(View.GONE);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Validates input and saves the product-supplier to the backend.
|
||||
*/
|
||||
private void save() {
|
||||
if (binding.spinnerPSProduct.getSelectedItemPosition() == 0) {
|
||||
Toast.makeText(getContext(), "Select a product", Toast.LENGTH_SHORT).show(); return;
|
||||
}
|
||||
if (binding.spinnerPSSupplier.getSelectedItemPosition() == 0) {
|
||||
Toast.makeText(getContext(), "Select a supplier", Toast.LENGTH_SHORT).show(); return;
|
||||
}
|
||||
if (!InputValidator.isSpinnerSelected(binding.spinnerPSProduct, "Product")) return;
|
||||
if (!InputValidator.isSpinnerSelected(binding.spinnerPSSupplier, "Supplier")) return;
|
||||
if (!InputValidator.isPositiveDecimal(binding.etPSCost, "Cost")) return;
|
||||
|
||||
if (!InputValidator.isNotEmpty(binding.etPSCost, "Cost") ||
|
||||
!InputValidator.isPositiveDecimal(binding.etPSCost, "Cost")) {
|
||||
return;
|
||||
}
|
||||
|
||||
ProductDTO product = productList.get(binding.spinnerPSProduct.getSelectedItemPosition() - 1);
|
||||
SupplierDTO supplier = supplierList.get(binding.spinnerPSSupplier.getSelectedItemPosition() - 1);
|
||||
ProductDTO product = viewModel.getProductList().getValue().get(binding.spinnerPSProduct.getSelectedItemPosition() - 1);
|
||||
SupplierDTO supplier = viewModel.getSupplierList().getValue().get(binding.spinnerPSSupplier.getSelectedItemPosition() - 1);
|
||||
BigDecimal cost = new BigDecimal(binding.etPSCost.getText().toString().trim());
|
||||
|
||||
ProductSupplierDTO dto = new ProductSupplierDTO(
|
||||
product.getProdId(), supplier.getSupId(), cost);
|
||||
ProductSupplierDTO dto = new ProductSupplierDTO(product.getProdId(), supplier.getSupId(), cost);
|
||||
|
||||
if (isEditing) {
|
||||
psViewModel.updateProductSupplier(editProductId, editSupplierId, dto).observe(getViewLifecycleOwner(), resource -> {
|
||||
if (resource.status == Resource.Status.SUCCESS) {
|
||||
Toast.makeText(getContext(), "Updated", Toast.LENGTH_SHORT).show();
|
||||
navigateBack();
|
||||
} else if (resource.status == Resource.Status.ERROR) {
|
||||
Toast.makeText(getContext(), "Error: " + resource.message, Toast.LENGTH_SHORT).show();
|
||||
}
|
||||
});
|
||||
} else {
|
||||
psViewModel.createProductSupplier(dto).observe(getViewLifecycleOwner(), resource -> {
|
||||
if (resource.status == Resource.Status.SUCCESS) {
|
||||
Toast.makeText(getContext(), "Saved", Toast.LENGTH_SHORT).show();
|
||||
navigateBack();
|
||||
} else if (resource.status == Resource.Status.ERROR) {
|
||||
Toast.makeText(getContext(), "Error: " + resource.message, Toast.LENGTH_SHORT).show();
|
||||
}
|
||||
});
|
||||
}
|
||||
viewModel.saveProductSupplier(dto).observe(getViewLifecycleOwner(), resource -> {
|
||||
if (resource == null) return;
|
||||
setLoading(resource.status == Resource.Status.LOADING);
|
||||
if (resource.status == Resource.Status.SUCCESS) {
|
||||
Toast.makeText(getContext(), viewModel.isEditing() ? "Updated" : "Saved", Toast.LENGTH_SHORT).show();
|
||||
navigateBack();
|
||||
} else if (resource.status == Resource.Status.ERROR) {
|
||||
Toast.makeText(getContext(), "Error: " + resource.message, Toast.LENGTH_SHORT).show();
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* Shows a confirmation dialog before deleting a product-supplier relationship.
|
||||
*/
|
||||
private void confirmDelete() {
|
||||
DialogUtils.showDeleteConfirmDialog(requireContext(), "Product Supplier", () ->
|
||||
psViewModel.deleteProductSupplier(editProductId, editSupplierId).observe(getViewLifecycleOwner(), resource -> {
|
||||
DialogUtils.showDeleteConfirmDialog(requireContext(), "Product Supplier Relationship", () ->
|
||||
viewModel.deleteProductSupplier().observe(getViewLifecycleOwner(), resource -> {
|
||||
if (resource == null) return;
|
||||
setLoading(resource.status == Resource.Status.LOADING);
|
||||
if (resource.status == Resource.Status.SUCCESS) {
|
||||
Toast.makeText(getContext(), "Deleted", Toast.LENGTH_SHORT).show();
|
||||
navigateBack();
|
||||
@@ -202,9 +160,6 @@ public class ProductSupplierDetailFragment extends Fragment {
|
||||
}));
|
||||
}
|
||||
|
||||
/**
|
||||
* Navigates back to the previous screen.
|
||||
*/
|
||||
private void navigateBack() {
|
||||
NavHostFragment.findNavController(this).popBackStack();
|
||||
}
|
||||
|
||||
@@ -14,7 +14,7 @@ import androidx.navigation.fragment.NavHostFragment;
|
||||
import com.example.petstoremobile.databinding.FragmentPurchaseOrderDetailBinding;
|
||||
import com.example.petstoremobile.dtos.PurchaseOrderDTO;
|
||||
import com.example.petstoremobile.utils.Resource;
|
||||
import com.example.petstoremobile.viewmodels.PurchaseOrderViewModel;
|
||||
import com.example.petstoremobile.viewmodels.PurchaseOrderDetailViewModel;
|
||||
|
||||
import dagger.hilt.android.AndroidEntryPoint;
|
||||
|
||||
@@ -25,13 +25,13 @@ import dagger.hilt.android.AndroidEntryPoint;
|
||||
public class PurchaseOrderDetailFragment extends Fragment {
|
||||
|
||||
private FragmentPurchaseOrderDetailBinding binding;
|
||||
private PurchaseOrderViewModel viewModel;
|
||||
private PurchaseOrderDetailViewModel viewModel;
|
||||
private long purchaseOrderId;
|
||||
|
||||
@Override
|
||||
public void onCreate(@Nullable Bundle savedInstanceState) {
|
||||
super.onCreate(savedInstanceState);
|
||||
viewModel = new ViewModelProvider(this).get(PurchaseOrderViewModel.class);
|
||||
viewModel = new ViewModelProvider(this).get(PurchaseOrderDetailViewModel.class);
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -66,9 +66,16 @@ public class PurchaseOrderDetailFragment extends Fragment {
|
||||
}
|
||||
}
|
||||
|
||||
private void setLoading(boolean loading) {
|
||||
if (binding != null && binding.progressBar != null) {
|
||||
binding.progressBar.setVisibility(loading ? View.VISIBLE : View.GONE);
|
||||
}
|
||||
}
|
||||
|
||||
private void loadPurchaseOrderData() {
|
||||
viewModel.getPurchaseOrderById(purchaseOrderId).observe(getViewLifecycleOwner(), resource -> {
|
||||
viewModel.loadPurchaseOrder(purchaseOrderId).observe(getViewLifecycleOwner(), resource -> {
|
||||
if (resource == null) return;
|
||||
setLoading(resource.status == Resource.Status.LOADING);
|
||||
if (resource.status == Resource.Status.SUCCESS && resource.data != null) {
|
||||
PurchaseOrderDTO po = resource.data;
|
||||
binding.tvPODetailId.setText("PO #" + po.getPurchaseOrderId());
|
||||
|
||||
@@ -1,6 +1,5 @@
|
||||
package com.example.petstoremobile.fragments.listfragments.detailfragments;
|
||||
|
||||
import android.app.AlertDialog;
|
||||
import android.os.Bundle;
|
||||
import android.util.Log;
|
||||
import android.view.*;
|
||||
@@ -12,7 +11,10 @@ import androidx.navigation.fragment.NavHostFragment;
|
||||
import com.example.petstoremobile.R;
|
||||
import com.example.petstoremobile.databinding.FragmentRefundBinding;
|
||||
import com.example.petstoremobile.dtos.SaleDTO;
|
||||
import com.example.petstoremobile.viewmodels.SaleViewModel;
|
||||
import com.example.petstoremobile.viewmodels.RefundViewModel;
|
||||
import com.example.petstoremobile.utils.DialogUtils;
|
||||
import com.example.petstoremobile.utils.Resource;
|
||||
import com.example.petstoremobile.utils.SpinnerUtils;
|
||||
import dagger.hilt.android.AndroidEntryPoint;
|
||||
import java.math.BigDecimal;
|
||||
import java.math.RoundingMode;
|
||||
@@ -22,53 +24,23 @@ import java.util.*;
|
||||
public class RefundFragment extends Fragment {
|
||||
|
||||
private FragmentRefundBinding binding;
|
||||
private SaleViewModel saleViewModel;
|
||||
private SaleDTO currentSale;
|
||||
private List<SaleDTO> allSales = new ArrayList<>();
|
||||
|
||||
// Items available to refund (after accounting for previous refunds)
|
||||
private List<RefundItem> availableItems = new ArrayList<>();
|
||||
// Items user has added to refund cart
|
||||
private List<RefundItem> refundCart = new ArrayList<>();
|
||||
private RefundViewModel viewModel;
|
||||
|
||||
private final String[] PAYMENT_METHODS = {"Cash", "Card"};
|
||||
|
||||
// Inner class to track refund items
|
||||
static class RefundItem {
|
||||
long prodId;
|
||||
String productName;
|
||||
int quantity;
|
||||
BigDecimal unitPrice;
|
||||
|
||||
RefundItem(long prodId, String productName, int quantity, BigDecimal unitPrice) {
|
||||
this.prodId = prodId;
|
||||
this.productName = productName;
|
||||
this.quantity = quantity;
|
||||
this.unitPrice = unitPrice;
|
||||
}
|
||||
|
||||
BigDecimal getTotal() {
|
||||
return unitPrice != null
|
||||
? unitPrice.multiply(BigDecimal.valueOf(quantity))
|
||||
: BigDecimal.ZERO;
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public View onCreateView(@NonNull LayoutInflater inflater, ViewGroup container,
|
||||
Bundle savedInstanceState) {
|
||||
binding = FragmentRefundBinding.inflate(inflater, container, false);
|
||||
saleViewModel = new ViewModelProvider(this).get(SaleViewModel.class);
|
||||
viewModel = new ViewModelProvider(this).get(RefundViewModel.class);
|
||||
|
||||
setupSpinner();
|
||||
observeViewModel();
|
||||
loadAllSales();
|
||||
|
||||
// Pre-fill sale ID if passed from SaleFragment
|
||||
Bundle args = getArguments();
|
||||
if (args != null && args.containsKey("saleId")) {
|
||||
long saleId = args.getLong("saleId");
|
||||
binding.etRefundSaleId.setText(String.valueOf(saleId));
|
||||
// Auto-load after sales are fetched
|
||||
binding.etRefundSaleId.setText(String.valueOf(args.getLong("saleId")));
|
||||
}
|
||||
|
||||
binding.btnLoadSale.setOnClickListener(v -> loadSale());
|
||||
@@ -79,31 +51,36 @@ public class RefundFragment extends Fragment {
|
||||
}
|
||||
|
||||
private void setupSpinner() {
|
||||
binding.spinnerRefundPayment.setAdapter(new ArrayAdapter<>(requireContext(),
|
||||
android.R.layout.simple_spinner_item, PAYMENT_METHODS));
|
||||
SpinnerUtils.setupStringSpinner(requireContext(), binding.spinnerRefundPayment, PAYMENT_METHODS);
|
||||
}
|
||||
|
||||
private void observeViewModel() {
|
||||
viewModel.getAvailableItems().observe(getViewLifecycleOwner(), items -> renderOriginalItems());
|
||||
viewModel.getRefundCart().observe(getViewLifecycleOwner(), cart -> {
|
||||
renderRefundCart();
|
||||
updateRefundTotal();
|
||||
renderOriginalItems(); // Re-render to reflect quantities in cart
|
||||
});
|
||||
}
|
||||
|
||||
private void setLoading(boolean loading) {
|
||||
if (binding != null && binding.progressBar != null) {
|
||||
binding.progressBar.setVisibility(loading ? View.VISIBLE : View.GONE);
|
||||
}
|
||||
}
|
||||
|
||||
private void loadAllSales() {
|
||||
saleViewModel.getAllSales(0, 1000, null, null, null, "saleDate,desc")
|
||||
.observe(getViewLifecycleOwner(), resource -> {
|
||||
if (resource != null) {
|
||||
switch (resource.status) {
|
||||
case SUCCESS:
|
||||
if (resource.data != null) {
|
||||
allSales = resource.data.getContent();
|
||||
// Auto-load if saleId was pre-filled
|
||||
Bundle args = getArguments();
|
||||
if (args != null && args.containsKey("saleId")) {
|
||||
loadSale();
|
||||
}
|
||||
}
|
||||
break;
|
||||
case ERROR:
|
||||
Log.e("Refund", "Failed to load sales: " + resource.message);
|
||||
break;
|
||||
}
|
||||
}
|
||||
});
|
||||
viewModel.loadAllSales().observe(getViewLifecycleOwner(), resource -> {
|
||||
if (resource == null) return;
|
||||
setLoading(resource.status == Resource.Status.LOADING);
|
||||
if (resource.status == Resource.Status.SUCCESS && resource.data != null) {
|
||||
viewModel.setAllSales(resource.data.getContent());
|
||||
Bundle args = getArguments();
|
||||
if (args != null && args.containsKey("saleId")) {
|
||||
loadSale();
|
||||
}
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
private void loadSale() {
|
||||
@@ -120,11 +97,12 @@ public class RefundFragment extends Fragment {
|
||||
return;
|
||||
}
|
||||
|
||||
// Find sale in loaded list
|
||||
SaleDTO found = null;
|
||||
for (SaleDTO s : allSales) {
|
||||
if (s.getSaleId() != null && s.getSaleId() == saleId) {
|
||||
found = s; break;
|
||||
if (viewModel.getAllSalesList() != null) {
|
||||
for (SaleDTO s : viewModel.getAllSalesList()) {
|
||||
if (s.getSaleId() != null && s.getSaleId() == saleId) {
|
||||
found = s; break;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -139,9 +117,9 @@ public class RefundFragment extends Fragment {
|
||||
return;
|
||||
}
|
||||
|
||||
currentSale = found;
|
||||
viewModel.setCurrentSale(found);
|
||||
SaleDTO currentSale = viewModel.getCurrentSale();
|
||||
|
||||
// Show sale info
|
||||
binding.tvSaleInfo.setVisibility(View.VISIBLE);
|
||||
binding.tvSaleInfo.setText("Sale #" + currentSale.getSaleId()
|
||||
+ " | " + (currentSale.getSaleDate() != null
|
||||
@@ -151,94 +129,44 @@ public class RefundFragment extends Fragment {
|
||||
+ " | Total: $" + currentSale.getTotalAmount()
|
||||
+ " | Payment: " + currentSale.getPaymentMethod());
|
||||
|
||||
// Pre-select payment method
|
||||
if (currentSale.getPaymentMethod() != null) {
|
||||
for (int i = 0; i < PAYMENT_METHODS.length; i++) {
|
||||
if (PAYMENT_METHODS[i].equalsIgnoreCase(currentSale.getPaymentMethod())) {
|
||||
binding.spinnerRefundPayment.setSelection(i); break;
|
||||
}
|
||||
}
|
||||
SpinnerUtils.setSelectionByValue(binding.spinnerRefundPayment, currentSale.getPaymentMethod());
|
||||
}
|
||||
|
||||
// Build refundable items accounting for previous refunds
|
||||
buildRefundableItems();
|
||||
|
||||
if (availableItems.isEmpty()) {
|
||||
Toast.makeText(getContext(),
|
||||
"This sale has no remaining refundable items", Toast.LENGTH_LONG).show();
|
||||
if (viewModel.getAvailableItems().getValue() == null || viewModel.getAvailableItems().getValue().isEmpty()) {
|
||||
Toast.makeText(getContext(), "This sale has no remaining refundable items", Toast.LENGTH_LONG).show();
|
||||
return;
|
||||
}
|
||||
|
||||
// Reset refund cart
|
||||
refundCart.clear();
|
||||
|
||||
// Show cards
|
||||
binding.cardOriginalItems.setVisibility(View.VISIBLE);
|
||||
binding.cardRefundItems.setVisibility(View.VISIBLE);
|
||||
binding.cardPayment.setVisibility(View.VISIBLE);
|
||||
binding.btnProcessRefund.setVisibility(View.VISIBLE);
|
||||
|
||||
renderOriginalItems();
|
||||
renderRefundCart();
|
||||
updateRefundTotal();
|
||||
}
|
||||
|
||||
private void buildRefundableItems() {
|
||||
availableItems.clear();
|
||||
if (currentSale.getItems() == null) return;
|
||||
|
||||
// Find all previous refunds for this sale
|
||||
Map<Long, Integer> alreadyRefunded = new HashMap<>();
|
||||
for (SaleDTO s : allSales) {
|
||||
if (Boolean.TRUE.equals(s.getIsRefund())
|
||||
&& currentSale.getSaleId().equals(s.getOriginalSaleId())
|
||||
&& s.getItems() != null) {
|
||||
for (SaleDTO.SaleItemDTO item : s.getItems()) {
|
||||
if (item.getProdId() != null && item.getQuantity() != null) {
|
||||
alreadyRefunded.merge(item.getProdId(),
|
||||
Math.abs(item.getQuantity()), Integer::sum);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Build available items
|
||||
for (SaleDTO.SaleItemDTO item : currentSale.getItems()) {
|
||||
if (item.getProdId() == null || item.getQuantity() == null) continue;
|
||||
int refunded = alreadyRefunded.getOrDefault(item.getProdId(), 0);
|
||||
int remaining = item.getQuantity() - refunded;
|
||||
if (remaining > 0) {
|
||||
availableItems.add(new RefundItem(
|
||||
item.getProdId(),
|
||||
item.getProductName() != null ? item.getProductName() : "Unknown",
|
||||
remaining,
|
||||
item.getUnitPrice()
|
||||
));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private void renderOriginalItems() {
|
||||
binding.llOriginalItems.removeAllViews();
|
||||
List<RefundViewModel.RefundItem> available = viewModel.getAvailableItems().getValue();
|
||||
if (available == null) return;
|
||||
|
||||
// Header
|
||||
addTableHeader(binding.llOriginalItems);
|
||||
|
||||
for (RefundItem item : availableItems) {
|
||||
// Calculate pending in cart
|
||||
int pendingQty = 0;
|
||||
for (RefundItem r : refundCart) {
|
||||
if (r.prodId == item.prodId) { pendingQty = r.quantity; break; }
|
||||
for (RefundViewModel.RefundItem item : available) {
|
||||
int inCart = 0;
|
||||
if (viewModel.getRefundCart().getValue() != null) {
|
||||
for (RefundViewModel.RefundItem r : viewModel.getRefundCart().getValue()) {
|
||||
if (r.prodId == item.prodId) { inCart = r.quantity; break; }
|
||||
}
|
||||
}
|
||||
int displayQty = item.quantity - pendingQty;
|
||||
int displayQty = item.quantity - inCart;
|
||||
if (displayQty <= 0) continue;
|
||||
|
||||
LinearLayout row = buildItemRow(
|
||||
item.productName,
|
||||
displayQty,
|
||||
item.unitPrice,
|
||||
true, // show add button
|
||||
() -> showQuantityDialog(item)
|
||||
true,
|
||||
() -> showQuantityDialog(item, displayQty)
|
||||
);
|
||||
binding.llOriginalItems.addView(row);
|
||||
}
|
||||
@@ -246,8 +174,9 @@ public class RefundFragment extends Fragment {
|
||||
|
||||
private void renderRefundCart() {
|
||||
binding.llRefundItems.removeAllViews();
|
||||
List<RefundViewModel.RefundItem> cart = viewModel.getRefundCart().getValue();
|
||||
|
||||
if (refundCart.isEmpty()) {
|
||||
if (cart == null || cart.isEmpty()) {
|
||||
TextView empty = new TextView(getContext());
|
||||
empty.setText("No items added to refund yet");
|
||||
empty.setTextColor(0xFF888780);
|
||||
@@ -258,18 +187,13 @@ public class RefundFragment extends Fragment {
|
||||
|
||||
addTableHeader(binding.llRefundItems);
|
||||
|
||||
for (RefundItem item : refundCart) {
|
||||
for (RefundViewModel.RefundItem item : cart) {
|
||||
LinearLayout row = buildItemRow(
|
||||
item.productName,
|
||||
item.quantity,
|
||||
item.unitPrice,
|
||||
false, // show remove button
|
||||
() -> {
|
||||
refundCart.remove(item);
|
||||
renderOriginalItems();
|
||||
renderRefundCart();
|
||||
updateRefundTotal();
|
||||
}
|
||||
false,
|
||||
() -> viewModel.removeFromCart(item)
|
||||
);
|
||||
binding.llRefundItems.addView(row);
|
||||
}
|
||||
@@ -342,146 +266,79 @@ public class RefundFragment extends Fragment {
|
||||
return row;
|
||||
}
|
||||
|
||||
private void showQuantityDialog(RefundItem item) {
|
||||
// Calculate how many are already in cart
|
||||
int inCart = 0;
|
||||
for (RefundItem r : refundCart) {
|
||||
if (r.prodId == item.prodId) { inCart = r.quantity; break; }
|
||||
}
|
||||
int available = item.quantity - inCart;
|
||||
if (available <= 0) {
|
||||
Toast.makeText(getContext(), "All units already added to refund",
|
||||
Toast.LENGTH_SHORT).show();
|
||||
return;
|
||||
}
|
||||
|
||||
// Build dialog
|
||||
AlertDialog.Builder builder = new AlertDialog.Builder(requireContext());
|
||||
builder.setTitle("Refund Quantity");
|
||||
builder.setMessage("Product: " + item.productName
|
||||
+ "\nAvailable: " + available);
|
||||
|
||||
private void showQuantityDialog(RefundViewModel.RefundItem item, int available) {
|
||||
EditText input = new EditText(getContext());
|
||||
input.setInputType(android.text.InputType.TYPE_CLASS_NUMBER);
|
||||
input.setText(String.valueOf(available));
|
||||
input.setSelectAllOnFocus(true);
|
||||
builder.setView(input);
|
||||
input.setPadding(40, 40, 40, 40);
|
||||
|
||||
builder.setPositiveButton("Add to Refund", (d, w) -> {
|
||||
String val = input.getText().toString().trim();
|
||||
if (val.isEmpty()) return;
|
||||
int qty;
|
||||
try { qty = Integer.parseInt(val); }
|
||||
catch (Exception e) {
|
||||
Toast.makeText(getContext(), "Invalid quantity", Toast.LENGTH_SHORT).show();
|
||||
return;
|
||||
}
|
||||
if (qty <= 0) {
|
||||
Toast.makeText(getContext(), "Quantity must be at least 1",
|
||||
Toast.LENGTH_SHORT).show();
|
||||
return;
|
||||
}
|
||||
if (qty > available) {
|
||||
Toast.makeText(getContext(), "Cannot exceed " + available,
|
||||
Toast.LENGTH_SHORT).show();
|
||||
return;
|
||||
}
|
||||
|
||||
// Add or merge into cart
|
||||
boolean merged = false;
|
||||
for (int i = 0; i < refundCart.size(); i++) {
|
||||
if (refundCart.get(i).prodId == item.prodId) {
|
||||
RefundItem existing = refundCart.get(i);
|
||||
refundCart.set(i, new RefundItem(existing.prodId,
|
||||
existing.productName,
|
||||
existing.quantity + qty,
|
||||
existing.unitPrice));
|
||||
merged = true; break;
|
||||
}
|
||||
}
|
||||
if (!merged) {
|
||||
refundCart.add(new RefundItem(item.prodId, item.productName,
|
||||
qty, item.unitPrice));
|
||||
}
|
||||
|
||||
renderOriginalItems();
|
||||
renderRefundCart();
|
||||
updateRefundTotal();
|
||||
});
|
||||
|
||||
builder.setNegativeButton("Cancel", null);
|
||||
builder.show();
|
||||
new androidx.appcompat.app.AlertDialog.Builder(requireContext())
|
||||
.setTitle("Refund Quantity")
|
||||
.setMessage("Product: " + item.productName + "\nAvailable: " + available)
|
||||
.setView(input)
|
||||
.setPositiveButton("Add to Refund", (d, w) -> {
|
||||
String val = input.getText().toString().trim();
|
||||
if (val.isEmpty()) return;
|
||||
int qty;
|
||||
try { qty = Integer.parseInt(val); }
|
||||
catch (Exception e) {
|
||||
Toast.makeText(getContext(), "Invalid quantity", Toast.LENGTH_SHORT).show();
|
||||
return;
|
||||
}
|
||||
if (qty <= 0) {
|
||||
Toast.makeText(getContext(), "Quantity must be at least 1", Toast.LENGTH_SHORT).show();
|
||||
return;
|
||||
}
|
||||
if (qty > available) {
|
||||
Toast.makeText(getContext(), "Cannot exceed " + available, Toast.LENGTH_SHORT).show();
|
||||
return;
|
||||
}
|
||||
viewModel.addToCart(item, qty);
|
||||
})
|
||||
.setNegativeButton("Cancel", null)
|
||||
.show();
|
||||
}
|
||||
|
||||
private void updateRefundTotal() {
|
||||
BigDecimal total = BigDecimal.ZERO;
|
||||
for (RefundItem item : refundCart) total = total.add(item.getTotal());
|
||||
List<RefundViewModel.RefundItem> cart = viewModel.getRefundCart().getValue();
|
||||
if (cart != null) {
|
||||
for (RefundViewModel.RefundItem item : cart) total = total.add(item.getTotal());
|
||||
}
|
||||
binding.tvRefundTotal.setText("Refund Total: $" + total.setScale(2, RoundingMode.HALF_UP));
|
||||
}
|
||||
|
||||
private void processRefund() {
|
||||
if (currentSale == null) {
|
||||
if (viewModel.getCurrentSale() == null) {
|
||||
Toast.makeText(getContext(), "Load a sale first", Toast.LENGTH_SHORT).show();
|
||||
return;
|
||||
}
|
||||
if (refundCart.isEmpty()) {
|
||||
Toast.makeText(getContext(), "Add at least one item to refund",
|
||||
Toast.LENGTH_SHORT).show();
|
||||
if (viewModel.getRefundCart().getValue() == null || viewModel.getRefundCart().getValue().isEmpty()) {
|
||||
Toast.makeText(getContext(), "Add at least one item to refund", Toast.LENGTH_SHORT).show();
|
||||
return;
|
||||
}
|
||||
|
||||
String payment = PAYMENT_METHODS[binding.spinnerRefundPayment.getSelectedItemPosition()];
|
||||
|
||||
// Confirm dialog
|
||||
BigDecimal total = BigDecimal.ZERO;
|
||||
for (RefundItem item : refundCart) total = total.add(item.getTotal());
|
||||
for (RefundViewModel.RefundItem item : viewModel.getRefundCart().getValue()) total = total.add(item.getTotal());
|
||||
final BigDecimal finalTotal = total;
|
||||
|
||||
new AlertDialog.Builder(requireContext())
|
||||
.setTitle("Confirm Refund")
|
||||
.setMessage("Process refund for Sale #" + currentSale.getSaleId()
|
||||
+ "?\nRefund amount: $" + finalTotal.setScale(2, RoundingMode.HALF_UP))
|
||||
.setPositiveButton("Yes", (d, w) -> submitRefund(payment))
|
||||
.setNegativeButton("No", null)
|
||||
.show();
|
||||
DialogUtils.showConfirmDialog(requireContext(), "Confirm Refund",
|
||||
"Process refund for Sale #" + viewModel.getCurrentSale().getSaleId()
|
||||
+ "?\nRefund amount: $" + finalTotal.setScale(2, RoundingMode.HALF_UP),
|
||||
() -> submitRefund(payment));
|
||||
}
|
||||
|
||||
private void submitRefund(String payment) {
|
||||
// Build sale items list
|
||||
List<SaleDTO.SaleItemDTO> items = new ArrayList<>();
|
||||
for (RefundItem item : refundCart) {
|
||||
// Backend expects negative quantity for refunds
|
||||
items.add(new SaleDTO.SaleItemDTO(item.prodId, -item.quantity));
|
||||
}
|
||||
|
||||
SaleDTO dto = new SaleDTO(
|
||||
currentSale.getStoreId(),
|
||||
payment,
|
||||
items,
|
||||
true, // isRefund = true
|
||||
currentSale.getSaleId(), // originalSaleId
|
||||
null // no customer needed
|
||||
);
|
||||
|
||||
Log.d("REFUND", "Submitting refund for saleId=" + currentSale.getSaleId()
|
||||
+ " items=" + items.size());
|
||||
|
||||
saleViewModel.createSale(dto).observe(getViewLifecycleOwner(), resource -> {
|
||||
viewModel.submitRefund(payment).observe(getViewLifecycleOwner(), resource -> {
|
||||
if (resource != null) {
|
||||
switch (resource.status) {
|
||||
case SUCCESS:
|
||||
if (resource.data != null) {
|
||||
Toast.makeText(getContext(),
|
||||
"Refund #" + resource.data.getSaleId() + " processed successfully!",
|
||||
Toast.LENGTH_LONG).show();
|
||||
navigateBack();
|
||||
}
|
||||
break;
|
||||
case ERROR:
|
||||
Log.e("REFUND", "Error: " + resource.message);
|
||||
Toast.makeText(getContext(), "Error: " + resource.message,
|
||||
Toast.LENGTH_LONG).show();
|
||||
break;
|
||||
setLoading(resource.status == Resource.Status.LOADING);
|
||||
if (resource.status == Resource.Status.SUCCESS) {
|
||||
Toast.makeText(getContext(), "Refund processed successfully!", Toast.LENGTH_LONG).show();
|
||||
navigateBack();
|
||||
} else if (resource.status == Resource.Status.ERROR) {
|
||||
Toast.makeText(getContext(), "Error: " + resource.message, Toast.LENGTH_LONG).show();
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
@@ -11,10 +11,12 @@ import androidx.navigation.fragment.NavHostFragment;
|
||||
import com.example.petstoremobile.R;
|
||||
import com.example.petstoremobile.databinding.FragmentSaleDetailBinding;
|
||||
import com.example.petstoremobile.dtos.*;
|
||||
import com.example.petstoremobile.viewmodels.*;
|
||||
import com.example.petstoremobile.viewmodels.SaleDetailViewModel;
|
||||
import com.example.petstoremobile.utils.SpinnerUtils;
|
||||
import com.example.petstoremobile.utils.DialogUtils;
|
||||
import com.example.petstoremobile.utils.InputValidator;
|
||||
import com.example.petstoremobile.utils.Resource;
|
||||
import com.example.petstoremobile.utils.UIUtils;
|
||||
import dagger.hilt.android.AndroidEntryPoint;
|
||||
import java.math.BigDecimal;
|
||||
import java.util.*;
|
||||
@@ -23,18 +25,7 @@ import java.util.*;
|
||||
public class SaleDetailFragment extends Fragment {
|
||||
|
||||
private FragmentSaleDetailBinding binding;
|
||||
private SaleViewModel saleViewModel;
|
||||
private StoreViewModel storeViewModel;
|
||||
private CustomerViewModel customerViewModel;
|
||||
private ProductViewModel productViewModel;
|
||||
|
||||
private boolean viewOnly = false;
|
||||
private long saleId = -1;
|
||||
|
||||
private List<StoreDTO> storeList = new ArrayList<>();
|
||||
private List<CustomerDTO> customerList = new ArrayList<>();
|
||||
private List<ProductDTO> productList = new ArrayList<>();
|
||||
private List<SaleDTO.SaleItemDTO> cartItems = new ArrayList<>();
|
||||
private SaleDetailViewModel viewModel;
|
||||
|
||||
private final String[] PAYMENT_METHODS = { "Cash", "Card"};
|
||||
|
||||
@@ -42,17 +33,14 @@ public class SaleDetailFragment extends Fragment {
|
||||
public View onCreateView(@NonNull LayoutInflater inflater, ViewGroup container,
|
||||
Bundle savedInstanceState) {
|
||||
binding = FragmentSaleDetailBinding.inflate(inflater, container, false);
|
||||
|
||||
saleViewModel = new ViewModelProvider(this).get(SaleViewModel.class);
|
||||
storeViewModel = new ViewModelProvider(this).get(StoreViewModel.class);
|
||||
customerViewModel = new ViewModelProvider(this).get(CustomerViewModel.class);
|
||||
productViewModel = new ViewModelProvider(this).get(ProductViewModel.class);
|
||||
viewModel = new ViewModelProvider(this).get(SaleDetailViewModel.class);
|
||||
|
||||
SpinnerUtils.setupStringSpinner(requireContext(), binding.spinnerPaymentMethod, PAYMENT_METHODS);
|
||||
|
||||
observeViewModel();
|
||||
handleArguments();
|
||||
|
||||
if (!viewOnly) {
|
||||
if (!viewModel.isViewOnly()) {
|
||||
loadData();
|
||||
setupAddItem();
|
||||
}
|
||||
@@ -64,32 +52,55 @@ public class SaleDetailFragment extends Fragment {
|
||||
return binding.getRoot();
|
||||
}
|
||||
|
||||
private void observeViewModel() {
|
||||
viewModel.getStoreList().observe(getViewLifecycleOwner(), list -> {
|
||||
SpinnerUtils.populateSpinner(requireContext(), binding.spinnerSaleStore, list,
|
||||
DropdownDTO::getLabel, "-- Select Store --", -1L, DropdownDTO::getId);
|
||||
});
|
||||
|
||||
viewModel.getCustomerList().observe(getViewLifecycleOwner(), list -> {
|
||||
SpinnerUtils.populateSpinner(requireContext(), binding.spinnerSaleCustomer, list,
|
||||
DropdownDTO::getLabel, "-- No Customer --", -1L, DropdownDTO::getId);
|
||||
});
|
||||
|
||||
viewModel.getProductList().observe(getViewLifecycleOwner(), list -> {
|
||||
SpinnerUtils.populateSpinner(requireContext(), binding.spinnerSaleProduct, list,
|
||||
ProductDTO::getProdName, "Select Product", -1L, ProductDTO::getProdId);
|
||||
});
|
||||
|
||||
viewModel.getCartItems().observe(getViewLifecycleOwner(), items -> {
|
||||
renderCartItems();
|
||||
updateTotal();
|
||||
});
|
||||
}
|
||||
|
||||
private void handleArguments() {
|
||||
Bundle a = getArguments();
|
||||
if (a != null && a.containsKey("saleId")) {
|
||||
saleId = a.getLong("saleId");
|
||||
viewOnly = a.getBoolean("viewOnly", false);
|
||||
long saleId = a.getLong("saleId");
|
||||
boolean viewOnly = a.getBoolean("viewOnly", false);
|
||||
viewModel.setSaleId(saleId, viewOnly);
|
||||
|
||||
binding.tvSaleMode.setText("Sale #" + saleId);
|
||||
binding.tvSaleDetailId.setText("ID: " + saleId);
|
||||
|
||||
// Show refund button for existing non-refund sales
|
||||
if (!a.getBoolean("isRefund", false)) {
|
||||
binding.btnRefundSale.setVisibility(View.VISIBLE);
|
||||
}
|
||||
|
||||
// Hide save and input controls for view only
|
||||
if (viewOnly) {
|
||||
binding.btnSaveSale.setVisibility(View.GONE);
|
||||
binding.spinnerSaleStore.setEnabled(false);
|
||||
binding.spinnerSaleCustomer.setEnabled(false);
|
||||
binding.spinnerPaymentMethod.setEnabled(false);
|
||||
UIUtils.setViewsEnabled(false,
|
||||
binding.spinnerSaleStore,
|
||||
binding.spinnerSaleCustomer,
|
||||
binding.spinnerPaymentMethod);
|
||||
binding.llAddItemRow.setVisibility(View.GONE);
|
||||
binding.llExtraInfo.setVisibility(View.VISIBLE);
|
||||
}
|
||||
|
||||
// Load sale details
|
||||
loadSaleDetails();
|
||||
} else {
|
||||
viewModel.setSaleId(-1, false);
|
||||
binding.tvSaleMode.setText("New Sale");
|
||||
binding.tvSaleDetailId.setVisibility(View.GONE);
|
||||
binding.btnRefundSale.setVisibility(View.GONE);
|
||||
@@ -97,89 +108,62 @@ public class SaleDetailFragment extends Fragment {
|
||||
}
|
||||
}
|
||||
|
||||
private void setLoading(boolean loading) {
|
||||
if (binding != null && binding.progressBar != null) {
|
||||
binding.progressBar.setVisibility(loading ? View.VISIBLE : View.GONE);
|
||||
}
|
||||
}
|
||||
|
||||
private void loadData() {
|
||||
loadStores();
|
||||
loadCustomers();
|
||||
loadProducts();
|
||||
}
|
||||
|
||||
private void loadStores() {
|
||||
storeViewModel.getAllStores(0, 50).observe(getViewLifecycleOwner(), resource -> {
|
||||
if (resource != null && resource.status == Resource.Status.SUCCESS && resource.data != null) {
|
||||
storeList = resource.data.getContent();
|
||||
if (binding != null) {
|
||||
SpinnerUtils.populateSpinner(requireContext(), binding.spinnerSaleStore, storeList,
|
||||
StoreDTO::getStoreName, "-- Select Store --", -1L, StoreDTO::getStoreId);
|
||||
}
|
||||
} else if (storeList.isEmpty()) {
|
||||
storeList = Collections.singletonList(new StoreDTO(1L, "Downtown Branch"));
|
||||
if (binding != null) {
|
||||
SpinnerUtils.populateSpinner(requireContext(), binding.spinnerSaleStore, storeList,
|
||||
StoreDTO::getStoreName, "-- Select Store --", -1L, StoreDTO::getStoreId);
|
||||
}
|
||||
}
|
||||
viewModel.loadStores().observe(getViewLifecycleOwner(), resource -> {
|
||||
if (resource == null) return;
|
||||
setLoading(resource.status == Resource.Status.LOADING);
|
||||
if (resource.status == Resource.Status.SUCCESS && resource.data != null) viewModel.setStoreList(resource.data);
|
||||
});
|
||||
}
|
||||
|
||||
private void loadCustomers() {
|
||||
customerViewModel.getAllCustomers(0, 200).observe(getViewLifecycleOwner(), resource -> {
|
||||
if (resource != null && resource.status == Resource.Status.SUCCESS && resource.data != null) {
|
||||
customerList = resource.data.getContent();
|
||||
if (binding != null) {
|
||||
SpinnerUtils.populateSpinner(requireContext(), binding.spinnerSaleCustomer, customerList,
|
||||
CustomerDTO::getFullName, "-- No Customer --", -1L, CustomerDTO::getCustomerId);
|
||||
}
|
||||
}
|
||||
viewModel.loadCustomers().observe(getViewLifecycleOwner(), resource -> {
|
||||
if (resource == null) return;
|
||||
setLoading(resource.status == Resource.Status.LOADING);
|
||||
if (resource.status == Resource.Status.SUCCESS && resource.data != null) viewModel.setCustomerList(resource.data);
|
||||
});
|
||||
}
|
||||
|
||||
private void loadProducts() {
|
||||
productViewModel.getAllProducts(null, null, 0, 200, null).observe(getViewLifecycleOwner(), resource -> {
|
||||
if (resource != null && resource.status == Resource.Status.SUCCESS && resource.data != null) {
|
||||
productList = resource.data.getContent();
|
||||
if (binding != null) {
|
||||
SpinnerUtils.populateSpinner(requireContext(), binding.spinnerSaleProduct, productList,
|
||||
ProductDTO::getProdName, "Select Product", -1L, ProductDTO::getProdId);
|
||||
}
|
||||
}
|
||||
viewModel.loadProducts().observe(getViewLifecycleOwner(), resource -> {
|
||||
if (resource == null) return;
|
||||
setLoading(resource.status == Resource.Status.LOADING);
|
||||
if (resource.status == Resource.Status.SUCCESS && resource.data != null) viewModel.setProductList(resource.data.getContent());
|
||||
});
|
||||
}
|
||||
|
||||
private void loadSaleDetails() {
|
||||
saleViewModel.getSaleById(saleId).observe(getViewLifecycleOwner(), resource -> {
|
||||
if (resource != null && resource.status == Resource.Status.SUCCESS && resource.data != null) {
|
||||
viewModel.loadSaleDetails().observe(getViewLifecycleOwner(), resource -> {
|
||||
if (resource == null) return;
|
||||
setLoading(resource.status == Resource.Status.LOADING);
|
||||
if (resource.status == Resource.Status.SUCCESS && resource.data != null) {
|
||||
SaleDTO sale = resource.data;
|
||||
if (binding != null) {
|
||||
binding.tvSaleDetailTotal.setText("Total: $" + sale.getTotalAmount());
|
||||
binding.tvSaleSubtotal.setText("$" + (sale.getSubtotalAmount() != null ? sale.getSubtotalAmount() : sale.getTotalAmount()));
|
||||
|
||||
if (sale.getCouponDiscountAmount() != null && sale.getCouponDiscountAmount().compareTo(BigDecimal.ZERO) > 0) {
|
||||
binding.llCouponDiscount.setVisibility(View.VISIBLE);
|
||||
binding.tvSaleCouponDiscount.setText("-$" + sale.getCouponDiscountAmount());
|
||||
} else {
|
||||
binding.llCouponDiscount.setVisibility(View.GONE);
|
||||
}
|
||||
binding.tvSaleDetailTotal.setText("Total: $" + sale.getTotalAmount());
|
||||
binding.tvSaleSubtotal.setText("$" + (sale.getSubtotalAmount() != null ? sale.getSubtotalAmount() : sale.getTotalAmount()));
|
||||
|
||||
if (sale.getCouponDiscountAmount() != null && sale.getCouponDiscountAmount().compareTo(BigDecimal.ZERO) > 0) {
|
||||
binding.llCouponDiscount.setVisibility(View.VISIBLE);
|
||||
binding.tvSaleCouponDiscount.setText("-$" + sale.getCouponDiscountAmount());
|
||||
} else {
|
||||
binding.llCouponDiscount.setVisibility(View.GONE);
|
||||
}
|
||||
|
||||
if (sale.getEmployeeDiscountAmount() != null && sale.getEmployeeDiscountAmount().compareTo(BigDecimal.ZERO) > 0) {
|
||||
binding.llEmployeeDiscount.setVisibility(View.VISIBLE);
|
||||
binding.tvSaleEmployeeDiscount.setText("-$" + sale.getEmployeeDiscountAmount());
|
||||
} else {
|
||||
binding.llEmployeeDiscount.setVisibility(View.GONE);
|
||||
}
|
||||
if (sale.getEmployeeDiscountAmount() != null && sale.getEmployeeDiscountAmount().compareTo(BigDecimal.ZERO) > 0) {
|
||||
binding.llEmployeeDiscount.setVisibility(View.VISIBLE);
|
||||
binding.tvSaleEmployeeDiscount.setText("-$" + sale.getEmployeeDiscountAmount());
|
||||
} else {
|
||||
binding.llEmployeeDiscount.setVisibility(View.GONE);
|
||||
}
|
||||
|
||||
binding.tvSaleChannel.setText(sale.getChannel() != null ? sale.getChannel() : "—");
|
||||
binding.tvSalePoints.setText(String.valueOf(sale.getPointsEarned() != null ? sale.getPointsEarned() : 0));
|
||||
binding.tvSaleChannel.setText(sale.getChannel() != null ? sale.getChannel() : "—");
|
||||
binding.tvSalePoints.setText(String.valueOf(sale.getPointsEarned() != null ? sale.getPointsEarned() : 0));
|
||||
|
||||
SpinnerUtils.setSelectionByValue(binding.spinnerPaymentMethod, sale.getPaymentMethod());
|
||||
SpinnerUtils.setSelectionByValue(binding.spinnerPaymentMethod, sale.getPaymentMethod());
|
||||
|
||||
// Display items
|
||||
if (sale.getItems() != null) {
|
||||
binding.llSaleItems.removeAllViews();
|
||||
for (SaleDTO.SaleItemDTO item : sale.getItems()) {
|
||||
addItemRow(item.getProductName(),
|
||||
Math.abs(item.getQuantity()),
|
||||
item.getUnitPrice());
|
||||
}
|
||||
if (sale.getItems() != null) {
|
||||
binding.llSaleItems.removeAllViews();
|
||||
for (SaleDTO.SaleItemDTO item : sale.getItems()) {
|
||||
addItemRow(item.getProductName(), Math.abs(item.getQuantity()), item.getUnitPrice());
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -188,41 +172,44 @@ public class SaleDetailFragment extends Fragment {
|
||||
|
||||
private void setupAddItem() {
|
||||
binding.btnAddItem.setOnClickListener(v -> {
|
||||
if (binding.spinnerSaleProduct.getSelectedItemPosition() == 0) {
|
||||
Toast.makeText(getContext(), "Select a product", Toast.LENGTH_SHORT).show();
|
||||
return;
|
||||
}
|
||||
String qtyStr = binding.etSaleQuantity.getText().toString().trim();
|
||||
if (qtyStr.isEmpty()) {
|
||||
binding.etSaleQuantity.setError("Enter quantity");
|
||||
return;
|
||||
}
|
||||
int qty;
|
||||
try {
|
||||
qty = Integer.parseInt(qtyStr);
|
||||
} catch (Exception e) {
|
||||
binding.etSaleQuantity.setError("Invalid quantity");
|
||||
return;
|
||||
}
|
||||
if (!InputValidator.isSpinnerSelected(binding.spinnerSaleProduct, "Product")) return;
|
||||
if (!InputValidator.isPositiveInteger(binding.etSaleQuantity, "Quantity")) return;
|
||||
|
||||
ProductDTO product = productList.get(binding.spinnerSaleProduct.getSelectedItemPosition() - 1);
|
||||
int qty = Integer.parseInt(binding.etSaleQuantity.getText().toString().trim());
|
||||
ProductDTO product = viewModel.getProductList().getValue().get(binding.spinnerSaleProduct.getSelectedItemPosition() - 1);
|
||||
|
||||
// Check if product already in cart
|
||||
for (SaleDTO.SaleItemDTO existing : cartItems) {
|
||||
for (SaleDTO.SaleItemDTO existing : viewModel.getCartItems().getValue()) {
|
||||
if (existing.getProdId().equals(product.getProdId())) {
|
||||
Toast.makeText(getContext(), "Product already added", Toast.LENGTH_SHORT).show();
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
SaleDTO.SaleItemDTO item = new SaleDTO.SaleItemDTO(product.getProdId(), qty);
|
||||
cartItems.add(item);
|
||||
addItemRow(product.getProdName(), qty, product.getProdPrice());
|
||||
updateTotal();
|
||||
viewModel.addToCart(new SaleDTO.SaleItemDTO(product.getProdId(), qty));
|
||||
binding.etSaleQuantity.setText("");
|
||||
});
|
||||
}
|
||||
|
||||
private void renderCartItems() {
|
||||
binding.llSaleItems.removeAllViews();
|
||||
List<SaleDTO.SaleItemDTO> items = viewModel.getCartItems().getValue();
|
||||
List<ProductDTO> products = viewModel.getProductList().getValue();
|
||||
if (items == null || products == null) return;
|
||||
|
||||
for (SaleDTO.SaleItemDTO item : items) {
|
||||
String name = "Unknown";
|
||||
BigDecimal price = BigDecimal.ZERO;
|
||||
for (ProductDTO p : products) {
|
||||
if (p.getProdId().equals(item.getProdId())) {
|
||||
name = p.getProdName();
|
||||
price = p.getProdPrice();
|
||||
break;
|
||||
}
|
||||
}
|
||||
addItemRow(name, item.getQuantity(), price);
|
||||
}
|
||||
}
|
||||
|
||||
private void addItemRow(String name, int qty, BigDecimal price) {
|
||||
if (getContext() == null) return;
|
||||
LinearLayout row = new LinearLayout(getContext());
|
||||
@@ -230,18 +217,15 @@ public class SaleDetailFragment extends Fragment {
|
||||
row.setPadding(0, 8, 0, 8);
|
||||
|
||||
TextView tvName = new TextView(getContext());
|
||||
tvName.setLayoutParams(new LinearLayout.LayoutParams(
|
||||
0, LinearLayout.LayoutParams.WRAP_CONTENT, 2f));
|
||||
tvName.setLayoutParams(new LinearLayout.LayoutParams(0, LinearLayout.LayoutParams.WRAP_CONTENT, 2f));
|
||||
tvName.setText(name);
|
||||
|
||||
TextView tvQty = new TextView(getContext());
|
||||
tvQty.setLayoutParams(new LinearLayout.LayoutParams(
|
||||
0, LinearLayout.LayoutParams.WRAP_CONTENT, 1f));
|
||||
tvQty.setLayoutParams(new LinearLayout.LayoutParams(0, LinearLayout.LayoutParams.WRAP_CONTENT, 1f));
|
||||
tvQty.setText("x" + qty);
|
||||
|
||||
TextView tvPrice = new TextView(getContext());
|
||||
tvPrice.setLayoutParams(new LinearLayout.LayoutParams(
|
||||
0, LinearLayout.LayoutParams.WRAP_CONTENT, 1f));
|
||||
tvPrice.setLayoutParams(new LinearLayout.LayoutParams(0, LinearLayout.LayoutParams.WRAP_CONTENT, 1f));
|
||||
tvPrice.setText(price != null ? "$" + price : "");
|
||||
|
||||
row.addView(tvName);
|
||||
@@ -251,59 +235,37 @@ public class SaleDetailFragment extends Fragment {
|
||||
}
|
||||
|
||||
private void updateTotal() {
|
||||
BigDecimal total = BigDecimal.ZERO;
|
||||
for (SaleDTO.SaleItemDTO item : cartItems) {
|
||||
for (ProductDTO p : productList) {
|
||||
if (p.getProdId().equals(item.getProdId()) && p.getProdPrice() != null) {
|
||||
total = total.add(p.getProdPrice()
|
||||
.multiply(BigDecimal.valueOf(item.getQuantity())));
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
BigDecimal total = viewModel.calculateSubtotal();
|
||||
binding.tvSaleSubtotal.setText("$" + total);
|
||||
binding.tvSaleDetailTotal.setText("Total: $" + total);
|
||||
}
|
||||
|
||||
private void saveSale() {
|
||||
if (binding.spinnerSaleStore.getSelectedItemPosition() == 0) {
|
||||
Toast.makeText(getContext(), "Select a store", Toast.LENGTH_SHORT).show();
|
||||
return;
|
||||
}
|
||||
if (cartItems.isEmpty()) {
|
||||
if (!InputValidator.isSpinnerSelected(binding.spinnerSaleStore, "Store")) return;
|
||||
|
||||
if (viewModel.getCartItems().getValue() == null || viewModel.getCartItems().getValue().isEmpty()) {
|
||||
Toast.makeText(getContext(), "Add at least one item", Toast.LENGTH_SHORT).show();
|
||||
return;
|
||||
}
|
||||
|
||||
StoreDTO store = storeList.get(binding.spinnerSaleStore.getSelectedItemPosition() - 1);
|
||||
DropdownDTO store = viewModel.getStoreList().getValue().get(binding.spinnerSaleStore.getSelectedItemPosition() - 1);
|
||||
String payment = PAYMENT_METHODS[binding.spinnerPaymentMethod.getSelectedItemPosition()];
|
||||
|
||||
// Optional customer
|
||||
Long customerId = null;
|
||||
if (binding.spinnerSaleCustomer.getSelectedItemPosition() > 0) {
|
||||
customerId = customerList.get(binding.spinnerSaleCustomer.getSelectedItemPosition() - 1)
|
||||
.getCustomerId();
|
||||
customerId = viewModel.getCustomerList().getValue().get(binding.spinnerSaleCustomer.getSelectedItemPosition() - 1).getId();
|
||||
}
|
||||
|
||||
SaleDTO dto = new SaleDTO(
|
||||
store.getStoreId(),
|
||||
payment,
|
||||
cartItems,
|
||||
false,
|
||||
null,
|
||||
customerId);
|
||||
SaleDTO dto = new SaleDTO(store.getId(), payment, viewModel.getCartItems().getValue(), false, null, customerId);
|
||||
|
||||
saleViewModel.createSale(dto).observe(getViewLifecycleOwner(), resource -> {
|
||||
viewModel.createSale(dto).observe(getViewLifecycleOwner(), resource -> {
|
||||
if (resource != null) {
|
||||
switch (resource.status) {
|
||||
case SUCCESS:
|
||||
Toast.makeText(getContext(), "Sale saved!", Toast.LENGTH_SHORT).show();
|
||||
navigateBack();
|
||||
break;
|
||||
case ERROR:
|
||||
Log.e("SALE_SAVE", "Error: " + resource.message);
|
||||
DialogUtils.showInfoDialog(requireContext(), "Save Error", resource.message);
|
||||
break;
|
||||
setLoading(resource.status == Resource.Status.LOADING);
|
||||
if (resource.status == Resource.Status.SUCCESS) {
|
||||
Toast.makeText(getContext(), "Sale saved!", Toast.LENGTH_SHORT).show();
|
||||
navigateBack();
|
||||
} else if (resource.status == Resource.Status.ERROR) {
|
||||
DialogUtils.showInfoDialog(requireContext(), "Save Error", resource.message);
|
||||
}
|
||||
}
|
||||
});
|
||||
@@ -313,7 +275,7 @@ public class SaleDetailFragment extends Fragment {
|
||||
DialogUtils.showConfirmDialog(requireContext(), "Process Refund",
|
||||
"Are you sure you want to process a refund for this sale?", () -> {
|
||||
Bundle args = new Bundle();
|
||||
args.putLong("saleId", saleId);
|
||||
args.putLong("saleId", viewModel.getSaleId());
|
||||
NavHostFragment.findNavController(this).navigate(R.id.nav_refund, args);
|
||||
});
|
||||
}
|
||||
|
||||
@@ -17,10 +17,11 @@ import com.example.petstoremobile.R;
|
||||
import com.example.petstoremobile.databinding.FragmentServiceDetailBinding;
|
||||
import com.example.petstoremobile.dtos.ServiceDTO;
|
||||
import com.example.petstoremobile.utils.ActivityLogger;
|
||||
import com.example.petstoremobile.utils.DateTimeUtils;
|
||||
import com.example.petstoremobile.utils.DialogUtils;
|
||||
import com.example.petstoremobile.utils.InputValidator;
|
||||
import com.example.petstoremobile.utils.Resource;
|
||||
import com.example.petstoremobile.viewmodels.ServiceViewModel;
|
||||
import com.example.petstoremobile.viewmodels.ServiceDetailViewModel;
|
||||
|
||||
import dagger.hilt.android.AndroidEntryPoint;
|
||||
|
||||
@@ -31,15 +32,12 @@ import dagger.hilt.android.AndroidEntryPoint;
|
||||
public class ServiceDetailFragment extends Fragment {
|
||||
|
||||
private FragmentServiceDetailBinding binding;
|
||||
private long serviceId;
|
||||
private boolean isEditing = false;
|
||||
|
||||
private ServiceViewModel viewModel;
|
||||
private ServiceDetailViewModel viewModel;
|
||||
|
||||
@Override
|
||||
public void onCreate(Bundle savedInstanceState) {
|
||||
super.onCreate(savedInstanceState);
|
||||
viewModel = new ViewModelProvider(this).get(ServiceViewModel.class);
|
||||
viewModel = new ViewModelProvider(this).get(ServiceDetailViewModel.class);
|
||||
}
|
||||
|
||||
@Override
|
||||
@@ -53,78 +51,67 @@ public class ServiceDetailFragment extends Fragment {
|
||||
public void onViewCreated(@NonNull View view, @Nullable Bundle savedInstanceState) {
|
||||
super.onViewCreated(view, savedInstanceState);
|
||||
|
||||
//get controls from layout and display the view depending on the mode
|
||||
handleArguments();
|
||||
|
||||
//set button click listeners
|
||||
binding.btnBack.setOnClickListener(v -> navigateBack());
|
||||
binding.btnSaveService.setOnClickListener(v -> saveService());
|
||||
binding.btnDeleteService.setOnClickListener(v -> deleteService());
|
||||
}
|
||||
|
||||
private void setLoading(boolean loading) {
|
||||
if (binding != null && binding.progressBar != null) {
|
||||
binding.progressBar.setVisibility(loading ? View.VISIBLE : View.GONE);
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onDestroyView() {
|
||||
super.onDestroyView();
|
||||
binding = null;
|
||||
}
|
||||
|
||||
/**
|
||||
* Handles the saving of service data (adding or updating).
|
||||
*/
|
||||
private void saveService() {
|
||||
// Validates all fields using InputValidator
|
||||
if (!InputValidator.isNotEmpty(binding.etServiceName, "Service Name")) return;
|
||||
if (!InputValidator.isNotEmpty(binding.etServiceDesc, "Description")) return;
|
||||
if (!InputValidator.isPositiveInteger(binding.etServiceDuration, "Duration")) return;
|
||||
if (!InputValidator.isPositiveDecimal(binding.etServicePrice, "Price")) return;
|
||||
|
||||
//get all the values from the fields
|
||||
String name = binding.etServiceName.getText().toString().trim();
|
||||
String desc = binding.etServiceDesc.getText().toString().trim();
|
||||
int duration = Integer.parseInt(binding.etServiceDuration.getText().toString().trim());
|
||||
double price = Double.parseDouble(binding.etServicePrice.getText().toString().trim());
|
||||
|
||||
//create a service object to send to the API
|
||||
ServiceDTO serviceDTO = new ServiceDTO();
|
||||
serviceDTO.setServiceName(name);
|
||||
serviceDTO.setServiceDesc(desc);
|
||||
serviceDTO.setServiceDuration(duration);
|
||||
serviceDTO.setServicePrice(price);
|
||||
|
||||
//check if the service is being edited or added
|
||||
if (isEditing) {
|
||||
// Update existing service
|
||||
serviceDTO.setServiceId(serviceId);
|
||||
viewModel.updateService(serviceId, serviceDTO).observe(getViewLifecycleOwner(), resource -> {
|
||||
if (resource.status == Resource.Status.SUCCESS) {
|
||||
ActivityLogger.logChange(requireContext(), "Service", "UPDATED", (int) serviceId);
|
||||
viewModel.saveService(serviceDTO).observe(getViewLifecycleOwner(), resource -> {
|
||||
if (resource == null) return;
|
||||
setLoading(resource.status == Resource.Status.LOADING);
|
||||
if (resource.status == Resource.Status.SUCCESS) {
|
||||
if (viewModel.isEditing()) {
|
||||
ActivityLogger.logChange(requireContext(), "Service", "UPDATED", (int) viewModel.getServiceId());
|
||||
Toast.makeText(getContext(), "Service updated successfully!", Toast.LENGTH_SHORT).show();
|
||||
navigateBack();
|
||||
} else if (resource.status == Resource.Status.ERROR) {
|
||||
Toast.makeText(getContext(), "Error: " + resource.message, Toast.LENGTH_SHORT).show();
|
||||
}
|
||||
});
|
||||
} else {
|
||||
viewModel.createService(serviceDTO).observe(getViewLifecycleOwner(), resource -> {
|
||||
if (resource.status == Resource.Status.SUCCESS) {
|
||||
} else {
|
||||
ActivityLogger.log(requireContext(), "Added new Service: " + name);
|
||||
Toast.makeText(getContext(), "Service added successfully!", Toast.LENGTH_SHORT).show();
|
||||
navigateBack();
|
||||
} else if (resource.status == Resource.Status.ERROR) {
|
||||
Toast.makeText(getContext(), "Error: " + resource.message, Toast.LENGTH_SHORT).show();
|
||||
}
|
||||
});
|
||||
}
|
||||
navigateBack();
|
||||
} else if (resource.status == Resource.Status.ERROR) {
|
||||
Toast.makeText(getContext(), "Error: " + resource.message, Toast.LENGTH_SHORT).show();
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* Displays a confirmation dialog and handles the deletion of a service.
|
||||
*/
|
||||
private void deleteService() {
|
||||
DialogUtils.showDeleteConfirmDialog(requireContext(), "Service", () ->
|
||||
viewModel.deleteService(serviceId).observe(getViewLifecycleOwner(), resource -> {
|
||||
viewModel.deleteService().observe(getViewLifecycleOwner(), resource -> {
|
||||
if (resource == null) return;
|
||||
setLoading(resource.status == Resource.Status.LOADING);
|
||||
if (resource.status == Resource.Status.SUCCESS) {
|
||||
ActivityLogger.logChange(requireContext(), "Service", "DELETED", (int) serviceId);
|
||||
ActivityLogger.logChange(requireContext(), "Service", "DELETED", (int) viewModel.getServiceId());
|
||||
Toast.makeText(getContext(), "Service deleted successfully!", Toast.LENGTH_SHORT).show();
|
||||
navigateBack();
|
||||
} else if (resource.status == Resource.Status.ERROR) {
|
||||
@@ -133,30 +120,20 @@ public class ServiceDetailFragment extends Fragment {
|
||||
}));
|
||||
}
|
||||
|
||||
/**
|
||||
* Navigates back to the previous screen.
|
||||
*/
|
||||
private void navigateBack() {
|
||||
NavHostFragment.findNavController(this).popBackStack();
|
||||
}
|
||||
|
||||
/**
|
||||
* Handles arguments passed to the fragment to determine if it's in edit or add mode.
|
||||
*/
|
||||
private void handleArguments() {
|
||||
// Service is being edited if the bundle contains a serviceId
|
||||
if (getArguments() != null && getArguments().containsKey("serviceId")) {
|
||||
// Get service data from arguments and populate fields
|
||||
isEditing = true;
|
||||
serviceId = getArguments().getLong("serviceId");
|
||||
long serviceId = getArguments().getLong("serviceId");
|
||||
viewModel.setServiceId(serviceId);
|
||||
binding.tvMode.setText("Edit Service");
|
||||
binding.tvServiceId.setText("ID: " + serviceId);
|
||||
binding.tvServiceId.setText(DateTimeUtils.formatId(serviceId));
|
||||
binding.btnDeleteService.setVisibility(View.VISIBLE);
|
||||
loadServiceData();
|
||||
} else {
|
||||
// Service is being added
|
||||
// Set default values for add a new service
|
||||
isEditing = false;
|
||||
viewModel.setServiceId(-1);
|
||||
binding.tvMode.setText("Add Service");
|
||||
binding.tvServiceId.setVisibility(View.GONE);
|
||||
binding.btnDeleteService.setVisibility(View.GONE);
|
||||
@@ -164,12 +141,10 @@ public class ServiceDetailFragment extends Fragment {
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Fetches specific service details from the backend using the ID.
|
||||
*/
|
||||
private void loadServiceData() {
|
||||
viewModel.getServiceById(serviceId).observe(getViewLifecycleOwner(), resource -> {
|
||||
viewModel.loadService().observe(getViewLifecycleOwner(), resource -> {
|
||||
if (resource == null) return;
|
||||
setLoading(resource.status == Resource.Status.LOADING);
|
||||
if (resource.status == Resource.Status.SUCCESS && resource.data != null) {
|
||||
ServiceDTO s = resource.data;
|
||||
binding.etServiceName.setText(s.getServiceName());
|
||||
|
||||
@@ -1,27 +1,28 @@
|
||||
package com.example.petstoremobile.fragments.listfragments.detailfragments;
|
||||
|
||||
import android.os.Bundle;
|
||||
import android.util.Log;
|
||||
import android.view.*;
|
||||
import android.widget.*;
|
||||
import androidx.annotation.NonNull;
|
||||
import androidx.appcompat.app.AlertDialog;
|
||||
import androidx.fragment.app.Fragment;
|
||||
import androidx.lifecycle.ViewModelProvider;
|
||||
import androidx.navigation.fragment.NavHostFragment;
|
||||
import com.example.petstoremobile.R;
|
||||
import com.example.petstoremobile.databinding.FragmentStaffDetailBinding;
|
||||
import com.example.petstoremobile.dtos.EmployeeDTO;
|
||||
import com.example.petstoremobile.viewmodels.EmployeeViewModel;
|
||||
import com.example.petstoremobile.utils.DialogUtils;
|
||||
import com.example.petstoremobile.utils.InputValidator;
|
||||
import com.example.petstoremobile.utils.SpinnerUtils;
|
||||
import com.example.petstoremobile.utils.UIUtils;
|
||||
import com.example.petstoremobile.viewmodels.StaffDetailViewModel;
|
||||
import com.example.petstoremobile.utils.Resource;
|
||||
import dagger.hilt.android.AndroidEntryPoint;
|
||||
|
||||
@AndroidEntryPoint
|
||||
public class StaffDetailFragment extends Fragment {
|
||||
|
||||
private FragmentStaffDetailBinding binding;
|
||||
private EmployeeViewModel employeeViewModel;
|
||||
private long employeeId = -1;
|
||||
private boolean isEditing = false;
|
||||
private StaffDetailViewModel viewModel;
|
||||
|
||||
private final String[] ROLES = {"STAFF", "ADMIN"};
|
||||
private final String[] STATUSES = {"Active", "Inactive"};
|
||||
@@ -30,7 +31,7 @@ public class StaffDetailFragment extends Fragment {
|
||||
public View onCreateView(@NonNull LayoutInflater inflater, ViewGroup container,
|
||||
Bundle savedInstanceState) {
|
||||
binding = FragmentStaffDetailBinding.inflate(inflater, container, false);
|
||||
employeeViewModel = new ViewModelProvider(this).get(EmployeeViewModel.class);
|
||||
viewModel = new ViewModelProvider(this).get(StaffDetailViewModel.class);
|
||||
|
||||
setupSpinners();
|
||||
handleArguments();
|
||||
@@ -38,21 +39,22 @@ public class StaffDetailFragment extends Fragment {
|
||||
binding.btnStaffBack.setOnClickListener(v -> navigateBack());
|
||||
binding.btnSaveStaff.setOnClickListener(v -> save());
|
||||
binding.btnDeleteStaff.setOnClickListener(v -> confirmDelete());
|
||||
|
||||
UIUtils.formatPhoneInput(binding.etStaffPhone);
|
||||
|
||||
return binding.getRoot();
|
||||
}
|
||||
|
||||
private void setupSpinners() {
|
||||
binding.spinnerStaffRole.setAdapter(new ArrayAdapter<>(requireContext(),
|
||||
android.R.layout.simple_spinner_item, ROLES));
|
||||
binding.spinnerStaffStatus.setAdapter(new ArrayAdapter<>(requireContext(),
|
||||
android.R.layout.simple_spinner_item, STATUSES));
|
||||
SpinnerUtils.setupStringSpinner(requireContext(), binding.spinnerStaffRole, ROLES);
|
||||
SpinnerUtils.setupStringSpinner(requireContext(), binding.spinnerStaffStatus, STATUSES);
|
||||
}
|
||||
|
||||
private void handleArguments() {
|
||||
Bundle a = getArguments();
|
||||
if (a != null && a.getBoolean("isEditing", false)) {
|
||||
isEditing = true;
|
||||
employeeId = a.getLong("employeeId", -1);
|
||||
long employeeId = a.getLong("employeeId", -1);
|
||||
viewModel.setEmployeeId(employeeId, true);
|
||||
|
||||
binding.tvStaffMode.setText("Edit Staff Account");
|
||||
binding.tvStaffId.setText("ID: " + employeeId);
|
||||
@@ -64,52 +66,50 @@ public class StaffDetailFragment extends Fragment {
|
||||
binding.etStaffPhone.setText(a.getString("phone", ""));
|
||||
binding.btnDeleteStaff.setVisibility(View.VISIBLE);
|
||||
|
||||
// Pre-fill role
|
||||
String role = a.getString("role", "STAFF");
|
||||
for (int i = 0; i < ROLES.length; i++) {
|
||||
if (ROLES[i].equals(role)) {
|
||||
binding.spinnerStaffRole.setSelection(i);
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
// Pre-fill status
|
||||
boolean active = a.getBoolean("active", true);
|
||||
binding.spinnerStaffStatus.setSelection(active ? 0 : 1);
|
||||
SpinnerUtils.setSelectionByValue(binding.spinnerStaffRole, a.getString("role", "STAFF"));
|
||||
binding.spinnerStaffStatus.setSelection(a.getBoolean("active", true) ? 0 : 1);
|
||||
|
||||
} else {
|
||||
isEditing = false;
|
||||
employeeId = -1;
|
||||
viewModel.setEmployeeId(-1, false);
|
||||
binding.tvStaffMode.setText("Add Staff Account");
|
||||
binding.btnDeleteStaff.setVisibility(View.GONE);
|
||||
binding.tvStaffId.setVisibility(View.GONE);
|
||||
}
|
||||
}
|
||||
|
||||
private void setLoading(boolean loading) {
|
||||
if (binding != null && binding.progressBar != null) {
|
||||
binding.progressBar.setVisibility(loading ? View.VISIBLE : View.GONE);
|
||||
}
|
||||
}
|
||||
|
||||
private void save() {
|
||||
String username = binding.etStaffUsername.getText() != null ? binding.etStaffUsername.getText().toString().trim() : "";
|
||||
String password = binding.etStaffPassword.getText() != null ? binding.etStaffPassword.getText().toString().trim() : "";
|
||||
String firstName = binding.etStaffFirstName.getText() != null ? binding.etStaffFirstName.getText().toString().trim() : "";
|
||||
String lastName = binding.etStaffLastName.getText() != null ? binding.etStaffLastName.getText().toString().trim() : "";
|
||||
String email = binding.etStaffEmail.getText() != null ? binding.etStaffEmail.getText().toString().trim() : "";
|
||||
String phone = binding.etStaffPhone.getText() != null ? binding.etStaffPhone.getText().toString().trim() : "";
|
||||
if (!InputValidator.isNotEmpty(binding.etStaffUsername, "Username")) return;
|
||||
|
||||
if (!viewModel.isEditing()) {
|
||||
if (!InputValidator.isNotEmpty(binding.etStaffPassword, "Password")) return;
|
||||
String pass = binding.etStaffPassword.getText().toString();
|
||||
if (pass.length() < 6) {
|
||||
binding.etStaffPassword.setError("At least 6 characters");
|
||||
binding.etStaffPassword.requestFocus();
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
if (!InputValidator.isNotEmpty(binding.etStaffFirstName, "First Name")) return;
|
||||
if (!InputValidator.isNotEmpty(binding.etStaffLastName, "Last Name")) return;
|
||||
if (!InputValidator.isValidEmail(binding.etStaffEmail)) return;
|
||||
if (!InputValidator.isValidPhone(binding.etStaffPhone)) return;
|
||||
|
||||
String username = binding.etStaffUsername.getText().toString().trim();
|
||||
String password = binding.etStaffPassword.getText().toString().trim();
|
||||
String firstName = binding.etStaffFirstName.getText().toString().trim();
|
||||
String lastName = binding.etStaffLastName.getText().toString().trim();
|
||||
String email = binding.etStaffEmail.getText().toString().trim();
|
||||
String phone = binding.etStaffPhone.getText().toString().trim();
|
||||
String role = ROLES[binding.spinnerStaffRole.getSelectedItemPosition()];
|
||||
boolean active = binding.spinnerStaffStatus.getSelectedItemPosition() == 0;
|
||||
|
||||
// Validation
|
||||
if (username.isEmpty()) { binding.etStaffUsername.setError("Required"); return; }
|
||||
if (!isEditing && password.isEmpty()) {
|
||||
binding.etStaffPassword.setError("Required for new account"); return;
|
||||
}
|
||||
if (!isEditing && password.length() < 6) {
|
||||
binding.etStaffPassword.setError("At least 6 characters"); return;
|
||||
}
|
||||
if (firstName.isEmpty()) { binding.etStaffFirstName.setError("Required"); return; }
|
||||
if (lastName.isEmpty()) { binding.etStaffLastName.setError("Required"); return; }
|
||||
if (email.isEmpty()) { binding.etStaffEmail.setError("Required"); return; }
|
||||
if (phone.isEmpty()) { binding.etStaffPhone.setError("Required"); return; }
|
||||
|
||||
EmployeeDTO dto = new EmployeeDTO(
|
||||
username,
|
||||
password.isEmpty() ? null : password,
|
||||
@@ -121,56 +121,32 @@ public class StaffDetailFragment extends Fragment {
|
||||
active
|
||||
);
|
||||
|
||||
if (isEditing && employeeId > 0) {
|
||||
employeeViewModel.updateEmployee(employeeId, dto).observe(getViewLifecycleOwner(), resource -> {
|
||||
if (resource != null) {
|
||||
switch (resource.status) {
|
||||
case SUCCESS:
|
||||
Toast.makeText(getContext(), "Updated successfully", Toast.LENGTH_SHORT).show();
|
||||
navigateBack();
|
||||
break;
|
||||
case ERROR:
|
||||
Toast.makeText(getContext(), "Error: " + resource.message, Toast.LENGTH_LONG).show();
|
||||
break;
|
||||
}
|
||||
viewModel.saveEmployee(dto).observe(getViewLifecycleOwner(), resource -> {
|
||||
if (resource != null) {
|
||||
setLoading(resource.status == Resource.Status.LOADING);
|
||||
if (resource.status == Resource.Status.SUCCESS) {
|
||||
Toast.makeText(getContext(), viewModel.isEditing() ? "Updated successfully" : "Staff account created", Toast.LENGTH_SHORT).show();
|
||||
navigateBack();
|
||||
} else if (resource.status == Resource.Status.ERROR) {
|
||||
Toast.makeText(getContext(), "Error: " + resource.message, Toast.LENGTH_LONG).show();
|
||||
}
|
||||
});
|
||||
} else {
|
||||
employeeViewModel.createEmployee(dto).observe(getViewLifecycleOwner(), resource -> {
|
||||
if (resource != null) {
|
||||
switch (resource.status) {
|
||||
case SUCCESS:
|
||||
Toast.makeText(getContext(), "Staff account created", Toast.LENGTH_SHORT).show();
|
||||
navigateBack();
|
||||
break;
|
||||
case ERROR:
|
||||
Toast.makeText(getContext(), "Error: " + resource.message, Toast.LENGTH_LONG).show();
|
||||
break;
|
||||
}
|
||||
}
|
||||
});
|
||||
}
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
private void confirmDelete() {
|
||||
new AlertDialog.Builder(requireContext())
|
||||
.setTitle("Delete Staff Account?")
|
||||
.setMessage("This will permanently delete this staff account.")
|
||||
.setPositiveButton("Yes", (d, w) ->
|
||||
employeeViewModel.deleteEmployee(employeeId).observe(getViewLifecycleOwner(), resource -> {
|
||||
if (resource != null) {
|
||||
switch (resource.status) {
|
||||
case SUCCESS:
|
||||
navigateBack();
|
||||
break;
|
||||
case ERROR:
|
||||
Toast.makeText(getContext(), "Delete failed: " + resource.message,
|
||||
Toast.LENGTH_SHORT).show();
|
||||
break;
|
||||
}
|
||||
}
|
||||
}))
|
||||
.setNegativeButton("No", null).show();
|
||||
DialogUtils.showDeleteConfirmDialog(requireContext(), "Staff Account", () ->
|
||||
viewModel.deleteEmployee().observe(getViewLifecycleOwner(), resource -> {
|
||||
if (resource != null) {
|
||||
setLoading(resource.status == Resource.Status.LOADING);
|
||||
if (resource.status == Resource.Status.SUCCESS) {
|
||||
navigateBack();
|
||||
} else if (resource.status == Resource.Status.ERROR) {
|
||||
Toast.makeText(getContext(), "Delete failed: " + resource.message,
|
||||
Toast.LENGTH_SHORT).show();
|
||||
}
|
||||
}
|
||||
}));
|
||||
}
|
||||
|
||||
private void navigateBack() {
|
||||
|
||||
@@ -20,7 +20,7 @@ import com.example.petstoremobile.utils.DialogUtils;
|
||||
import com.example.petstoremobile.utils.InputValidator;
|
||||
import com.example.petstoremobile.utils.Resource;
|
||||
import com.example.petstoremobile.utils.UIUtils;
|
||||
import com.example.petstoremobile.viewmodels.SupplierViewModel;
|
||||
import com.example.petstoremobile.viewmodels.SupplierDetailViewModel;
|
||||
|
||||
import dagger.hilt.android.AndroidEntryPoint;
|
||||
|
||||
@@ -31,15 +31,12 @@ import dagger.hilt.android.AndroidEntryPoint;
|
||||
public class SupplierDetailFragment extends Fragment {
|
||||
|
||||
private FragmentSupplierDetailBinding binding;
|
||||
private long supId;
|
||||
private boolean isEditing = false;
|
||||
|
||||
private SupplierViewModel viewModel;
|
||||
private SupplierDetailViewModel viewModel;
|
||||
|
||||
@Override
|
||||
public void onCreate(Bundle savedInstanceState) {
|
||||
super.onCreate(savedInstanceState);
|
||||
viewModel = new ViewModelProvider(this).get(SupplierViewModel.class);
|
||||
viewModel = new ViewModelProvider(this).get(SupplierDetailViewModel.class);
|
||||
}
|
||||
|
||||
@Override
|
||||
@@ -53,42 +50,39 @@ public class SupplierDetailFragment extends Fragment {
|
||||
public void onViewCreated(@NonNull View view, @Nullable Bundle savedInstanceState) {
|
||||
super.onViewCreated(view, savedInstanceState);
|
||||
|
||||
// Add phone number formatting (CA) and limit length to 14 characters
|
||||
UIUtils.formatPhoneInput(binding.etSupPhone);
|
||||
|
||||
handleArguments();
|
||||
|
||||
//set button click listeners
|
||||
binding.btnBack.setOnClickListener(v -> navigateBack());
|
||||
binding.btnSaveSupplier.setOnClickListener(v -> saveSupplier());
|
||||
binding.btnDeleteSupplier.setOnClickListener(v -> deleteSupplier());
|
||||
}
|
||||
|
||||
private void setLoading(boolean loading) {
|
||||
if (binding != null && binding.progressBar != null) {
|
||||
binding.progressBar.setVisibility(loading ? View.VISIBLE : View.GONE);
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onDestroyView() {
|
||||
super.onDestroyView();
|
||||
binding = null;
|
||||
}
|
||||
|
||||
/**
|
||||
* Handles the saving of supplier data (adding or updating).
|
||||
*/
|
||||
private void saveSupplier() {
|
||||
// Validates all fields using InputValidator
|
||||
if (!InputValidator.isNotEmpty(binding.etSupCompany, "Company Name")) return;
|
||||
if (!InputValidator.isNotEmpty(binding.etSupContactFirstName, "First Name")) return;
|
||||
if (!InputValidator.isNotEmpty(binding.etSupContactLastName, "Last Name")) return;
|
||||
if (!InputValidator.isValidEmail(binding.etSupEmail)) return;
|
||||
if (!InputValidator.isValidPhone(binding.etSupPhone)) return;
|
||||
|
||||
//get all the values from the fields
|
||||
String company = binding.etSupCompany.getText().toString().trim();
|
||||
String firstName = binding.etSupContactFirstName.getText().toString().trim();
|
||||
String lastName = binding.etSupContactLastName.getText().toString().trim();
|
||||
String email = binding.etSupEmail.getText().toString().trim();
|
||||
String phone = binding.etSupPhone.getText().toString().trim();
|
||||
|
||||
//create a supplier object to send to the API
|
||||
SupplierDTO supplierDTO = new SupplierDTO();
|
||||
supplierDTO.setSupCompany(company);
|
||||
supplierDTO.setSupContactFirstName(firstName);
|
||||
@@ -96,41 +90,31 @@ public class SupplierDetailFragment extends Fragment {
|
||||
supplierDTO.setSupEmail(email);
|
||||
supplierDTO.setSupPhone(phone);
|
||||
|
||||
//check if the supplier is being edited or added
|
||||
if (isEditing) {
|
||||
// Update existing supplier
|
||||
supplierDTO.setSupId(supId);
|
||||
viewModel.updateSupplier(supId, supplierDTO).observe(getViewLifecycleOwner(), resource -> {
|
||||
if (resource.status == Resource.Status.SUCCESS) {
|
||||
ActivityLogger.logChange(requireContext(), "Supplier", "UPDATED", (int) supId);
|
||||
viewModel.saveSupplier(supplierDTO).observe(getViewLifecycleOwner(), resource -> {
|
||||
if (resource == null) return;
|
||||
setLoading(resource.status == Resource.Status.LOADING);
|
||||
if (resource.status == Resource.Status.SUCCESS) {
|
||||
if (viewModel.isEditing()) {
|
||||
ActivityLogger.logChange(requireContext(), "Supplier", "UPDATED", (int) viewModel.getSupId());
|
||||
Toast.makeText(getContext(), "Supplier updated successfully!", Toast.LENGTH_SHORT).show();
|
||||
navigateBack();
|
||||
} else if (resource.status == Resource.Status.ERROR) {
|
||||
Toast.makeText(getContext(), "Error: " + resource.message, Toast.LENGTH_SHORT).show();
|
||||
}
|
||||
});
|
||||
} else {
|
||||
// Add new supplier
|
||||
viewModel.createSupplier(supplierDTO).observe(getViewLifecycleOwner(), resource -> {
|
||||
if (resource.status == Resource.Status.SUCCESS) {
|
||||
} else {
|
||||
ActivityLogger.log(requireContext(), "Added new Supplier: " + company);
|
||||
Toast.makeText(getContext(), "Supplier added successfully!", Toast.LENGTH_SHORT).show();
|
||||
navigateBack();
|
||||
} else if (resource.status == Resource.Status.ERROR) {
|
||||
Toast.makeText(getContext(), "Error: " + resource.message, Toast.LENGTH_SHORT).show();
|
||||
}
|
||||
});
|
||||
}
|
||||
navigateBack();
|
||||
} else if (resource.status == Resource.Status.ERROR) {
|
||||
Toast.makeText(getContext(), "Error: " + resource.message, Toast.LENGTH_SHORT).show();
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* Displays a confirmation dialog and handles the deletion of a supplier.
|
||||
*/
|
||||
private void deleteSupplier() {
|
||||
DialogUtils.showDeleteConfirmDialog(requireContext(), "Supplier", () ->
|
||||
viewModel.deleteSupplier(supId).observe(getViewLifecycleOwner(), resource -> {
|
||||
viewModel.deleteSupplier().observe(getViewLifecycleOwner(), resource -> {
|
||||
if (resource == null) return;
|
||||
setLoading(resource.status == Resource.Status.LOADING);
|
||||
if (resource.status == Resource.Status.SUCCESS) {
|
||||
ActivityLogger.logChange(requireContext(), "Supplier", "DELETED", (int) supId);
|
||||
ActivityLogger.logChange(requireContext(), "Supplier", "DELETED", (int) viewModel.getSupId());
|
||||
Toast.makeText(getContext(), "Supplier deleted successfully!", Toast.LENGTH_SHORT).show();
|
||||
navigateBack();
|
||||
} else if (resource.status == Resource.Status.ERROR) {
|
||||
@@ -139,31 +123,21 @@ public class SupplierDetailFragment extends Fragment {
|
||||
}));
|
||||
}
|
||||
|
||||
/**
|
||||
* Navigates back to the previous screen.
|
||||
*/
|
||||
private void navigateBack() {
|
||||
NavHostFragment.findNavController(this).popBackStack();
|
||||
}
|
||||
|
||||
/**
|
||||
* Handles arguments passed to the fragment to determine if it's in edit or add mode.
|
||||
*/
|
||||
private void handleArguments() {
|
||||
// Supplier is being edited if the bundle contains a supId
|
||||
if (getArguments() != null && getArguments().containsKey("supId")) {
|
||||
// Get supplier data from arguments and populate fields
|
||||
isEditing = true;
|
||||
supId = getArguments().getLong("supId");
|
||||
long supId = getArguments().getLong("supId");
|
||||
viewModel.setSupId(supId);
|
||||
binding.tvMode.setText("Edit Supplier");
|
||||
binding.tvSupId.setText("ID: " + supId);
|
||||
binding.tvSupId.setVisibility(View.VISIBLE);
|
||||
binding.btnDeleteSupplier.setVisibility(View.VISIBLE);
|
||||
loadSupplierData();
|
||||
} else {
|
||||
// Supplier is being added
|
||||
// Set default values for add a new supplier
|
||||
isEditing = false;
|
||||
viewModel.setSupId(-1);
|
||||
binding.tvMode.setText("Add Supplier");
|
||||
binding.tvSupId.setVisibility(View.GONE);
|
||||
binding.btnDeleteSupplier.setVisibility(View.GONE);
|
||||
@@ -171,12 +145,10 @@ public class SupplierDetailFragment extends Fragment {
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Fetches specific supplier details from the backend using the ID.
|
||||
*/
|
||||
private void loadSupplierData() {
|
||||
viewModel.getSupplierById(supId).observe(getViewLifecycleOwner(), resource -> {
|
||||
viewModel.loadSupplier().observe(getViewLifecycleOwner(), resource -> {
|
||||
if (resource == null) return;
|
||||
setLoading(resource.status == Resource.Status.LOADING);
|
||||
if (resource.status == Resource.Status.SUCCESS && resource.data != null) {
|
||||
SupplierDTO s = resource.data;
|
||||
binding.etSupCompany.setText(s.getSupCompany());
|
||||
|
||||
@@ -23,7 +23,7 @@ import com.example.petstoremobile.utils.FileUtils;
|
||||
import com.example.petstoremobile.utils.GlideUtils;
|
||||
import com.example.petstoremobile.utils.ImagePickerHelper;
|
||||
import com.example.petstoremobile.utils.Resource;
|
||||
import com.example.petstoremobile.viewmodels.PetViewModel;
|
||||
import com.example.petstoremobile.viewmodels.PetProfileViewModel;
|
||||
|
||||
import java.io.File;
|
||||
import java.util.Locale;
|
||||
@@ -46,17 +46,13 @@ public class PetProfileFragment extends Fragment {
|
||||
@Inject @Named("baseUrl") String baseUrl;
|
||||
@Inject TokenManager tokenManager;
|
||||
|
||||
private PetViewModel viewModel;
|
||||
private PetProfileViewModel viewModel;
|
||||
private ImagePickerHelper imagePickerHelper;
|
||||
|
||||
|
||||
/**
|
||||
* Initializes activity launchers for gallery, camera, and permissions.
|
||||
*/
|
||||
@Override
|
||||
public void onCreate(Bundle savedInstanceState) {
|
||||
super.onCreate(savedInstanceState);
|
||||
viewModel = new ViewModelProvider(this).get(PetViewModel.class);
|
||||
viewModel = new ViewModelProvider(this).get(PetProfileViewModel.class);
|
||||
|
||||
imagePickerHelper = new ImagePickerHelper(this, "pet_photo.jpg", new ImagePickerHelper.ImagePickerListener() {
|
||||
@Override
|
||||
@@ -71,34 +67,27 @@ public class PetProfileFragment extends Fragment {
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* Inflates the layout using view binding, initializes views, and sets up click listeners.
|
||||
*/
|
||||
@Override
|
||||
public View onCreateView(@NonNull LayoutInflater inflater, ViewGroup container,
|
||||
Bundle savedInstanceState) {
|
||||
binding = FragmentPetProfileBinding.inflate(inflater, container, false);
|
||||
|
||||
// Set pet details to display
|
||||
if (getArguments() != null) {
|
||||
petId = getArguments().getLong("petId");
|
||||
loadPetData();
|
||||
loadPetImage((int) petId);
|
||||
}
|
||||
|
||||
//set button click listeners
|
||||
binding.btnBack.setOnClickListener(v -> {
|
||||
NavHostFragment.findNavController(this).popBackStack();
|
||||
});
|
||||
|
||||
//Make the edit button go to the pet detail view
|
||||
binding.btnEditPet.setOnClickListener(v -> {
|
||||
Bundle args = new Bundle();
|
||||
args.putLong("petId", petId);
|
||||
NavHostFragment.findNavController(this).navigate(R.id.nav_pet_detail, args);
|
||||
});
|
||||
|
||||
//Make change photo button ask user to select a new photo
|
||||
binding.btnChangePhoto.setOnClickListener(v -> {
|
||||
imagePickerHelper.showImagePickerDialog("Change Pet Photo", hasImage);
|
||||
});
|
||||
@@ -106,18 +95,22 @@ public class PetProfileFragment extends Fragment {
|
||||
return binding.getRoot();
|
||||
}
|
||||
|
||||
private void setLoading(boolean loading) {
|
||||
if (binding != null && binding.progressBar != null) {
|
||||
binding.progressBar.setVisibility(loading ? View.VISIBLE : View.GONE);
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onDestroyView() {
|
||||
super.onDestroyView();
|
||||
binding = null;
|
||||
}
|
||||
|
||||
/**
|
||||
* Fetches current pet data from the backend and updates the UI.
|
||||
*/
|
||||
private void loadPetData() {
|
||||
viewModel.getPetById(petId).observe(getViewLifecycleOwner(), resource -> {
|
||||
if (resource == null) return;
|
||||
setLoading(resource.status == Resource.Status.LOADING);
|
||||
if (resource.status == Resource.Status.SUCCESS && resource.data != null) {
|
||||
PetDTO pet = resource.data;
|
||||
binding.tvPetName.setText(pet.getPetName());
|
||||
@@ -133,7 +126,6 @@ public class PetProfileFragment extends Fragment {
|
||||
|
||||
String status = pet.getPetStatus();
|
||||
|
||||
// Display owner name only if the pet is Adopted or Owned
|
||||
if ("Adopted".equalsIgnoreCase(status) || "Owned".equalsIgnoreCase(status)) {
|
||||
binding.layoutPetOwner.setVisibility(View.VISIBLE);
|
||||
if (pet.getCustomerName() != null && !pet.getCustomerName().isEmpty()) {
|
||||
@@ -145,7 +137,6 @@ public class PetProfileFragment extends Fragment {
|
||||
binding.layoutPetOwner.setVisibility(View.GONE);
|
||||
}
|
||||
|
||||
// Display store name only if the pet is Adopted or Available
|
||||
if ("Available".equalsIgnoreCase(status) || "Adopted".equalsIgnoreCase(status)) {
|
||||
binding.layoutPetStore.setVisibility(View.VISIBLE);
|
||||
if (pet.getStoreName() != null && !pet.getStoreName().isEmpty()) {
|
||||
@@ -162,9 +153,6 @@ public class PetProfileFragment extends Fragment {
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* Fetches and displays the pet\'s image from the server.
|
||||
*/
|
||||
private void loadPetImage(int petId) {
|
||||
String imageUrl = baseUrl + String.format(Locale.US, PetApi.PET_IMAGE_PATH, petId);
|
||||
String token = tokenManager.getToken();
|
||||
@@ -182,27 +170,22 @@ public class PetProfileFragment extends Fragment {
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* Uploads a selected or captured image a pet photo through the ViewModel.
|
||||
*/
|
||||
private void uploadPetImage(Uri uri) {
|
||||
try {
|
||||
File file = FileUtils.getFileFromUri(requireContext(), uri);
|
||||
if (file == null) return;
|
||||
|
||||
// Create RequestBody for file upload
|
||||
RequestBody requestFile = RequestBody.create(file, MediaType.parse(requireContext().getContentResolver().getType(uri)));
|
||||
MultipartBody.Part body = MultipartBody.Part.createFormData("image", file.getName(), requestFile);
|
||||
|
||||
// Use ViewModel to upload image
|
||||
viewModel.uploadPetImage(petId, body).observe(getViewLifecycleOwner(), resource -> {
|
||||
if (resource != null && resource.status != Resource.Status.LOADING) {
|
||||
if (resource.status == Resource.Status.SUCCESS) {
|
||||
Toast.makeText(getContext(), "Pet photo updated successfully", Toast.LENGTH_SHORT).show();
|
||||
loadPetImage((int) petId);
|
||||
} else {
|
||||
Toast.makeText(getContext(), "Upload failed: " + resource.message, Toast.LENGTH_SHORT).show();
|
||||
}
|
||||
if (resource == null) return;
|
||||
setLoading(resource.status == Resource.Status.LOADING);
|
||||
if (resource.status == Resource.Status.SUCCESS) {
|
||||
Toast.makeText(getContext(), "Pet photo updated successfully", Toast.LENGTH_SHORT).show();
|
||||
loadPetImage((int) petId);
|
||||
} else if (resource.status == Resource.Status.ERROR) {
|
||||
Toast.makeText(getContext(), "Upload failed: " + resource.message, Toast.LENGTH_SHORT).show();
|
||||
}
|
||||
});
|
||||
} catch (Exception e) {
|
||||
@@ -210,19 +193,16 @@ public class PetProfileFragment extends Fragment {
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Sends a request to the ViewModel to remove the current pet photo.
|
||||
*/
|
||||
private void deletePetImage() {
|
||||
viewModel.deletePetImage(petId).observe(getViewLifecycleOwner(), resource -> {
|
||||
if (resource != null && resource.status != Resource.Status.LOADING) {
|
||||
if (resource.status == Resource.Status.SUCCESS) {
|
||||
Toast.makeText(getContext(), "Pet photo removed", Toast.LENGTH_SHORT).show();
|
||||
hasImage = false;
|
||||
binding.imgPet.setImageResource(R.drawable.placeholder);
|
||||
} else {
|
||||
Toast.makeText(getContext(), "Delete failed: " + resource.message, Toast.LENGTH_SHORT).show();
|
||||
}
|
||||
if (resource == null) return;
|
||||
setLoading(resource.status == Resource.Status.LOADING);
|
||||
if (resource.status == Resource.Status.SUCCESS) {
|
||||
Toast.makeText(getContext(), "Pet photo removed", Toast.LENGTH_SHORT).show();
|
||||
hasImage = false;
|
||||
binding.imgPet.setImageResource(R.drawable.placeholder);
|
||||
} else if (resource.status == Resource.Status.ERROR) {
|
||||
Toast.makeText(getContext(), "Delete failed: " + resource.message, Toast.LENGTH_SHORT).show();
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
@@ -6,13 +6,15 @@ public class Chat {
|
||||
private String lastMessage;
|
||||
private Long customerId;
|
||||
private Long staffId;
|
||||
private String status;
|
||||
|
||||
public Chat(String chatId, String customerName, String lastMessage, Long customerId, Long staffId) {
|
||||
public Chat(String chatId, String customerName, String lastMessage, Long customerId, Long staffId, String status) {
|
||||
this.chatId = chatId;
|
||||
this.customerName = customerName;
|
||||
this.lastMessage = lastMessage;
|
||||
this.customerId = customerId;
|
||||
this.staffId = staffId;
|
||||
this.status = status;
|
||||
}
|
||||
|
||||
public String getChatId() {
|
||||
@@ -34,4 +36,8 @@ public class Chat {
|
||||
public Long getStaffId() {
|
||||
return staffId;
|
||||
}
|
||||
|
||||
public String getStatus() {
|
||||
return status;
|
||||
}
|
||||
}
|
||||
@@ -9,7 +9,8 @@ public class Message {
|
||||
private Boolean isRead;
|
||||
private String attachmentUrl;
|
||||
private String attachmentName;
|
||||
private String attachmentType;
|
||||
private String attachmentMimeType;
|
||||
private Long attachmentSizeBytes;
|
||||
|
||||
public Message() {}
|
||||
|
||||
@@ -43,6 +44,9 @@ public class Message {
|
||||
public String getAttachmentName() { return attachmentName; }
|
||||
public void setAttachmentName(String attachmentName) { this.attachmentName = attachmentName; }
|
||||
|
||||
public String getAttachmentType() { return attachmentType; }
|
||||
public void setAttachmentType(String attachmentType) { this.attachmentType = attachmentType; }
|
||||
public String getAttachmentMimeType() { return attachmentMimeType; }
|
||||
public void setAttachmentMimeType(String attachmentMimeType) { this.attachmentMimeType = attachmentMimeType; }
|
||||
|
||||
public Long getAttachmentSizeBytes() { return attachmentSizeBytes; }
|
||||
public void setAttachmentSizeBytes(Long attachmentSizeBytes) { this.attachmentSizeBytes = attachmentSizeBytes; }
|
||||
}
|
||||
@@ -17,6 +17,10 @@ import java.util.List;
|
||||
import javax.inject.Inject;
|
||||
import javax.inject.Singleton;
|
||||
|
||||
import okhttp3.MultipartBody;
|
||||
import okhttp3.RequestBody;
|
||||
import okhttp3.ResponseBody;
|
||||
|
||||
/**
|
||||
* Repository for handling chat-related data operations.
|
||||
*/
|
||||
@@ -55,6 +59,20 @@ public class ChatRepository extends BaseRepository {
|
||||
return executeCall(messageApi.sendMessage(conversationId, request));
|
||||
}
|
||||
|
||||
/**
|
||||
* Sends a message with an attachment.
|
||||
*/
|
||||
public LiveData<Resource<MessageDTO>> sendMessageWithAttachment(Long conversationId, MultipartBody.Part content, MultipartBody.Part attachment) {
|
||||
return executeCall(messageApi.sendMessageWithAttachment(conversationId, content, attachment));
|
||||
}
|
||||
|
||||
/**
|
||||
* Downloads an attachment for a specific message.
|
||||
*/
|
||||
public LiveData<Resource<ResponseBody>> downloadAttachment(Long messageId) {
|
||||
return executeCall(messageApi.downloadAttachment(messageId));
|
||||
}
|
||||
|
||||
/**
|
||||
* Fetches a paginated list of customers.
|
||||
*/
|
||||
|
||||
@@ -4,9 +4,12 @@ import androidx.lifecycle.LiveData;
|
||||
|
||||
import com.example.petstoremobile.api.CustomerApi;
|
||||
import com.example.petstoremobile.dtos.CustomerDTO;
|
||||
import com.example.petstoremobile.dtos.DropdownDTO;
|
||||
import com.example.petstoremobile.dtos.PageResponse;
|
||||
import com.example.petstoremobile.utils.Resource;
|
||||
|
||||
import java.util.List;
|
||||
|
||||
import javax.inject.Inject;
|
||||
import javax.inject.Singleton;
|
||||
|
||||
@@ -33,4 +36,11 @@ public class CustomerRepository extends BaseRepository {
|
||||
public LiveData<Resource<CustomerDTO>> getCustomerById(Long id) {
|
||||
return executeCall(customerApi.getCustomerById(id));
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Retrieves a list of customer dropdowns from the API.
|
||||
*/
|
||||
public LiveData<Resource<List<DropdownDTO>>> getCustomerDropdowns() {
|
||||
return executeCall(customerApi.getCustomerDropdowns());
|
||||
}
|
||||
}
|
||||
@@ -4,10 +4,13 @@ import androidx.lifecycle.LiveData;
|
||||
|
||||
import com.example.petstoremobile.api.PetApi;
|
||||
import com.example.petstoremobile.dtos.BulkDeleteRequest;
|
||||
import com.example.petstoremobile.dtos.DropdownDTO;
|
||||
import com.example.petstoremobile.dtos.PageResponse;
|
||||
import com.example.petstoremobile.dtos.PetDTO;
|
||||
import com.example.petstoremobile.utils.Resource;
|
||||
|
||||
import java.util.List;
|
||||
|
||||
import javax.inject.Inject;
|
||||
import javax.inject.Singleton;
|
||||
|
||||
@@ -26,8 +29,22 @@ public class PetRepository extends BaseRepository {
|
||||
/**
|
||||
* Retrieves a paginated list of pets from the API with optional filters.
|
||||
*/
|
||||
public LiveData<Resource<PageResponse<PetDTO>>> getAllPets(int page, int size, String query, String status, String species, Long storeId, String sort) {
|
||||
return executeCall(petApi.getAllPets(page, size, query, status, species, storeId, sort));
|
||||
public LiveData<Resource<PageResponse<PetDTO>>> getAllPets(int page, int size, String query, String status, String species, Long storeId, Long customerId, String sort) {
|
||||
return executeCall(petApi.getAllPets(page, size, query, status, species, storeId, customerId, sort));
|
||||
}
|
||||
|
||||
/**
|
||||
* Retrieves a list of pets for a specific customer from the dropdowns API.
|
||||
*/
|
||||
public LiveData<Resource<List<DropdownDTO>>> getCustomerPets(Long customerId) {
|
||||
return executeCall(petApi.getCustomerPets(customerId));
|
||||
}
|
||||
|
||||
/**
|
||||
* Retrieves a list of pets available for adoption from the dropdowns API.
|
||||
*/
|
||||
public LiveData<Resource<List<DropdownDTO>>> getAdoptionPets() {
|
||||
return executeCall(petApi.getAdoptionPets());
|
||||
}
|
||||
|
||||
/**
|
||||
|
||||
@@ -3,10 +3,13 @@ package com.example.petstoremobile.repositories;
|
||||
import androidx.lifecycle.LiveData;
|
||||
|
||||
import com.example.petstoremobile.api.StoreApi;
|
||||
import com.example.petstoremobile.dtos.DropdownDTO;
|
||||
import com.example.petstoremobile.dtos.PageResponse;
|
||||
import com.example.petstoremobile.dtos.StoreDTO;
|
||||
import com.example.petstoremobile.utils.Resource;
|
||||
|
||||
import java.util.List;
|
||||
|
||||
import javax.inject.Inject;
|
||||
import javax.inject.Singleton;
|
||||
|
||||
@@ -26,4 +29,18 @@ public class StoreRepository extends BaseRepository {
|
||||
public LiveData<Resource<PageResponse<StoreDTO>>> getAllStores(int page, int size) {
|
||||
return executeCall(storeApi.getAllStores(page, size));
|
||||
}
|
||||
|
||||
/**
|
||||
* Retrieves a list of store dropdowns from the API.
|
||||
*/
|
||||
public LiveData<Resource<List<DropdownDTO>>> getStoreDropdowns() {
|
||||
return executeCall(storeApi.getStoreDropdowns());
|
||||
}
|
||||
|
||||
/**
|
||||
* Retrieves a list of employees for a specific store from the dropdowns API.
|
||||
*/
|
||||
public LiveData<Resource<List<DropdownDTO>>> getStoreEmployees(Long storeId) {
|
||||
return executeCall(storeApi.getStoreEmployees(storeId));
|
||||
}
|
||||
}
|
||||
|
||||
@@ -0,0 +1,113 @@
|
||||
package com.example.petstoremobile.utils;
|
||||
|
||||
import android.util.Log;
|
||||
|
||||
import java.util.Calendar;
|
||||
import java.util.Locale;
|
||||
|
||||
/**
|
||||
* Utility class for date and time operations.
|
||||
*/
|
||||
public class DateTimeUtils {
|
||||
|
||||
private static final String TAG = "DateTimeUtils";
|
||||
|
||||
/**
|
||||
* Formats status from backend format to UI format
|
||||
* (backend is using all caps so we lower case them with this function)
|
||||
*/
|
||||
public static String formatStatusFromBackend(String status) {
|
||||
if (status == null || status.isEmpty()) return status;
|
||||
return status.substring(0, 1).toUpperCase() + status.substring(1).toLowerCase();
|
||||
}
|
||||
|
||||
/**
|
||||
* Converts a date and time string to a Calendar object.
|
||||
* format: date = "YYYY-MM-DD", time = "HH:MM"
|
||||
*/
|
||||
public static Calendar parseDateTimeToCalendar(String date, String time) throws Exception {
|
||||
Calendar calendar = Calendar.getInstance();
|
||||
String[] dateParts = date.split("-");
|
||||
String[] timeParts = time.split(":");
|
||||
|
||||
calendar.set(
|
||||
Integer.parseInt(dateParts[0]),
|
||||
Integer.parseInt(dateParts[1]) - 1,
|
||||
Integer.parseInt(dateParts[2]),
|
||||
Integer.parseInt(timeParts[0]),
|
||||
Integer.parseInt(timeParts[1]),
|
||||
0
|
||||
);
|
||||
return calendar;
|
||||
}
|
||||
|
||||
/**
|
||||
* Checks if a given date is in the past.
|
||||
* format: date = "YYYY-MM-DD"
|
||||
*/
|
||||
public static boolean isDateInPast(String date) {
|
||||
if (date == null || date.isEmpty()) return false;
|
||||
try {
|
||||
Calendar selected = Calendar.getInstance();
|
||||
String[] dateParts = date.split("-");
|
||||
selected.set(
|
||||
Integer.parseInt(dateParts[0]),
|
||||
Integer.parseInt(dateParts[1]) - 1,
|
||||
Integer.parseInt(dateParts[2]),
|
||||
0, 0, 0
|
||||
);
|
||||
return selected.before(Calendar.getInstance());
|
||||
} catch (Exception e) {
|
||||
Log.e(TAG, "Error parsing date: " + e.getMessage());
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Checks if a given date and time are in the past.
|
||||
* format: date = "YYYY-MM-DD", time = "HH:MM"
|
||||
*/
|
||||
public static boolean isDateTimeInPast(String date, String time) {
|
||||
if (date == null || date.isEmpty() || time == null || time.isEmpty()) return false;
|
||||
try {
|
||||
Calendar selected = parseDateTimeToCalendar(date, time);
|
||||
return selected.before(Calendar.getInstance());
|
||||
} catch (Exception e) {
|
||||
Log.e(TAG, "Error parsing date/time: " + e.getMessage());
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Parses a time string and returns hour and minute values.
|
||||
*/
|
||||
public static int[] parseTimeString(String time) {
|
||||
if (time == null || time.isEmpty()) return null;
|
||||
if (time.length() > 5) time = time.substring(0, 5);
|
||||
|
||||
String[] parts = time.split(":");
|
||||
if (parts.length != 2) return null;
|
||||
|
||||
try {
|
||||
int hour = Integer.parseInt(parts[0]);
|
||||
int min = Integer.parseInt(parts[1]);
|
||||
return new int[]{hour, min};
|
||||
} catch (NumberFormatException e) {
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Formats an hour and minute into an HH:mm string.
|
||||
*/
|
||||
public static String formatTime(int hour, int minute) {
|
||||
return String.format(Locale.getDefault(), "%02d:%02d", hour, minute);
|
||||
}
|
||||
|
||||
/**
|
||||
* Formats an ID for display (e.g., "ID: 123").
|
||||
*/
|
||||
public static String formatId(long id) {
|
||||
return String.format(Locale.getDefault(), "ID: %d", id);
|
||||
}
|
||||
}
|
||||
@@ -1,7 +1,9 @@
|
||||
package com.example.petstoremobile.utils;
|
||||
|
||||
import android.content.Context;
|
||||
import android.database.Cursor;
|
||||
import android.net.Uri;
|
||||
import android.provider.OpenableColumns;
|
||||
import java.io.File;
|
||||
import java.io.FileOutputStream;
|
||||
import java.io.InputStream;
|
||||
@@ -9,8 +11,11 @@ import java.io.InputStream;
|
||||
public class FileUtils {
|
||||
public static File getFileFromUri(Context context, Uri uri) {
|
||||
try {
|
||||
String fileName = getFileName(context, uri);
|
||||
if (fileName == null) fileName = "upload_" + System.currentTimeMillis();
|
||||
|
||||
InputStream inputStream = context.getContentResolver().openInputStream(uri);
|
||||
File tempFile = new File(context.getCacheDir(), "upload_image_" + System.currentTimeMillis() + ".jpg");
|
||||
File tempFile = new File(context.getCacheDir(), fileName);
|
||||
FileOutputStream outputStream = new FileOutputStream(tempFile);
|
||||
byte[] buffer = new byte[1024];
|
||||
int length;
|
||||
@@ -24,4 +29,22 @@ public class FileUtils {
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
public static String getFileName(Context context, Uri uri) {
|
||||
String result = null;
|
||||
if (uri.getScheme().equals("content")) {
|
||||
try (Cursor cursor = context.getContentResolver().query(uri, null, null, null, null)) {
|
||||
if (cursor != null && cursor.moveToFirst()) {
|
||||
int index = cursor.getColumnIndex(OpenableColumns.DISPLAY_NAME);
|
||||
if (index != -1) result = cursor.getString(index);
|
||||
}
|
||||
}
|
||||
}
|
||||
if (result == null) {
|
||||
result = uri.getPath();
|
||||
int cut = result.lastIndexOf('/');
|
||||
if (cut != -1) result = result.substring(cut + 1);
|
||||
}
|
||||
return result;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -10,8 +10,10 @@ import com.example.petstoremobile.adapters.BlackTextArrayAdapter;
|
||||
import com.example.petstoremobile.adapters.WhiteTextArrayAdapter;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.Arrays;
|
||||
import java.util.List;
|
||||
import java.util.Objects;
|
||||
import java.util.function.Consumer;
|
||||
import java.util.function.Function;
|
||||
|
||||
/**
|
||||
@@ -50,6 +52,12 @@ public class SpinnerUtils {
|
||||
names.add(nameExtractor.apply(item));
|
||||
}
|
||||
|
||||
// Only update adapter if contents changed to remove infinite loop when spinner is opened
|
||||
if (isAdapterDataSame(spinner, names)) {
|
||||
setSelectedId(spinner, data, defaultText, preselectedId, idExtractor);
|
||||
return;
|
||||
}
|
||||
|
||||
ArrayAdapter<String> adapter;
|
||||
if (useWhiteText) {
|
||||
adapter = new WhiteTextArrayAdapter<>(context, android.R.layout.simple_spinner_item, names);
|
||||
@@ -60,26 +68,41 @@ public class SpinnerUtils {
|
||||
adapter.setDropDownViewResource(android.R.layout.simple_spinner_dropdown_item);
|
||||
spinner.setAdapter(adapter);
|
||||
|
||||
setSelectedId(spinner, data, defaultText, preselectedId, idExtractor);
|
||||
}
|
||||
|
||||
private static <T> void setSelectedId(Spinner spinner, List<T> data, String defaultText, Long preselectedId, Function<T, Long> idExtractor) {
|
||||
if (preselectedId != null && preselectedId != -1) {
|
||||
int offset = (defaultText != null) ? 1 : 0;
|
||||
for (int i = 0; i < data.size(); i++) {
|
||||
Long currentId = idExtractor.apply(data.get(i));
|
||||
if (Objects.equals(currentId, preselectedId)) {
|
||||
spinner.setSelection(i + offset);
|
||||
if (spinner.getSelectedItemPosition() != i + offset) {
|
||||
spinner.setSelection(i + offset);
|
||||
}
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Checks if the adapter data is the same as the new data.
|
||||
*/
|
||||
private static boolean isAdapterDataSame(Spinner spinner, List<String> newNames) {
|
||||
if (spinner.getAdapter() == null) return false;
|
||||
if (spinner.getAdapter().getCount() != newNames.size()) return false;
|
||||
for (int i = 0; i < newNames.size(); i++) {
|
||||
if (!Objects.equals(spinner.getAdapter().getItem(i), newNames.get(i))) return false;
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
/**
|
||||
* Sets up a simple string spinner for filtering with a callback.
|
||||
*/
|
||||
public static void setupStringFilterSpinner(Context context, Spinner spinner, String[] items, Runnable onSelectionChanged) {
|
||||
WhiteTextArrayAdapter<String> adapter = new WhiteTextArrayAdapter<>(context,
|
||||
android.R.layout.simple_spinner_item, items);
|
||||
adapter.setDropDownViewResource(android.R.layout.simple_spinner_dropdown_item);
|
||||
spinner.setAdapter(adapter);
|
||||
updateStringSpinnerIfChanged(context, spinner, items, true);
|
||||
setupFilterSpinner(spinner, onSelectionChanged);
|
||||
}
|
||||
|
||||
@@ -96,6 +119,13 @@ public class SpinnerUtils {
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* Sets a listener that provides the selected index to a consumer.
|
||||
*/
|
||||
public static void setOnIndexSelectedListener(Spinner spinner, Consumer<Integer> callback) {
|
||||
spinner.setOnItemSelectedListener(new OnIndexSelected(callback));
|
||||
}
|
||||
|
||||
/**
|
||||
* Sets the selection of a spinner based on a string value.
|
||||
*/
|
||||
@@ -103,18 +133,65 @@ public class SpinnerUtils {
|
||||
if (value == null || spinner.getAdapter() == null) return;
|
||||
ArrayAdapter<String> adapter = (ArrayAdapter<String>) spinner.getAdapter();
|
||||
int pos = adapter.getPosition(value);
|
||||
if (pos >= 0) {
|
||||
if (pos >= 0 && spinner.getSelectedItemPosition() != pos) {
|
||||
spinner.setSelection(pos);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Sets the selection of a spinner based on a value within an array.
|
||||
*/
|
||||
public static <T> void setSelectionByValueArray(Spinner spinner, T[] array, T value) {
|
||||
if (spinner == null || array == null || value == null) return;
|
||||
for (int i = 0; i < array.length; i++) {
|
||||
if (Objects.equals(array[i], value)) {
|
||||
if (spinner.getSelectedItemPosition() != i) {
|
||||
spinner.setSelection(i);
|
||||
}
|
||||
return;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Configures a simple string array spinner.
|
||||
*/
|
||||
public static void setupStringSpinner(Context context, Spinner spinner, String[] items) {
|
||||
BlackTextArrayAdapter<String> adapter = new BlackTextArrayAdapter<>(context,
|
||||
android.R.layout.simple_spinner_item, items);
|
||||
updateStringSpinnerIfChanged(context, spinner, items, false);
|
||||
}
|
||||
|
||||
/**
|
||||
* Updates a string spinner only if the items have changed.
|
||||
*/
|
||||
public static void updateStringSpinnerIfChanged(Context context, Spinner spinner, String[] items, boolean useWhiteText) {
|
||||
if (isAdapterDataSame(spinner, Arrays.asList(items))) return;
|
||||
|
||||
ArrayAdapter<String> adapter;
|
||||
if (useWhiteText) {
|
||||
adapter = new WhiteTextArrayAdapter<>(context, android.R.layout.simple_spinner_item, items);
|
||||
} else {
|
||||
adapter = new BlackTextArrayAdapter<>(context, android.R.layout.simple_spinner_item, items);
|
||||
}
|
||||
adapter.setDropDownViewResource(android.R.layout.simple_spinner_dropdown_item);
|
||||
spinner.setAdapter(adapter);
|
||||
}
|
||||
|
||||
/**
|
||||
* Helper listener to get selected index from a spinner.
|
||||
*/
|
||||
public static class OnIndexSelected implements AdapterView.OnItemSelectedListener {
|
||||
private final Consumer<Integer> callback;
|
||||
|
||||
public OnIndexSelected(Consumer<Integer> callback) {
|
||||
this.callback = callback;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onItemSelected(AdapterView<?> parent, View view, int position, long id) {
|
||||
callback.accept(position);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onNothingSelected(AdapterView<?> parent) {}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,5 +1,7 @@
|
||||
package com.example.petstoremobile.utils;
|
||||
|
||||
import android.app.DatePickerDialog;
|
||||
import android.content.Context;
|
||||
import android.telephony.PhoneNumberFormattingTextWatcher;
|
||||
import android.text.Editable;
|
||||
import android.text.InputFilter;
|
||||
@@ -8,10 +10,14 @@ import android.view.View;
|
||||
import android.widget.EditText;
|
||||
import android.widget.ImageButton;
|
||||
import android.widget.Spinner;
|
||||
import android.widget.Toast;
|
||||
|
||||
import androidx.fragment.app.Fragment;
|
||||
import com.example.petstoremobile.fragments.ListFragment;
|
||||
|
||||
import java.util.Calendar;
|
||||
import java.util.Locale;
|
||||
|
||||
/**
|
||||
* Utility class for shared UI component logic and formatting.
|
||||
*/
|
||||
@@ -25,24 +31,16 @@ public class UIUtils {
|
||||
}
|
||||
|
||||
/**
|
||||
* Sets up a toggle for a filter layout, including icon changes and field resets.
|
||||
* Sets up a toggle for a filter layout, including icon changes.
|
||||
*/
|
||||
public static void setupFilterToggle(ImageButton btnToggle, View layoutFilter, EditText etSearch, Spinner... spinners) {
|
||||
btnToggle.setOnClickListener(v -> {
|
||||
boolean isVisible = layoutFilter.getVisibility() == View.VISIBLE;
|
||||
layoutFilter.setVisibility(isVisible ? View.GONE : View.VISIBLE);
|
||||
|
||||
// Use Android default icons or app-specific ones if available
|
||||
btnToggle.setImageResource(isVisible ?
|
||||
android.R.drawable.ic_menu_search :
|
||||
android.R.drawable.ic_menu_close_clear_cancel);
|
||||
|
||||
if (isVisible) {
|
||||
if (etSearch != null) etSearch.setText("");
|
||||
for (Spinner spinner : spinners) {
|
||||
if (spinner != null) spinner.setSelection(0);
|
||||
}
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
@@ -73,4 +71,80 @@ public class UIUtils {
|
||||
@Override public void afterTextChanged(Editable s) {}
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* Sets the enabled state and alpha for multiple views, only if changed.
|
||||
*/
|
||||
public static void setViewsEnabled(boolean enabled, View... views) {
|
||||
for (View v : views) {
|
||||
if (v != null) {
|
||||
if (v.isEnabled() != enabled) {
|
||||
v.setEnabled(enabled);
|
||||
}
|
||||
float targetAlpha = enabled ? 1.0f : 0.5f;
|
||||
if (Math.abs(v.getAlpha() - targetAlpha) > 0.01f) {
|
||||
v.setAlpha(targetAlpha);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Sets enabled state for a field and updates alpha for both the field and its label, only if changed.
|
||||
*/
|
||||
public static void setFieldEnabled(boolean enabled, View field, View label) {
|
||||
if (field != null) {
|
||||
if (field.isEnabled() != enabled) {
|
||||
field.setEnabled(enabled);
|
||||
}
|
||||
float targetAlpha = enabled ? 1.0f : 0.5f;
|
||||
if (Math.abs(field.getAlpha() - targetAlpha) > 0.01f) {
|
||||
field.setAlpha(targetAlpha);
|
||||
}
|
||||
}
|
||||
if (label != null) {
|
||||
float targetAlpha = enabled ? 1.0f : 0.5f;
|
||||
if (Math.abs(label.getAlpha() - targetAlpha) > 0.01f) {
|
||||
label.setAlpha(targetAlpha);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Sets the alpha for multiple views, only if changed.
|
||||
*/
|
||||
public static void setViewsAlpha(float alpha, View... views) {
|
||||
for (View v : views) {
|
||||
if (v != null && Math.abs(v.getAlpha() - alpha) > 0.01f) {
|
||||
v.setAlpha(alpha);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Displays a DatePickerDialog and sets the result to an EditText.
|
||||
*/
|
||||
public static void showDatePicker(Context context, EditText editText, Runnable onDateSet) {
|
||||
Calendar c = Calendar.getInstance();
|
||||
DatePickerDialog d = new DatePickerDialog(context,
|
||||
(dp, y, m, d1) -> {
|
||||
String selectedDate = String.format(Locale.getDefault(), "%04d-%02d-%02d", y, m + 1, d1);
|
||||
editText.setText(selectedDate);
|
||||
if (onDateSet != null) onDateSet.run();
|
||||
},
|
||||
c.get(Calendar.YEAR), c.get(Calendar.MONTH),
|
||||
c.get(Calendar.DAY_OF_MONTH));
|
||||
d.getDatePicker().setMinDate(System.currentTimeMillis() - 1000);
|
||||
d.show();
|
||||
}
|
||||
|
||||
/**
|
||||
* Displays a toast and returns false. Useful for validation chains.
|
||||
*/
|
||||
public static boolean showToast(Context context, String msg) {
|
||||
if (context != null) {
|
||||
Toast.makeText(context, msg, Toast.LENGTH_SHORT).show();
|
||||
}
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -0,0 +1,102 @@
|
||||
package com.example.petstoremobile.viewmodels;
|
||||
|
||||
import androidx.lifecycle.LiveData;
|
||||
import androidx.lifecycle.MutableLiveData;
|
||||
import androidx.lifecycle.ViewModel;
|
||||
|
||||
import com.example.petstoremobile.dtos.AdoptionDTO;
|
||||
import com.example.petstoremobile.dtos.DropdownDTO;
|
||||
import com.example.petstoremobile.repositories.AdoptionRepository;
|
||||
import com.example.petstoremobile.repositories.CustomerRepository;
|
||||
import com.example.petstoremobile.repositories.PetRepository;
|
||||
import com.example.petstoremobile.repositories.StoreRepository;
|
||||
import com.example.petstoremobile.utils.Resource;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.List;
|
||||
|
||||
import javax.inject.Inject;
|
||||
|
||||
import dagger.hilt.android.lifecycle.HiltViewModel;
|
||||
|
||||
@HiltViewModel
|
||||
public class AdoptionDetailViewModel extends ViewModel {
|
||||
private final AdoptionRepository adoptionRepository;
|
||||
private final PetRepository petRepository;
|
||||
private final CustomerRepository customerRepository;
|
||||
private final StoreRepository storeRepository;
|
||||
|
||||
private long adoptionId = -1;
|
||||
private boolean isEditing = false;
|
||||
|
||||
private final MutableLiveData<List<DropdownDTO>> petList = new MutableLiveData<>(new ArrayList<>());
|
||||
private final MutableLiveData<List<DropdownDTO>> customerList = new MutableLiveData<>(new ArrayList<>());
|
||||
private final MutableLiveData<List<DropdownDTO>> storeList = new MutableLiveData<>(new ArrayList<>());
|
||||
private final MutableLiveData<List<DropdownDTO>> employeeList = new MutableLiveData<>(new ArrayList<>());
|
||||
|
||||
@Inject
|
||||
public AdoptionDetailViewModel(AdoptionRepository adoptionRepository, PetRepository petRepository,
|
||||
CustomerRepository customerRepository, StoreRepository storeRepository) {
|
||||
this.adoptionRepository = adoptionRepository;
|
||||
this.petRepository = petRepository;
|
||||
this.customerRepository = customerRepository;
|
||||
this.storeRepository = storeRepository;
|
||||
}
|
||||
|
||||
public void setAdoptionId(long id) {
|
||||
this.adoptionId = id;
|
||||
this.isEditing = id != -1;
|
||||
}
|
||||
|
||||
public long getAdoptionId() {
|
||||
return adoptionId;
|
||||
}
|
||||
|
||||
public boolean isEditing() {
|
||||
return isEditing;
|
||||
}
|
||||
|
||||
public LiveData<Resource<AdoptionDTO>> loadAdoption() {
|
||||
return adoptionRepository.getAdoptionById(adoptionId);
|
||||
}
|
||||
|
||||
public LiveData<Resource<List<DropdownDTO>>> loadPets() {
|
||||
return petRepository.getAdoptionPets();
|
||||
}
|
||||
|
||||
public LiveData<Resource<List<DropdownDTO>>> loadCustomers() {
|
||||
return customerRepository.getCustomerDropdowns();
|
||||
}
|
||||
|
||||
public LiveData<Resource<List<DropdownDTO>>> loadStores() {
|
||||
return storeRepository.getStoreDropdowns();
|
||||
}
|
||||
|
||||
public LiveData<Resource<List<DropdownDTO>>> loadEmployees(Long storeId) {
|
||||
return storeRepository.getStoreEmployees(storeId);
|
||||
}
|
||||
|
||||
public LiveData<Resource<AdoptionDTO>> saveAdoption(AdoptionDTO dto) {
|
||||
if (isEditing) {
|
||||
return adoptionRepository.updateAdoption(adoptionId, dto);
|
||||
} else {
|
||||
return adoptionRepository.createAdoption(dto);
|
||||
}
|
||||
}
|
||||
|
||||
public LiveData<Resource<Void>> deleteAdoption() {
|
||||
return adoptionRepository.deleteAdoption(adoptionId);
|
||||
}
|
||||
|
||||
public void setPetList(List<DropdownDTO> list) { petList.setValue(list); }
|
||||
public LiveData<List<DropdownDTO>> getPetList() { return petList; }
|
||||
|
||||
public void setCustomerList(List<DropdownDTO> list) { customerList.setValue(list); }
|
||||
public LiveData<List<DropdownDTO>> getCustomerList() { return customerList; }
|
||||
|
||||
public void setStoreList(List<DropdownDTO> list) { storeList.setValue(list); }
|
||||
public LiveData<List<DropdownDTO>> getStoreList() { return storeList; }
|
||||
|
||||
public void setEmployeeList(List<DropdownDTO> list) { employeeList.setValue(list); }
|
||||
public LiveData<List<DropdownDTO>> getEmployeeList() { return employeeList; }
|
||||
}
|
||||
@@ -0,0 +1,83 @@
|
||||
package com.example.petstoremobile.viewmodels;
|
||||
|
||||
import androidx.lifecycle.LiveData;
|
||||
import androidx.lifecycle.MutableLiveData;
|
||||
import androidx.lifecycle.ViewModel;
|
||||
|
||||
import com.example.petstoremobile.dtos.AdoptionDTO;
|
||||
import com.example.petstoremobile.dtos.BulkDeleteRequest;
|
||||
import com.example.petstoremobile.dtos.StoreDTO;
|
||||
import com.example.petstoremobile.repositories.AdoptionRepository;
|
||||
import com.example.petstoremobile.repositories.StoreRepository;
|
||||
import com.example.petstoremobile.utils.Resource;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.List;
|
||||
|
||||
import javax.inject.Inject;
|
||||
|
||||
import dagger.hilt.android.lifecycle.HiltViewModel;
|
||||
|
||||
@HiltViewModel
|
||||
public class AdoptionListViewModel extends ViewModel {
|
||||
private final AdoptionRepository adoptionRepository;
|
||||
private final StoreRepository storeRepository;
|
||||
|
||||
private final MutableLiveData<List<AdoptionDTO>> adoptions = new MutableLiveData<>(new ArrayList<>());
|
||||
private final MutableLiveData<List<StoreDTO>> stores = new MutableLiveData<>(new ArrayList<>());
|
||||
private final MutableLiveData<Boolean> isLoading = new MutableLiveData<>(false);
|
||||
|
||||
private int currentPage = 0;
|
||||
private boolean isLastPage = false;
|
||||
private static final int PAGE_SIZE = 20;
|
||||
|
||||
@Inject
|
||||
public AdoptionListViewModel(AdoptionRepository adoptionRepository, StoreRepository storeRepository) {
|
||||
this.adoptionRepository = adoptionRepository;
|
||||
this.storeRepository = storeRepository;
|
||||
}
|
||||
|
||||
public LiveData<List<AdoptionDTO>> getAdoptions() { return adoptions; }
|
||||
public LiveData<List<StoreDTO>> getStores() { return stores; }
|
||||
public LiveData<Boolean> getIsLoading() { return isLoading; }
|
||||
public boolean isLastPage() { return isLastPage; }
|
||||
|
||||
public void loadAdoptions(boolean reset, String query, String status, Long storeId, String date, Long employeeId) {
|
||||
if (isLoading.getValue() != null && isLoading.getValue() && !reset) return;
|
||||
|
||||
if (reset) {
|
||||
currentPage = 0;
|
||||
isLastPage = false;
|
||||
}
|
||||
|
||||
if ("All Statuses".equals(status)) status = null;
|
||||
|
||||
isLoading.setValue(true);
|
||||
adoptionRepository.getAllAdoptions(currentPage, PAGE_SIZE, query, status, storeId, date, employeeId).observeForever(resource -> {
|
||||
if (resource != null) {
|
||||
if (resource.status == Resource.Status.SUCCESS && resource.data != null) {
|
||||
List<AdoptionDTO> currentList = reset ? new ArrayList<>() : new ArrayList<>(adoptions.getValue());
|
||||
currentList.addAll(resource.data.getContent());
|
||||
adoptions.setValue(currentList);
|
||||
isLastPage = resource.data.isLast();
|
||||
if (!isLastPage) currentPage++;
|
||||
isLoading.setValue(false);
|
||||
} else if (resource.status == Resource.Status.ERROR) {
|
||||
isLoading.setValue(false);
|
||||
}
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
public void loadStores() {
|
||||
storeRepository.getAllStores(0, 100).observeForever(resource -> {
|
||||
if (resource != null && resource.status == Resource.Status.SUCCESS && resource.data != null) {
|
||||
stores.setValue(resource.data.getContent());
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
public LiveData<Resource<Void>> bulkDeleteAdoptions(List<String> ids) {
|
||||
return adoptionRepository.bulkDeleteAdoptions(new BulkDeleteRequest(ids));
|
||||
}
|
||||
}
|
||||
@@ -1,68 +0,0 @@
|
||||
package com.example.petstoremobile.viewmodels;
|
||||
|
||||
import androidx.lifecycle.LiveData;
|
||||
import androidx.lifecycle.ViewModel;
|
||||
|
||||
import com.example.petstoremobile.dtos.AdoptionDTO;
|
||||
import com.example.petstoremobile.dtos.BulkDeleteRequest;
|
||||
import com.example.petstoremobile.dtos.PageResponse;
|
||||
import com.example.petstoremobile.repositories.AdoptionRepository;
|
||||
import com.example.petstoremobile.utils.Resource;
|
||||
|
||||
import java.util.List;
|
||||
|
||||
import javax.inject.Inject;
|
||||
|
||||
import dagger.hilt.android.lifecycle.HiltViewModel;
|
||||
|
||||
@HiltViewModel
|
||||
public class AdoptionViewModel extends ViewModel {
|
||||
private final AdoptionRepository repository;
|
||||
|
||||
@Inject
|
||||
public AdoptionViewModel(AdoptionRepository repository) {
|
||||
this.repository = repository;
|
||||
}
|
||||
|
||||
/**
|
||||
* Fetches a paginated list of all adoptions with filters.
|
||||
*/
|
||||
public LiveData<Resource<PageResponse<AdoptionDTO>>> getAllAdoptions(int page, int size, String query, String status, Long storeId, String date, Long employeeId) {
|
||||
return repository.getAllAdoptions(page, size, query, status, storeId, date, employeeId);
|
||||
}
|
||||
|
||||
/**
|
||||
* Retrieves a single adoption by its ID.
|
||||
*/
|
||||
public LiveData<Resource<AdoptionDTO>> getAdoptionById(Long id) {
|
||||
return repository.getAdoptionById(id);
|
||||
}
|
||||
|
||||
/**
|
||||
* Creates a new adoption record.
|
||||
*/
|
||||
public LiveData<Resource<AdoptionDTO>> createAdoption(AdoptionDTO adoption) {
|
||||
return repository.createAdoption(adoption);
|
||||
}
|
||||
|
||||
/**
|
||||
* Updates an existing adoption record by ID.
|
||||
*/
|
||||
public LiveData<Resource<AdoptionDTO>> updateAdoption(Long id, AdoptionDTO adoption) {
|
||||
return repository.updateAdoption(id, adoption);
|
||||
}
|
||||
|
||||
/**
|
||||
* Deletes an adoption record by ID.
|
||||
*/
|
||||
public LiveData<Resource<Void>> deleteAdoption(Long id) {
|
||||
return repository.deleteAdoption(id);
|
||||
}
|
||||
|
||||
/**
|
||||
* Deletes multiple adoption records.
|
||||
*/
|
||||
public LiveData<Resource<Void>> bulkDeleteAdoptions(List<String> ids) {
|
||||
return repository.bulkDeleteAdoptions(new BulkDeleteRequest(ids));
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,163 @@
|
||||
package com.example.petstoremobile.viewmodels;
|
||||
|
||||
import androidx.lifecycle.LiveData;
|
||||
import androidx.lifecycle.MutableLiveData;
|
||||
import androidx.lifecycle.ViewModel;
|
||||
|
||||
import com.example.petstoremobile.dtos.SaleDTO;
|
||||
import com.example.petstoremobile.repositories.SaleRepository;
|
||||
import com.example.petstoremobile.utils.Resource;
|
||||
|
||||
import java.math.BigDecimal;
|
||||
import java.math.RoundingMode;
|
||||
import java.util.ArrayList;
|
||||
import java.util.Calendar;
|
||||
import java.util.Collections;
|
||||
import java.util.HashMap;
|
||||
import java.util.LinkedHashMap;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
import java.util.TreeMap;
|
||||
|
||||
import javax.inject.Inject;
|
||||
|
||||
import dagger.hilt.android.lifecycle.HiltViewModel;
|
||||
|
||||
@HiltViewModel
|
||||
public class AnalyticsViewModel extends ViewModel {
|
||||
private final SaleRepository saleRepository;
|
||||
|
||||
private final MutableLiveData<AnalyticsData> analyticsData = new MutableLiveData<>();
|
||||
private final MutableLiveData<Boolean> isLoading = new MutableLiveData<>(false);
|
||||
private final MutableLiveData<String> errorMessage = new MutableLiveData<>();
|
||||
|
||||
@Inject
|
||||
public AnalyticsViewModel(SaleRepository saleRepository) {
|
||||
this.saleRepository = saleRepository;
|
||||
}
|
||||
|
||||
public LiveData<AnalyticsData> getAnalyticsData() { return analyticsData; }
|
||||
public LiveData<Boolean> getIsLoading() { return isLoading; }
|
||||
public LiveData<String> getErrorMessage() { return errorMessage; }
|
||||
|
||||
public void loadAnalytics() {
|
||||
isLoading.setValue(true);
|
||||
errorMessage.setValue(null);
|
||||
saleRepository.getAllSales(0, 1000, null, null, null, "saleDate,desc").observeForever(resource -> {
|
||||
if (resource != null) {
|
||||
if (resource.status == Resource.Status.SUCCESS && resource.data != null) {
|
||||
computeAnalytics(resource.data.getContent());
|
||||
isLoading.setValue(false);
|
||||
} else if (resource.status == Resource.Status.ERROR) {
|
||||
errorMessage.setValue(resource.message);
|
||||
isLoading.setValue(false);
|
||||
}
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
private void computeAnalytics(List<SaleDTO> sales) {
|
||||
List<SaleDTO> regularSales = new ArrayList<>();
|
||||
for (SaleDTO s : sales) {
|
||||
if (!Boolean.TRUE.equals(s.getIsRefund()))
|
||||
regularSales.add(s);
|
||||
}
|
||||
|
||||
AnalyticsData data = new AnalyticsData();
|
||||
|
||||
// Summary
|
||||
BigDecimal totalRevenue = BigDecimal.ZERO;
|
||||
int totalItems = 0;
|
||||
for (SaleDTO s : regularSales) {
|
||||
if (s.getTotalAmount() != null) totalRevenue = totalRevenue.add(s.getTotalAmount());
|
||||
if (s.getItems() != null) {
|
||||
for (SaleDTO.SaleItemDTO item : s.getItems()) {
|
||||
if (item.getQuantity() != null) totalItems += Math.abs(item.getQuantity());
|
||||
}
|
||||
}
|
||||
}
|
||||
data.totalRevenue = totalRevenue;
|
||||
data.totalTransactions = regularSales.size();
|
||||
data.avgTransaction = data.totalTransactions > 0
|
||||
? totalRevenue.divide(BigDecimal.valueOf(data.totalTransactions), 2, RoundingMode.HALF_UP)
|
||||
: BigDecimal.ZERO;
|
||||
data.totalItems = totalItems;
|
||||
|
||||
// Product Maps
|
||||
Map<String, BigDecimal> revenueByProduct = new LinkedHashMap<>();
|
||||
Map<String, Integer> quantityByProduct = new LinkedHashMap<>();
|
||||
Map<String, Integer> paymentCount = new LinkedHashMap<>();
|
||||
Map<String, BigDecimal> employeeRevenue = new LinkedHashMap<>();
|
||||
|
||||
for (SaleDTO s : regularSales) {
|
||||
// Payments
|
||||
String method = s.getPaymentMethod() != null ? s.getPaymentMethod() : "Unknown";
|
||||
paymentCount.merge(method, 1, Integer::sum);
|
||||
|
||||
// Employee
|
||||
String emp = s.getEmployeeName() != null ? s.getEmployeeName() : "Unknown";
|
||||
if (s.getTotalAmount() != null) employeeRevenue.merge(emp, s.getTotalAmount(), BigDecimal::add);
|
||||
|
||||
// Items
|
||||
if (s.getItems() != null) {
|
||||
for (SaleDTO.SaleItemDTO item : s.getItems()) {
|
||||
String name = item.getProductName() != null ? item.getProductName() : "Unknown";
|
||||
int qty = item.getQuantity() != null ? Math.abs(item.getQuantity()) : 0;
|
||||
BigDecimal lineTotal = item.getUnitPrice() != null
|
||||
? item.getUnitPrice().multiply(BigDecimal.valueOf(qty))
|
||||
: BigDecimal.ZERO;
|
||||
revenueByProduct.merge(name, lineTotal, BigDecimal::add);
|
||||
quantityByProduct.merge(name, qty, Integer::sum);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Sort Top Revenue
|
||||
data.topRevenueProducts = new ArrayList<>(revenueByProduct.entrySet());
|
||||
data.topRevenueProducts.sort((a, b) -> b.getValue().compareTo(a.getValue()));
|
||||
if (data.topRevenueProducts.size() > 5) data.topRevenueProducts = data.topRevenueProducts.subList(0, 5);
|
||||
|
||||
// Sort Top Quantity
|
||||
data.topQuantityProducts = new ArrayList<>(quantityByProduct.entrySet());
|
||||
data.topQuantityProducts.sort((a, b) -> b.getValue() - a.getValue());
|
||||
if (data.topQuantityProducts.size() > 5) data.topQuantityProducts = data.topQuantityProducts.subList(0, 5);
|
||||
|
||||
// Payment Stats
|
||||
data.paymentMethodStats = new ArrayList<>(paymentCount.entrySet());
|
||||
|
||||
// Employee Performance
|
||||
data.employeePerformance = new ArrayList<>(employeeRevenue.entrySet());
|
||||
data.employeePerformance.sort((a, b) -> b.getValue().compareTo(a.getValue()));
|
||||
|
||||
// Daily Revenue (last 7 days)
|
||||
Map<String, BigDecimal> dailyMap = new TreeMap<>();
|
||||
for (int i = 6; i >= 0; i--) {
|
||||
Calendar day = Calendar.getInstance();
|
||||
day.add(Calendar.DAY_OF_YEAR, -i);
|
||||
String key = String.format("%04d-%02d-%02d",
|
||||
day.get(Calendar.YEAR), day.get(Calendar.MONTH) + 1, day.get(Calendar.DAY_OF_MONTH));
|
||||
dailyMap.put(key, BigDecimal.ZERO);
|
||||
}
|
||||
for (SaleDTO s : regularSales) {
|
||||
if (s.getSaleDate() != null && s.getTotalAmount() != null) {
|
||||
String date = s.getSaleDate().length() >= 10 ? s.getSaleDate().substring(0, 10) : s.getSaleDate();
|
||||
if (dailyMap.containsKey(date)) dailyMap.merge(date, s.getTotalAmount(), BigDecimal::add);
|
||||
}
|
||||
}
|
||||
data.dailyRevenue = new ArrayList<>(dailyMap.entrySet());
|
||||
|
||||
analyticsData.setValue(data);
|
||||
}
|
||||
|
||||
public static class AnalyticsData {
|
||||
public BigDecimal totalRevenue;
|
||||
public int totalTransactions;
|
||||
public BigDecimal avgTransaction;
|
||||
public int totalItems;
|
||||
public List<Map.Entry<String, BigDecimal>> topRevenueProducts;
|
||||
public List<Map.Entry<String, Integer>> topQuantityProducts;
|
||||
public List<Map.Entry<String, Integer>> paymentMethodStats;
|
||||
public List<Map.Entry<String, BigDecimal>> employeePerformance;
|
||||
public List<Map.Entry<String, BigDecimal>> dailyRevenue;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,402 @@
|
||||
package com.example.petstoremobile.viewmodels;
|
||||
|
||||
import androidx.lifecycle.LiveData;
|
||||
import androidx.lifecycle.MutableLiveData;
|
||||
import androidx.lifecycle.ViewModel;
|
||||
|
||||
import com.example.petstoremobile.dtos.AppointmentDTO;
|
||||
import com.example.petstoremobile.dtos.DropdownDTO;
|
||||
import com.example.petstoremobile.dtos.ServiceDTO;
|
||||
import com.example.petstoremobile.repositories.AppointmentRepository;
|
||||
import com.example.petstoremobile.repositories.CustomerRepository;
|
||||
import com.example.petstoremobile.repositories.PetRepository;
|
||||
import com.example.petstoremobile.repositories.ServiceRepository;
|
||||
import com.example.petstoremobile.repositories.StoreRepository;
|
||||
import com.example.petstoremobile.utils.DateTimeUtils;
|
||||
import com.example.petstoremobile.utils.Resource;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.List;
|
||||
|
||||
import javax.inject.Inject;
|
||||
|
||||
import dagger.hilt.android.lifecycle.HiltViewModel;
|
||||
|
||||
/**
|
||||
* ViewModel for managing appointment details and form state.
|
||||
*/
|
||||
@HiltViewModel
|
||||
public class AppointmentDetailViewModel extends ViewModel {
|
||||
private final AppointmentRepository repository;
|
||||
private final CustomerRepository customerRepository;
|
||||
private final StoreRepository storeRepository;
|
||||
private final PetRepository petRepository;
|
||||
private final ServiceRepository serviceRepository;
|
||||
|
||||
private final MutableLiveData<List<DropdownDTO>> customers = new MutableLiveData<>();
|
||||
private final MutableLiveData<List<DropdownDTO>> stores = new MutableLiveData<>();
|
||||
private final MutableLiveData<List<ServiceDTO>> services = new MutableLiveData<>();
|
||||
private final MutableLiveData<List<DropdownDTO>> customerPets = new MutableLiveData<>();
|
||||
private final MutableLiveData<List<DropdownDTO>> storeEmployees = new MutableLiveData<>();
|
||||
private final MutableLiveData<ViewState> viewState = new MutableLiveData<>(new ViewState());
|
||||
|
||||
private long appointmentId = -1;
|
||||
private Long currentCustomerId;
|
||||
private Long currentStoreId;
|
||||
private Long currentPetId;
|
||||
private Long currentServiceId;
|
||||
private Long currentStaffId;
|
||||
|
||||
/**
|
||||
* Constructor for AppointmentDetailViewModel.
|
||||
*/
|
||||
@Inject
|
||||
public AppointmentDetailViewModel(
|
||||
AppointmentRepository repository,
|
||||
CustomerRepository customerRepository,
|
||||
StoreRepository storeRepository,
|
||||
PetRepository petRepository,
|
||||
ServiceRepository serviceRepository) {
|
||||
this.repository = repository;
|
||||
this.customerRepository = customerRepository;
|
||||
this.storeRepository = storeRepository;
|
||||
this.petRepository = petRepository;
|
||||
this.serviceRepository = serviceRepository;
|
||||
}
|
||||
|
||||
// Initial Data Loading
|
||||
|
||||
/**
|
||||
* Loads initial dropdown data for customers, stores, and services.
|
||||
*/
|
||||
public void loadInitialFormData() {
|
||||
customerRepository.getCustomerDropdowns().observeForever(r -> {
|
||||
if (r.status == Resource.Status.SUCCESS) customers.setValue(r.data);
|
||||
});
|
||||
storeRepository.getStoreDropdowns().observeForever(r -> {
|
||||
if (r.status == Resource.Status.SUCCESS) stores.setValue(r.data);
|
||||
});
|
||||
serviceRepository.getAllServices(0, 200, null, "serviceName").observeForever(r -> {
|
||||
if (r.status == Resource.Status.SUCCESS && r.data != null) services.setValue(r.data.getContent());
|
||||
});
|
||||
}
|
||||
|
||||
// LiveData Getters
|
||||
|
||||
/**
|
||||
* Returns the LiveData for the list of customers.
|
||||
*/
|
||||
public LiveData<List<DropdownDTO>> getCustomers() { return customers; }
|
||||
|
||||
/**
|
||||
* Returns the LiveData for the list of stores.
|
||||
*/
|
||||
public LiveData<List<DropdownDTO>> getStores() { return stores; }
|
||||
|
||||
/**
|
||||
* Returns the LiveData for the list of services.
|
||||
*/
|
||||
public LiveData<List<ServiceDTO>> getServices() { return services; }
|
||||
|
||||
/**
|
||||
* Returns the LiveData for the list of pets for the current customer.
|
||||
*/
|
||||
public LiveData<List<DropdownDTO>> getCustomerPets() { return customerPets; }
|
||||
|
||||
/**
|
||||
* Returns the LiveData for the list of employees for the current store.
|
||||
*/
|
||||
public LiveData<List<DropdownDTO>> getStoreEmployees() { return storeEmployees; }
|
||||
|
||||
/**
|
||||
* Returns the LiveData for the view state.
|
||||
*/
|
||||
public LiveData<ViewState> getViewState() { return viewState; }
|
||||
|
||||
//State Getters
|
||||
|
||||
/**
|
||||
* Returns the current appointment ID.
|
||||
*/
|
||||
public long getAppointmentId() { return appointmentId; }
|
||||
|
||||
/**
|
||||
* Sets the current appointment ID and updates the mode.
|
||||
*/
|
||||
public void setAppointmentId(long id) {
|
||||
this.appointmentId = id;
|
||||
initMode(id != -1);
|
||||
}
|
||||
|
||||
// Selection Handlers for spinners
|
||||
|
||||
/**
|
||||
* Handles customer selection and loads their pets.
|
||||
*/
|
||||
public void onCustomerSelected(int position) {
|
||||
List<DropdownDTO> list = customers.getValue();
|
||||
if (position > 0 && list != null && position <= list.size()) {
|
||||
currentCustomerId = list.get(position - 1).getId();
|
||||
loadPetsForCustomer(currentCustomerId);
|
||||
updateViewState(s -> {
|
||||
s.selectedCustomerId = currentCustomerId;
|
||||
s.isPetEnabled = !s.isEditing;
|
||||
});
|
||||
} else {
|
||||
currentCustomerId = null;
|
||||
customerPets.setValue(new ArrayList<>());
|
||||
updateViewState(s -> {
|
||||
s.selectedCustomerId = null;
|
||||
s.isPetEnabled = false;
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Handles store selection and loads its employees.
|
||||
*/
|
||||
public void onStoreSelected(int position) {
|
||||
List<DropdownDTO> list = stores.getValue();
|
||||
if (position > 0 && list != null && position <= list.size()) {
|
||||
currentStoreId = list.get(position - 1).getId();
|
||||
loadEmployeesForStore(currentStoreId);
|
||||
updateViewState(s -> {
|
||||
s.selectedStoreId = currentStoreId;
|
||||
s.isStaffEnabled = !s.isPast;
|
||||
});
|
||||
} else {
|
||||
currentStoreId = null;
|
||||
storeEmployees.setValue(new ArrayList<>());
|
||||
updateViewState(s -> {
|
||||
s.selectedStoreId = null;
|
||||
s.isStaffEnabled = false;
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Handles service selection.
|
||||
*/
|
||||
public void onServiceSelected(int position) {
|
||||
List<ServiceDTO> list = services.getValue();
|
||||
currentServiceId = (position > 0 && list != null && position <= list.size()) ? list.get(position - 1).getServiceId() : null;
|
||||
updateViewState(s -> s.selectedServiceId = currentServiceId);
|
||||
}
|
||||
|
||||
/**
|
||||
* Handles pet selection.
|
||||
*/
|
||||
public void onPetSelected(int position) {
|
||||
List<DropdownDTO> list = customerPets.getValue();
|
||||
currentPetId = (position > 0 && list != null && position <= list.size()) ? list.get(position - 1).getId() : null;
|
||||
updateViewState(s -> s.selectedPetId = currentPetId);
|
||||
}
|
||||
|
||||
/**
|
||||
* Handles staff selection.
|
||||
*/
|
||||
public void onStaffSelected(int position) {
|
||||
List<DropdownDTO> list = storeEmployees.getValue();
|
||||
currentStaffId = (position > 0 && list != null && position <= list.size()) ? list.get(position - 1).getId() : null;
|
||||
updateViewState(s -> s.selectedStaffId = currentStaffId);
|
||||
}
|
||||
|
||||
/**
|
||||
* Loads the list of pets for a specific customer.
|
||||
*/
|
||||
private void loadPetsForCustomer(Long customerId) {
|
||||
petRepository.getCustomerPets(customerId).observeForever(r -> {
|
||||
if (r.status == Resource.Status.SUCCESS) customerPets.setValue(r.data);
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* Loads the list of employees for a specific store.
|
||||
*/
|
||||
private void loadEmployeesForStore(Long storeId) {
|
||||
storeRepository.getStoreEmployees(storeId).observeForever(r -> {
|
||||
if (r.status == Resource.Status.SUCCESS) storeEmployees.setValue(r.data);
|
||||
});
|
||||
}
|
||||
|
||||
// Appointment Detail CRUD
|
||||
|
||||
/**
|
||||
* Fetches appointment details and populates internal state.
|
||||
*/
|
||||
public LiveData<Resource<AppointmentDTO>> loadAppointment() {
|
||||
MutableLiveData<Resource<AppointmentDTO>> result = new MutableLiveData<>();
|
||||
repository.getAppointmentById(appointmentId).observeForever(resource -> {
|
||||
if (resource.status == Resource.Status.SUCCESS && resource.data != null) {
|
||||
AppointmentDTO a = resource.data;
|
||||
currentCustomerId = a.getCustomerId();
|
||||
currentStoreId = a.getStoreId();
|
||||
currentPetId = a.getPetId();
|
||||
currentServiceId = a.getServiceId();
|
||||
currentStaffId = a.getEmployeeId();
|
||||
|
||||
updateViewState(s -> {
|
||||
s.selectedCustomerId = currentCustomerId;
|
||||
s.selectedStoreId = currentStoreId;
|
||||
s.selectedPetId = currentPetId;
|
||||
s.selectedServiceId = currentServiceId;
|
||||
s.selectedStaffId = currentStaffId;
|
||||
});
|
||||
|
||||
if (currentCustomerId != null) loadPetsForCustomer(currentCustomerId);
|
||||
if (currentStoreId != null) loadEmployeesForStore(currentStoreId);
|
||||
}
|
||||
result.setValue(resource);
|
||||
});
|
||||
return result;
|
||||
}
|
||||
|
||||
/**
|
||||
* Saves the appointment by building the DTO from tracked state.
|
||||
*/
|
||||
public LiveData<Resource<AppointmentDTO>> saveAppointment(String date, String time, String status) {
|
||||
AppointmentDTO dto = new AppointmentDTO(currentCustomerId, currentStoreId, currentServiceId, currentStaffId, date, time, status, currentPetId);
|
||||
if (appointmentId != -1) {
|
||||
return repository.updateAppointment(appointmentId, dto);
|
||||
} else {
|
||||
return repository.createAppointment(dto);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Deletes the current appointment.
|
||||
*/
|
||||
public LiveData<Resource<Void>> deleteAppointment() {
|
||||
return repository.deleteAppointment(appointmentId);
|
||||
}
|
||||
|
||||
// UI Logic
|
||||
|
||||
/**
|
||||
* Updates the UI state when date, time, or status changes.
|
||||
*/
|
||||
public void onDateOrTimeChanged(String date, String time, String currentStatus) {
|
||||
updateViewState(s -> {
|
||||
s.availableStatuses = calculateAvailableStatuses(s.isEditing, date, time, currentStatus);
|
||||
boolean isPast = DateTimeUtils.isDateTimeInPast(date, time);
|
||||
boolean isCancelled = "Cancelled".equalsIgnoreCase(currentStatus);
|
||||
|
||||
if (isCancelled) {
|
||||
s.isPast = true;
|
||||
setAllFieldsEnabled(s, false);
|
||||
s.isStatusEnabled = false;
|
||||
s.isSaveVisible = false;
|
||||
} else if (isPast) {
|
||||
s.isPast = true;
|
||||
setAllFieldsEnabled(s, false);
|
||||
s.isStatusEnabled = true;
|
||||
} else {
|
||||
s.isPast = false;
|
||||
if (!s.isEditing) {
|
||||
s.isCustomerEnabled = true;
|
||||
s.isStoreEnabled = true;
|
||||
s.isServiceEnabled = true;
|
||||
s.isPetEnabled = currentCustomerId != null;
|
||||
}
|
||||
s.isDateEnabled = true;
|
||||
s.isTimeEnabled = true;
|
||||
s.isStatusEnabled = true;
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* Calculates available appointment statuses based on the current context.
|
||||
*/
|
||||
private String[] calculateAvailableStatuses(boolean isEditing, String date, String currentTime, String currentStatus) {
|
||||
if (!isEditing) return new String[]{"Booked"};
|
||||
if (date == null || date.isEmpty()) return new String[]{};
|
||||
if ("Cancelled".equalsIgnoreCase(currentStatus)) return new String[]{"Cancelled"};
|
||||
if (DateTimeUtils.isDateTimeInPast(date, currentTime)) return new String[]{"Completed", "Missed"};
|
||||
return new String[]{"Booked", "Cancelled"};
|
||||
}
|
||||
|
||||
/**
|
||||
* Helper method to enable or disable all fields.
|
||||
*/
|
||||
private void setAllFieldsEnabled(ViewState s, boolean enabled) {
|
||||
s.isCustomerEnabled = enabled;
|
||||
s.isStoreEnabled = enabled;
|
||||
s.isPetEnabled = enabled;
|
||||
s.isServiceEnabled = enabled;
|
||||
s.isStaffEnabled = enabled;
|
||||
s.isDateEnabled = enabled;
|
||||
s.isTimeEnabled = enabled;
|
||||
}
|
||||
|
||||
/**
|
||||
* Initializes the UI mode (Create vs Edit).
|
||||
*/
|
||||
public void initMode(boolean isEditing) {
|
||||
updateViewState(s -> {
|
||||
s.isEditing = isEditing;
|
||||
s.isDeleteVisible = isEditing;
|
||||
if (isEditing) {
|
||||
s.isCustomerEnabled = false;
|
||||
s.isStoreEnabled = false;
|
||||
s.isPetEnabled = false;
|
||||
s.isServiceEnabled = false;
|
||||
} else {
|
||||
s.isCustomerEnabled = true;
|
||||
s.isStoreEnabled = true;
|
||||
s.isServiceEnabled = true;
|
||||
s.isPetEnabled = false; // until customer selected
|
||||
s.isStaffEnabled = false; // until store selected
|
||||
s.availableStatuses = new String[]{"Booked"};
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* Validates if a booking is in the future.
|
||||
*/
|
||||
public boolean isValidFutureBooking(String status, String date, String time) {
|
||||
return !"BOOKED".equalsIgnoreCase(status) || !DateTimeUtils.isDateTimeInPast(date, time);
|
||||
}
|
||||
|
||||
/**
|
||||
* Helper to update the view state and notify observers.
|
||||
*/
|
||||
private void updateViewState(Action<ViewState> action) {
|
||||
ViewState current = viewState.getValue();
|
||||
if (current != null) {
|
||||
action.run(current);
|
||||
viewState.setValue(current);
|
||||
}
|
||||
}
|
||||
|
||||
private interface Action<T> {
|
||||
void run(T t);
|
||||
}
|
||||
|
||||
/**
|
||||
* A Class to show the states of Appointment Detail Fragment.
|
||||
*/
|
||||
public static class ViewState {
|
||||
public boolean isPast = false;
|
||||
public boolean isEditing = false;
|
||||
public boolean isSaveVisible = true;
|
||||
public boolean isDeleteVisible = false;
|
||||
public boolean isCustomerEnabled = true;
|
||||
public boolean isStoreEnabled = true;
|
||||
public boolean isPetEnabled = false;
|
||||
public boolean isServiceEnabled = true;
|
||||
public boolean isStaffEnabled = false;
|
||||
public boolean isDateEnabled = true;
|
||||
public boolean isTimeEnabled = true;
|
||||
public boolean isStatusEnabled = true;
|
||||
public String[] availableStatuses = new String[]{};
|
||||
|
||||
// Selected IDs
|
||||
public Long selectedCustomerId = null;
|
||||
public Long selectedStoreId = null;
|
||||
public Long selectedPetId = null;
|
||||
public Long selectedServiceId = null;
|
||||
public Long selectedStaffId = null;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,65 @@
|
||||
package com.example.petstoremobile.viewmodels;
|
||||
|
||||
import androidx.lifecycle.LiveData;
|
||||
import androidx.lifecycle.MutableLiveData;
|
||||
import androidx.lifecycle.ViewModel;
|
||||
|
||||
import com.example.petstoremobile.dtos.AppointmentDTO;
|
||||
import com.example.petstoremobile.dtos.BulkDeleteRequest;
|
||||
import com.example.petstoremobile.dtos.StoreDTO;
|
||||
import com.example.petstoremobile.repositories.AppointmentRepository;
|
||||
import com.example.petstoremobile.repositories.StoreRepository;
|
||||
import com.example.petstoremobile.utils.Resource;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.List;
|
||||
|
||||
import javax.inject.Inject;
|
||||
|
||||
import dagger.hilt.android.lifecycle.HiltViewModel;
|
||||
|
||||
@HiltViewModel
|
||||
public class AppointmentListViewModel extends ViewModel {
|
||||
private final AppointmentRepository appointmentRepository;
|
||||
private final StoreRepository storeRepository;
|
||||
|
||||
private final MutableLiveData<List<AppointmentDTO>> appointments = new MutableLiveData<>(new ArrayList<>());
|
||||
private final MutableLiveData<List<StoreDTO>> stores = new MutableLiveData<>(new ArrayList<>());
|
||||
private final MutableLiveData<Boolean> isLoading = new MutableLiveData<>(false);
|
||||
|
||||
@Inject
|
||||
public AppointmentListViewModel(AppointmentRepository appointmentRepository, StoreRepository storeRepository) {
|
||||
this.appointmentRepository = appointmentRepository;
|
||||
this.storeRepository = storeRepository;
|
||||
}
|
||||
|
||||
public LiveData<List<AppointmentDTO>> getAppointments() { return appointments; }
|
||||
public LiveData<List<StoreDTO>> getStores() { return stores; }
|
||||
public LiveData<Boolean> getIsLoading() { return isLoading; }
|
||||
|
||||
public void loadAppointments(String query, String status, Long storeId, String date, Long employeeId) {
|
||||
isLoading.setValue(true);
|
||||
appointmentRepository.getAllAppointments(0, 500, query, status, storeId, date, employeeId).observeForever(resource -> {
|
||||
if (resource != null) {
|
||||
if (resource.status == Resource.Status.SUCCESS && resource.data != null) {
|
||||
appointments.setValue(resource.data.getContent());
|
||||
isLoading.setValue(false);
|
||||
} else if (resource.status == Resource.Status.ERROR) {
|
||||
isLoading.setValue(false);
|
||||
}
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
public void loadStores() {
|
||||
storeRepository.getAllStores(0, 100).observeForever(resource -> {
|
||||
if (resource != null && resource.status == Resource.Status.SUCCESS && resource.data != null) {
|
||||
stores.setValue(resource.data.getContent());
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
public LiveData<Resource<Void>> bulkDeleteAppointments(List<String> ids) {
|
||||
return appointmentRepository.bulkDeleteAppointments(new BulkDeleteRequest(ids));
|
||||
}
|
||||
}
|
||||
@@ -1,68 +0,0 @@
|
||||
package com.example.petstoremobile.viewmodels;
|
||||
|
||||
import androidx.lifecycle.LiveData;
|
||||
import androidx.lifecycle.ViewModel;
|
||||
|
||||
import com.example.petstoremobile.dtos.AppointmentDTO;
|
||||
import com.example.petstoremobile.dtos.BulkDeleteRequest;
|
||||
import com.example.petstoremobile.dtos.PageResponse;
|
||||
import com.example.petstoremobile.repositories.AppointmentRepository;
|
||||
import com.example.petstoremobile.utils.Resource;
|
||||
|
||||
import java.util.List;
|
||||
|
||||
import javax.inject.Inject;
|
||||
|
||||
import dagger.hilt.android.lifecycle.HiltViewModel;
|
||||
|
||||
@HiltViewModel
|
||||
public class AppointmentViewModel extends ViewModel {
|
||||
private final AppointmentRepository repository;
|
||||
|
||||
@Inject
|
||||
public AppointmentViewModel(AppointmentRepository repository) {
|
||||
this.repository = repository;
|
||||
}
|
||||
|
||||
/**
|
||||
* Fetches a paginated list of all appointments with optional filters.
|
||||
*/
|
||||
public LiveData<Resource<PageResponse<AppointmentDTO>>> getAllAppointments(int page, int size, String query, String status, Long storeId, String date, Long employeeId) {
|
||||
return repository.getAllAppointments(page, size, query, status, storeId, date, employeeId);
|
||||
}
|
||||
|
||||
/**
|
||||
* Retrieves a single appointment by its ID.
|
||||
*/
|
||||
public LiveData<Resource<AppointmentDTO>> getAppointmentById(Long id) {
|
||||
return repository.getAppointmentById(id);
|
||||
}
|
||||
|
||||
/**
|
||||
* Creates a new appointment.
|
||||
*/
|
||||
public LiveData<Resource<AppointmentDTO>> createAppointment(AppointmentDTO appointment) {
|
||||
return repository.createAppointment(appointment);
|
||||
}
|
||||
|
||||
/**
|
||||
* Updates an existing appointment record by ID.
|
||||
*/
|
||||
public LiveData<Resource<AppointmentDTO>> updateAppointment(Long id, AppointmentDTO appointment) {
|
||||
return repository.updateAppointment(id, appointment);
|
||||
}
|
||||
|
||||
/**
|
||||
* Deletes an appointment record by ID.
|
||||
*/
|
||||
public LiveData<Resource<Void>> deleteAppointment(Long id) {
|
||||
return repository.deleteAppointment(id);
|
||||
}
|
||||
|
||||
/**
|
||||
* Deletes multiple appointment records.
|
||||
*/
|
||||
public LiveData<Resource<Void>> bulkDeleteAppointments(List<String> ids) {
|
||||
return repository.bulkDeleteAppointments(new BulkDeleteRequest(ids));
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,174 @@
|
||||
package com.example.petstoremobile.viewmodels;
|
||||
|
||||
import androidx.lifecycle.LiveData;
|
||||
import androidx.lifecycle.MutableLiveData;
|
||||
import androidx.lifecycle.ViewModel;
|
||||
|
||||
import com.example.petstoremobile.dtos.ConversationDTO;
|
||||
import com.example.petstoremobile.dtos.CustomerDTO;
|
||||
import com.example.petstoremobile.dtos.MessageDTO;
|
||||
import com.example.petstoremobile.dtos.PageResponse;
|
||||
import com.example.petstoremobile.dtos.SendMessageRequest;
|
||||
import com.example.petstoremobile.models.Chat;
|
||||
import com.example.petstoremobile.models.Message;
|
||||
import com.example.petstoremobile.repositories.ChatRepository;
|
||||
import com.example.petstoremobile.repositories.CustomerRepository;
|
||||
import com.example.petstoremobile.utils.Resource;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.HashMap;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
import java.util.stream.Collectors;
|
||||
|
||||
import javax.inject.Inject;
|
||||
|
||||
import dagger.hilt.android.lifecycle.HiltViewModel;
|
||||
import okhttp3.MultipartBody;
|
||||
import okhttp3.RequestBody;
|
||||
import okhttp3.ResponseBody;
|
||||
|
||||
@HiltViewModel
|
||||
public class ChatListViewModel extends ViewModel {
|
||||
private final ChatRepository chatRepository;
|
||||
private final CustomerRepository customerRepository;
|
||||
|
||||
private final MutableLiveData<List<Chat>> activeChats = new MutableLiveData<>(new ArrayList<>());
|
||||
private final MutableLiveData<List<Chat>> closedChats = new MutableLiveData<>(new ArrayList<>());
|
||||
private final MutableLiveData<List<Message>> messageList = new MutableLiveData<>(new ArrayList<>());
|
||||
private final Map<Long, String> customerNames = new HashMap<>();
|
||||
private final MutableLiveData<Boolean> isLoading = new MutableLiveData<>(false);
|
||||
|
||||
private Long lastActiveConversationId = null;
|
||||
|
||||
@Inject
|
||||
public ChatListViewModel(ChatRepository chatRepository, CustomerRepository customerRepository) {
|
||||
this.chatRepository = chatRepository;
|
||||
this.customerRepository = customerRepository;
|
||||
}
|
||||
|
||||
public LiveData<List<Chat>> getActiveChats() { return activeChats; }
|
||||
public LiveData<List<Chat>> getClosedChats() { return closedChats; }
|
||||
public LiveData<List<Message>> getMessageList() { return messageList; }
|
||||
public LiveData<Boolean> getIsLoading() { return isLoading; }
|
||||
|
||||
public Long getLastActiveConversationId() {
|
||||
return lastActiveConversationId;
|
||||
}
|
||||
|
||||
public void setLastActiveConversationId(Long conversationId) {
|
||||
this.lastActiveConversationId = conversationId;
|
||||
}
|
||||
|
||||
public void loadCustomers() {
|
||||
isLoading.setValue(true);
|
||||
customerRepository.getAllCustomers(0, 100).observeForever(resource -> {
|
||||
if (resource != null && resource.status == Resource.Status.SUCCESS && resource.data != null) {
|
||||
for (CustomerDTO c : resource.data.getContent()) {
|
||||
customerNames.put(c.getCustomerId(), c.getFullName());
|
||||
}
|
||||
loadConversations();
|
||||
} else if (resource != null && resource.status == Resource.Status.ERROR) {
|
||||
isLoading.setValue(false);
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
public void loadConversations() {
|
||||
isLoading.setValue(true);
|
||||
chatRepository.getAllConversations().observeForever(resource -> {
|
||||
if (resource != null && resource.status == Resource.Status.SUCCESS && resource.data != null) {
|
||||
List<Chat> active = new ArrayList<>();
|
||||
List<Chat> closed = new ArrayList<>();
|
||||
for (ConversationDTO dto : resource.data) {
|
||||
String name = customerNames.getOrDefault(dto.getCustomerId(), "Customer #" + dto.getCustomerId());
|
||||
Chat chat = new Chat(String.valueOf(dto.getId()), name, dto.getLastMessage(), dto.getCustomerId(), dto.getStaffId(), dto.getStatus());
|
||||
if ("CLOSED".equalsIgnoreCase(dto.getStatus())) {
|
||||
closed.add(chat);
|
||||
} else {
|
||||
active.add(chat);
|
||||
}
|
||||
}
|
||||
activeChats.setValue(active);
|
||||
closedChats.setValue(closed);
|
||||
isLoading.setValue(false);
|
||||
} else if (resource != null && resource.status == Resource.Status.ERROR) {
|
||||
isLoading.setValue(false);
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
public void loadMessageHistory(Long conversationId) {
|
||||
isLoading.setValue(true);
|
||||
chatRepository.getMessages(conversationId).observeForever(resource -> {
|
||||
if (resource != null && resource.status == Resource.Status.SUCCESS && resource.data != null) {
|
||||
List<Message> messages = new ArrayList<>();
|
||||
for (MessageDTO dto : resource.data) {
|
||||
messages.add(dtoToModel(dto));
|
||||
}
|
||||
messageList.setValue(messages);
|
||||
isLoading.setValue(false);
|
||||
} else if (resource != null && resource.status == Resource.Status.ERROR) {
|
||||
isLoading.setValue(false);
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
public LiveData<Resource<MessageDTO>> sendMessage(Long conversationId, String text) {
|
||||
return chatRepository.sendMessage(conversationId, new SendMessageRequest(text));
|
||||
}
|
||||
|
||||
public LiveData<Resource<MessageDTO>> sendMessageWithAttachment(Long conversationId, MultipartBody.Part content, MultipartBody.Part attachment) {
|
||||
return chatRepository.sendMessageWithAttachment(conversationId, content, attachment);
|
||||
}
|
||||
|
||||
public LiveData<Resource<ResponseBody>> downloadAttachment(Long messageId) {
|
||||
return chatRepository.downloadAttachment(messageId);
|
||||
}
|
||||
|
||||
public void addMessageLocally(MessageDTO dto) {
|
||||
List<Message> current = new ArrayList<>(messageList.getValue());
|
||||
current.add(dtoToModel(dto));
|
||||
messageList.setValue(current);
|
||||
}
|
||||
|
||||
public void updateConversationLocally(ConversationDTO dto) {
|
||||
updateList(activeChats, dto);
|
||||
updateList(closedChats, dto);
|
||||
|
||||
loadConversations();
|
||||
}
|
||||
|
||||
private void updateList(MutableLiveData<List<Chat>> liveData, ConversationDTO dto) {
|
||||
List<Chat> current = new ArrayList<>(liveData.getValue());
|
||||
String name = customerNames.getOrDefault(dto.getCustomerId(), "Customer #" + dto.getCustomerId());
|
||||
boolean updated = false;
|
||||
for (int i = 0; i < current.size(); i++) {
|
||||
if (current.get(i).getChatId().equals(String.valueOf(dto.getId()))) {
|
||||
current.set(i, new Chat(String.valueOf(dto.getId()), name, dto.getLastMessage(), dto.getCustomerId(), dto.getStaffId(), dto.getStatus()));
|
||||
updated = true;
|
||||
break;
|
||||
}
|
||||
}
|
||||
if (updated) liveData.setValue(current);
|
||||
}
|
||||
|
||||
private Message dtoToModel(MessageDTO dto) {
|
||||
Message m = new Message();
|
||||
m.setId(dto.getId());
|
||||
m.setConversationId(dto.getConversationId());
|
||||
m.setSenderId(dto.getSenderId());
|
||||
m.setContent(dto.getContent());
|
||||
m.setTimestamp(dto.getTimestamp());
|
||||
m.setIsRead(dto.getIsRead());
|
||||
m.setAttachmentUrl(dto.getAttachmentUrl());
|
||||
m.setAttachmentName(dto.getAttachmentName());
|
||||
m.setAttachmentMimeType(dto.getAttachmentMimeType());
|
||||
m.setAttachmentSizeBytes(dto.getAttachmentSizeBytes());
|
||||
return m;
|
||||
}
|
||||
|
||||
public String getCustomerName(Long customerId) {
|
||||
return customerNames.getOrDefault(customerId, "Customer #" + customerId);
|
||||
}
|
||||
}
|
||||
@@ -1,58 +0,0 @@
|
||||
package com.example.petstoremobile.viewmodels;
|
||||
|
||||
import androidx.lifecycle.LiveData;
|
||||
import androidx.lifecycle.ViewModel;
|
||||
|
||||
import com.example.petstoremobile.dtos.ConversationDTO;
|
||||
import com.example.petstoremobile.dtos.CustomerDTO;
|
||||
import com.example.petstoremobile.dtos.MessageDTO;
|
||||
import com.example.petstoremobile.dtos.PageResponse;
|
||||
import com.example.petstoremobile.dtos.SendMessageRequest;
|
||||
import com.example.petstoremobile.repositories.ChatRepository;
|
||||
import com.example.petstoremobile.utils.Resource;
|
||||
|
||||
import java.util.List;
|
||||
|
||||
import javax.inject.Inject;
|
||||
|
||||
import dagger.hilt.android.lifecycle.HiltViewModel;
|
||||
/**
|
||||
* ViewModel for managing chat-related UI state and data operations.
|
||||
*/
|
||||
@HiltViewModel
|
||||
public class ChatViewModel extends ViewModel {
|
||||
private final ChatRepository repository;
|
||||
|
||||
@Inject
|
||||
public ChatViewModel(ChatRepository repository) {
|
||||
this.repository = repository;
|
||||
}
|
||||
|
||||
/**
|
||||
* Retrieves all chat conversations for the current user.
|
||||
*/
|
||||
public LiveData<Resource<List<ConversationDTO>>> getAllConversations() {
|
||||
return repository.getAllConversations();
|
||||
}
|
||||
|
||||
/**
|
||||
* Retrieves the message history for a specific conversation.
|
||||
*/
|
||||
public LiveData<Resource<List<MessageDTO>>> getMessages(Long conversationId) {
|
||||
return repository.getMessages(conversationId);
|
||||
}
|
||||
|
||||
/**
|
||||
* Sends a plain text message to a conversation.
|
||||
*/
|
||||
public LiveData<Resource<MessageDTO>> sendMessage(Long conversationId, SendMessageRequest request) {
|
||||
return repository.sendMessage(conversationId, request);
|
||||
}
|
||||
|
||||
/**
|
||||
* Fetches a paginated list of customers.
|
||||
*/
|
||||
public LiveData<Resource<PageResponse<CustomerDTO>>> getAllCustomers(int page, int size) {
|
||||
return repository.getAllCustomers(page, size);
|
||||
}
|
||||
}
|
||||
@@ -1,37 +0,0 @@
|
||||
package com.example.petstoremobile.viewmodels;
|
||||
|
||||
import androidx.lifecycle.LiveData;
|
||||
import androidx.lifecycle.ViewModel;
|
||||
|
||||
import com.example.petstoremobile.dtos.CustomerDTO;
|
||||
import com.example.petstoremobile.dtos.PageResponse;
|
||||
import com.example.petstoremobile.repositories.CustomerRepository;
|
||||
import com.example.petstoremobile.utils.Resource;
|
||||
|
||||
import javax.inject.Inject;
|
||||
|
||||
import dagger.hilt.android.lifecycle.HiltViewModel;
|
||||
|
||||
@HiltViewModel
|
||||
public class CustomerViewModel extends ViewModel {
|
||||
private final CustomerRepository repository;
|
||||
|
||||
@Inject
|
||||
public CustomerViewModel(CustomerRepository repository) {
|
||||
this.repository = repository;
|
||||
}
|
||||
|
||||
/**
|
||||
* Fetches a paginated list of all customers.
|
||||
*/
|
||||
public LiveData<Resource<PageResponse<CustomerDTO>>> getAllCustomers(int page, int size) {
|
||||
return repository.getAllCustomers(page, size);
|
||||
}
|
||||
|
||||
/**
|
||||
* Retrieves a single customer by their ID.
|
||||
*/
|
||||
public LiveData<Resource<CustomerDTO>> getCustomerById(Long id) {
|
||||
return repository.getCustomerById(id);
|
||||
}
|
||||
}
|
||||
@@ -1,43 +0,0 @@
|
||||
package com.example.petstoremobile.viewmodels;
|
||||
|
||||
import androidx.lifecycle.LiveData;
|
||||
import androidx.lifecycle.ViewModel;
|
||||
|
||||
import com.example.petstoremobile.dtos.EmployeeDTO;
|
||||
import com.example.petstoremobile.dtos.PageResponse;
|
||||
import com.example.petstoremobile.repositories.EmployeeRepository;
|
||||
import com.example.petstoremobile.utils.Resource;
|
||||
|
||||
import javax.inject.Inject;
|
||||
|
||||
import dagger.hilt.android.lifecycle.HiltViewModel;
|
||||
|
||||
@HiltViewModel
|
||||
public class EmployeeViewModel extends ViewModel {
|
||||
private final EmployeeRepository employeeRepository;
|
||||
|
||||
@Inject
|
||||
public EmployeeViewModel(EmployeeRepository employeeRepository) {
|
||||
this.employeeRepository = employeeRepository;
|
||||
}
|
||||
|
||||
public LiveData<Resource<PageResponse<EmployeeDTO>>> getAllEmployees(int page, int size) {
|
||||
return employeeRepository.getAllEmployees(page, size);
|
||||
}
|
||||
|
||||
public LiveData<Resource<EmployeeDTO>> getEmployeeById(Long id) {
|
||||
return employeeRepository.getEmployeeById(id);
|
||||
}
|
||||
|
||||
public LiveData<Resource<EmployeeDTO>> createEmployee(EmployeeDTO dto) {
|
||||
return employeeRepository.createEmployee(dto);
|
||||
}
|
||||
|
||||
public LiveData<Resource<EmployeeDTO>> updateEmployee(Long id, EmployeeDTO dto) {
|
||||
return employeeRepository.updateEmployee(id, dto);
|
||||
}
|
||||
|
||||
public LiveData<Resource<Void>> deleteEmployee(Long id) {
|
||||
return employeeRepository.deleteEmployee(id);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,79 @@
|
||||
package com.example.petstoremobile.viewmodels;
|
||||
|
||||
import androidx.lifecycle.LiveData;
|
||||
import androidx.lifecycle.MutableLiveData;
|
||||
import androidx.lifecycle.ViewModel;
|
||||
|
||||
import com.example.petstoremobile.dtos.DropdownDTO;
|
||||
import com.example.petstoremobile.dtos.InventoryDTO;
|
||||
import com.example.petstoremobile.dtos.PageResponse;
|
||||
import com.example.petstoremobile.dtos.ProductDTO;
|
||||
import com.example.petstoremobile.repositories.InventoryRepository;
|
||||
import com.example.petstoremobile.repositories.ProductRepository;
|
||||
import com.example.petstoremobile.repositories.StoreRepository;
|
||||
import com.example.petstoremobile.utils.Resource;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.List;
|
||||
|
||||
import javax.inject.Inject;
|
||||
|
||||
import dagger.hilt.android.lifecycle.HiltViewModel;
|
||||
|
||||
@HiltViewModel
|
||||
public class InventoryDetailViewModel extends ViewModel {
|
||||
private final InventoryRepository inventoryRepository;
|
||||
private final StoreRepository storeRepository;
|
||||
private final ProductRepository productRepository;
|
||||
|
||||
private long inventoryId = -1;
|
||||
private boolean isEditing = false;
|
||||
|
||||
private final MutableLiveData<List<DropdownDTO>> storeList = new MutableLiveData<>(new ArrayList<>());
|
||||
private final MutableLiveData<List<ProductDTO>> productList = new MutableLiveData<>(new ArrayList<>());
|
||||
|
||||
@Inject
|
||||
public InventoryDetailViewModel(InventoryRepository inventoryRepository, StoreRepository storeRepository, ProductRepository productRepository) {
|
||||
this.inventoryRepository = inventoryRepository;
|
||||
this.storeRepository = storeRepository;
|
||||
this.productRepository = productRepository;
|
||||
}
|
||||
|
||||
public void setInventoryId(long id) {
|
||||
this.inventoryId = id;
|
||||
this.isEditing = id != -1;
|
||||
}
|
||||
|
||||
public long getInventoryId() { return inventoryId; }
|
||||
public boolean isEditing() { return isEditing; }
|
||||
|
||||
public LiveData<Resource<InventoryDTO>> loadInventory() {
|
||||
return inventoryRepository.getInventoryById(inventoryId);
|
||||
}
|
||||
|
||||
public LiveData<Resource<List<DropdownDTO>>> loadStores() {
|
||||
return storeRepository.getStoreDropdowns();
|
||||
}
|
||||
|
||||
public LiveData<Resource<PageResponse<ProductDTO>>> loadProducts() {
|
||||
return productRepository.getAllProducts(null, null, 0, 500, "prodName");
|
||||
}
|
||||
|
||||
public LiveData<Resource<InventoryDTO>> saveInventory(InventoryDTO dto) {
|
||||
if (isEditing) {
|
||||
return inventoryRepository.updateInventory(inventoryId, dto);
|
||||
} else {
|
||||
return inventoryRepository.createInventory(dto);
|
||||
}
|
||||
}
|
||||
|
||||
public LiveData<Resource<Void>> deleteInventory() {
|
||||
return inventoryRepository.deleteInventory(inventoryId);
|
||||
}
|
||||
|
||||
public void setStoreList(List<DropdownDTO> list) { storeList.setValue(list); }
|
||||
public LiveData<List<DropdownDTO>> getStoreList() { return storeList; }
|
||||
|
||||
public void setProductList(List<ProductDTO> list) { productList.setValue(list); }
|
||||
public LiveData<List<ProductDTO>> getProductList() { return productList; }
|
||||
}
|
||||
@@ -0,0 +1,81 @@
|
||||
package com.example.petstoremobile.viewmodels;
|
||||
|
||||
import androidx.lifecycle.LiveData;
|
||||
import androidx.lifecycle.MutableLiveData;
|
||||
import androidx.lifecycle.ViewModel;
|
||||
|
||||
import com.example.petstoremobile.dtos.BulkDeleteRequest;
|
||||
import com.example.petstoremobile.dtos.InventoryDTO;
|
||||
import com.example.petstoremobile.dtos.StoreDTO;
|
||||
import com.example.petstoremobile.repositories.InventoryRepository;
|
||||
import com.example.petstoremobile.repositories.StoreRepository;
|
||||
import com.example.petstoremobile.utils.Resource;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.List;
|
||||
|
||||
import javax.inject.Inject;
|
||||
|
||||
import dagger.hilt.android.lifecycle.HiltViewModel;
|
||||
|
||||
@HiltViewModel
|
||||
public class InventoryListViewModel extends ViewModel {
|
||||
private final InventoryRepository inventoryRepository;
|
||||
private final StoreRepository storeRepository;
|
||||
|
||||
private final MutableLiveData<List<InventoryDTO>> inventory = new MutableLiveData<>(new ArrayList<>());
|
||||
private final MutableLiveData<List<StoreDTO>> stores = new MutableLiveData<>(new ArrayList<>());
|
||||
private final MutableLiveData<Boolean> isLoading = new MutableLiveData<>(false);
|
||||
|
||||
private int currentPage = 0;
|
||||
private boolean isLastPage = false;
|
||||
private static final int PAGE_SIZE = 20;
|
||||
|
||||
@Inject
|
||||
public InventoryListViewModel(InventoryRepository inventoryRepository, StoreRepository storeRepository) {
|
||||
this.inventoryRepository = inventoryRepository;
|
||||
this.storeRepository = storeRepository;
|
||||
}
|
||||
|
||||
public LiveData<List<InventoryDTO>> getInventory() { return inventory; }
|
||||
public LiveData<List<StoreDTO>> getStores() { return stores; }
|
||||
public LiveData<Boolean> getIsLoading() { return isLoading; }
|
||||
public boolean isLastPage() { return isLastPage; }
|
||||
|
||||
public void loadInventory(boolean reset, String query, Long storeId) {
|
||||
if (isLoading.getValue() != null && isLoading.getValue() && !reset) return;
|
||||
|
||||
if (reset) {
|
||||
currentPage = 0;
|
||||
isLastPage = false;
|
||||
}
|
||||
|
||||
isLoading.setValue(true);
|
||||
inventoryRepository.getAllInventory(query, storeId, currentPage, PAGE_SIZE, "product.prodName").observeForever(resource -> {
|
||||
if (resource != null) {
|
||||
if (resource.status == Resource.Status.SUCCESS && resource.data != null) {
|
||||
List<InventoryDTO> currentList = reset ? new ArrayList<>() : new ArrayList<>(inventory.getValue());
|
||||
currentList.addAll(resource.data.getContent());
|
||||
inventory.setValue(currentList);
|
||||
isLastPage = resource.data.isLast();
|
||||
if (!isLastPage) currentPage++;
|
||||
isLoading.setValue(false);
|
||||
} else if (resource.status == Resource.Status.ERROR) {
|
||||
isLoading.setValue(false);
|
||||
}
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
public void loadStores() {
|
||||
storeRepository.getAllStores(0, 100).observeForever(resource -> {
|
||||
if (resource != null && resource.status == Resource.Status.SUCCESS && resource.data != null) {
|
||||
stores.setValue(resource.data.getContent());
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
public LiveData<Resource<Void>> bulkDeleteInventory(List<String> ids) {
|
||||
return inventoryRepository.bulkDeleteInventory(new BulkDeleteRequest(ids));
|
||||
}
|
||||
}
|
||||
@@ -1,90 +0,0 @@
|
||||
package com.example.petstoremobile.viewmodels;
|
||||
|
||||
import androidx.lifecycle.LiveData;
|
||||
import androidx.lifecycle.ViewModel;
|
||||
|
||||
import com.example.petstoremobile.dtos.BulkDeleteRequest;
|
||||
import com.example.petstoremobile.dtos.CategoryDTO;
|
||||
import com.example.petstoremobile.dtos.InventoryDTO;
|
||||
import com.example.petstoremobile.dtos.PageResponse;
|
||||
import com.example.petstoremobile.dtos.StoreDTO;
|
||||
import com.example.petstoremobile.repositories.CategoryRepository;
|
||||
import com.example.petstoremobile.repositories.InventoryRepository;
|
||||
import com.example.petstoremobile.repositories.StoreRepository;
|
||||
import com.example.petstoremobile.utils.Resource;
|
||||
|
||||
import java.util.List;
|
||||
|
||||
import javax.inject.Inject;
|
||||
|
||||
import dagger.hilt.android.lifecycle.HiltViewModel;
|
||||
|
||||
@HiltViewModel
|
||||
public class InventoryViewModel extends ViewModel {
|
||||
private final InventoryRepository inventoryRepository;
|
||||
private final CategoryRepository categoryRepository;
|
||||
private final StoreRepository storeRepository;
|
||||
|
||||
@Inject
|
||||
public InventoryViewModel(InventoryRepository inventoryRepository, CategoryRepository categoryRepository, StoreRepository storeRepository) {
|
||||
this.inventoryRepository = inventoryRepository;
|
||||
this.categoryRepository = categoryRepository;
|
||||
this.storeRepository = storeRepository;
|
||||
}
|
||||
|
||||
/**
|
||||
* Retrieves a paginated list of inventory items, with optional filtering and sorting.
|
||||
*/
|
||||
public LiveData<Resource<PageResponse<InventoryDTO>>> getAllInventory(String query, Long storeId, int page, int size, String sort) {
|
||||
return inventoryRepository.getAllInventory(query, storeId, page, size, sort);
|
||||
}
|
||||
|
||||
/**
|
||||
* Retrieves a single inventory item by its ID.
|
||||
*/
|
||||
public LiveData<Resource<InventoryDTO>> getInventoryById(Long id) {
|
||||
return inventoryRepository.getInventoryById(id);
|
||||
}
|
||||
|
||||
/**
|
||||
* Creates a new inventory record.
|
||||
*/
|
||||
public LiveData<Resource<InventoryDTO>> createInventory(InventoryDTO request) {
|
||||
return inventoryRepository.createInventory(request);
|
||||
}
|
||||
|
||||
/**
|
||||
* Updates an existing inventory record by ID.
|
||||
*/
|
||||
public LiveData<Resource<InventoryDTO>> updateInventory(Long id, InventoryDTO request) {
|
||||
return inventoryRepository.updateInventory(id, request);
|
||||
}
|
||||
|
||||
/**
|
||||
* Deletes an inventory record by ID.
|
||||
*/
|
||||
public LiveData<Resource<Void>> deleteInventory(Long id) {
|
||||
return inventoryRepository.deleteInventory(id);
|
||||
}
|
||||
|
||||
/**
|
||||
* Deletes multiple inventory records in a single request.
|
||||
*/
|
||||
public LiveData<Resource<Void>> bulkDeleteInventory(List<String> ids) {
|
||||
return inventoryRepository.bulkDeleteInventory(new BulkDeleteRequest(ids));
|
||||
}
|
||||
|
||||
/**
|
||||
* Retrieves a paginated list of categories.
|
||||
*/
|
||||
public LiveData<Resource<PageResponse<CategoryDTO>>> getAllCategories(int page, int size) {
|
||||
return categoryRepository.getAllCategories(page, size);
|
||||
}
|
||||
|
||||
/**
|
||||
* Retrieves a paginated list of stores.
|
||||
*/
|
||||
public LiveData<Resource<PageResponse<StoreDTO>>> getAllStores(int page, int size) {
|
||||
return storeRepository.getAllStores(page, size);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,103 @@
|
||||
package com.example.petstoremobile.viewmodels;
|
||||
|
||||
import androidx.lifecycle.LiveData;
|
||||
import androidx.lifecycle.MutableLiveData;
|
||||
import androidx.lifecycle.ViewModel;
|
||||
|
||||
import com.example.petstoremobile.dtos.DropdownDTO;
|
||||
import com.example.petstoremobile.dtos.PetDTO;
|
||||
import com.example.petstoremobile.repositories.CustomerRepository;
|
||||
import com.example.petstoremobile.repositories.PetRepository;
|
||||
import com.example.petstoremobile.repositories.StoreRepository;
|
||||
import com.example.petstoremobile.utils.Resource;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.List;
|
||||
|
||||
import javax.inject.Inject;
|
||||
|
||||
import dagger.hilt.android.lifecycle.HiltViewModel;
|
||||
|
||||
@HiltViewModel
|
||||
public class PetDetailViewModel extends ViewModel {
|
||||
private final PetRepository petRepository;
|
||||
private final CustomerRepository customerRepository;
|
||||
private final StoreRepository storeRepository;
|
||||
|
||||
private final MutableLiveData<PetDTO> petState = new MutableLiveData<>();
|
||||
private final MutableLiveData<List<DropdownDTO>> customerList = new MutableLiveData<>(new ArrayList<>());
|
||||
private final MutableLiveData<List<DropdownDTO>> storeList = new MutableLiveData<>(new ArrayList<>());
|
||||
private final MutableLiveData<Boolean> isLoading = new MutableLiveData<>(false);
|
||||
|
||||
private long petId = -1;
|
||||
private boolean isEditing = false;
|
||||
|
||||
@Inject
|
||||
public PetDetailViewModel(PetRepository petRepository, CustomerRepository customerRepository, StoreRepository storeRepository) {
|
||||
this.petRepository = petRepository;
|
||||
this.customerRepository = customerRepository;
|
||||
this.storeRepository = storeRepository;
|
||||
}
|
||||
|
||||
public void setPetId(long id) {
|
||||
this.petId = id;
|
||||
this.isEditing = id != -1;
|
||||
}
|
||||
|
||||
public long getPetId() {
|
||||
return petId;
|
||||
}
|
||||
|
||||
public boolean isEditing() {
|
||||
return isEditing;
|
||||
}
|
||||
|
||||
public LiveData<Resource<PetDTO>> loadPet() {
|
||||
return petRepository.getPetById(petId);
|
||||
}
|
||||
|
||||
public LiveData<Resource<List<DropdownDTO>>> loadCustomers() {
|
||||
return customerRepository.getCustomerDropdowns();
|
||||
}
|
||||
|
||||
public LiveData<Resource<List<DropdownDTO>>> loadStores() {
|
||||
return storeRepository.getStoreDropdowns();
|
||||
}
|
||||
|
||||
public LiveData<Resource<PetDTO>> savePet(PetDTO petDTO) {
|
||||
if (isEditing) {
|
||||
petDTO.setPetId(petId);
|
||||
return petRepository.updatePet(petId, petDTO);
|
||||
} else {
|
||||
return petRepository.createPet(petDTO);
|
||||
}
|
||||
}
|
||||
|
||||
public LiveData<Resource<Void>> deletePet() {
|
||||
return petRepository.deletePet(petId);
|
||||
}
|
||||
|
||||
public void setCustomerList(List<DropdownDTO> list) {
|
||||
customerList.setValue(list);
|
||||
}
|
||||
|
||||
public LiveData<List<DropdownDTO>> getCustomerList() {
|
||||
return customerList;
|
||||
}
|
||||
|
||||
public void setStoreList(List<DropdownDTO> list) {
|
||||
storeList.setValue(list);
|
||||
}
|
||||
|
||||
public LiveData<List<DropdownDTO>> getStoreList() {
|
||||
return storeList;
|
||||
}
|
||||
|
||||
public LiveData<Boolean> getIsLoading() {
|
||||
return isLoading;
|
||||
}
|
||||
|
||||
public void setLoading(boolean loading) {
|
||||
isLoading.setValue(loading);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,69 @@
|
||||
package com.example.petstoremobile.viewmodels;
|
||||
|
||||
import androidx.lifecycle.LiveData;
|
||||
import androidx.lifecycle.MutableLiveData;
|
||||
import androidx.lifecycle.ViewModel;
|
||||
|
||||
import com.example.petstoremobile.dtos.BulkDeleteRequest;
|
||||
import com.example.petstoremobile.dtos.PageResponse;
|
||||
import com.example.petstoremobile.dtos.PetDTO;
|
||||
import com.example.petstoremobile.dtos.StoreDTO;
|
||||
import com.example.petstoremobile.repositories.PetRepository;
|
||||
import com.example.petstoremobile.repositories.StoreRepository;
|
||||
import com.example.petstoremobile.utils.Resource;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.List;
|
||||
|
||||
import javax.inject.Inject;
|
||||
|
||||
import dagger.hilt.android.lifecycle.HiltViewModel;
|
||||
|
||||
@HiltViewModel
|
||||
public class PetListViewModel extends ViewModel {
|
||||
private final PetRepository petRepository;
|
||||
private final StoreRepository storeRepository;
|
||||
|
||||
private final MutableLiveData<List<PetDTO>> pets = new MutableLiveData<>(new ArrayList<>());
|
||||
private final MutableLiveData<List<StoreDTO>> stores = new MutableLiveData<>(new ArrayList<>());
|
||||
private final MutableLiveData<Boolean> isLoading = new MutableLiveData<>(false);
|
||||
|
||||
@Inject
|
||||
public PetListViewModel(PetRepository petRepository, StoreRepository storeRepository) {
|
||||
this.petRepository = petRepository;
|
||||
this.storeRepository = storeRepository;
|
||||
}
|
||||
|
||||
public LiveData<List<PetDTO>> getPets() { return pets; }
|
||||
public LiveData<List<StoreDTO>> getStores() { return stores; }
|
||||
public LiveData<Boolean> getIsLoading() { return isLoading; }
|
||||
|
||||
public void loadPets(String query, String status, String species, Long storeId) {
|
||||
if ("All Statuses".equals(status)) status = null;
|
||||
if ("All Species".equals(species)) species = null;
|
||||
|
||||
isLoading.setValue(true);
|
||||
petRepository.getAllPets(0, 100, query, status, species, storeId, null, "petName").observeForever(resource -> {
|
||||
if (resource != null) {
|
||||
if (resource.status == Resource.Status.SUCCESS && resource.data != null) {
|
||||
pets.setValue(resource.data.getContent());
|
||||
isLoading.setValue(false);
|
||||
} else if (resource.status == Resource.Status.ERROR) {
|
||||
isLoading.setValue(false);
|
||||
}
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
public void loadStores() {
|
||||
storeRepository.getAllStores(0, 100).observeForever(resource -> {
|
||||
if (resource != null && resource.status == Resource.Status.SUCCESS && resource.data != null) {
|
||||
stores.setValue(resource.data.getContent());
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
public LiveData<Resource<Void>> bulkDeletePets(List<String> ids) {
|
||||
return petRepository.bulkDeletePets(new BulkDeleteRequest(ids));
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,35 @@
|
||||
package com.example.petstoremobile.viewmodels;
|
||||
|
||||
import androidx.lifecycle.LiveData;
|
||||
import androidx.lifecycle.ViewModel;
|
||||
|
||||
import com.example.petstoremobile.dtos.PetDTO;
|
||||
import com.example.petstoremobile.repositories.PetRepository;
|
||||
import com.example.petstoremobile.utils.Resource;
|
||||
|
||||
import javax.inject.Inject;
|
||||
|
||||
import dagger.hilt.android.lifecycle.HiltViewModel;
|
||||
import okhttp3.MultipartBody;
|
||||
|
||||
@HiltViewModel
|
||||
public class PetProfileViewModel extends ViewModel {
|
||||
private final PetRepository repository;
|
||||
|
||||
@Inject
|
||||
public PetProfileViewModel(PetRepository repository) {
|
||||
this.repository = repository;
|
||||
}
|
||||
|
||||
public LiveData<Resource<PetDTO>> getPetById(Long id) {
|
||||
return repository.getPetById(id);
|
||||
}
|
||||
|
||||
public LiveData<Resource<Void>> uploadPetImage(Long id, MultipartBody.Part image) {
|
||||
return repository.uploadPetImage(id, image);
|
||||
}
|
||||
|
||||
public LiveData<Resource<Void>> deletePetImage(Long id) {
|
||||
return repository.deletePetImage(id);
|
||||
}
|
||||
}
|
||||
@@ -1,83 +0,0 @@
|
||||
package com.example.petstoremobile.viewmodels;
|
||||
|
||||
import androidx.lifecycle.LiveData;
|
||||
import androidx.lifecycle.ViewModel;
|
||||
|
||||
import com.example.petstoremobile.dtos.BulkDeleteRequest;
|
||||
import com.example.petstoremobile.dtos.PageResponse;
|
||||
import com.example.petstoremobile.dtos.PetDTO;
|
||||
import com.example.petstoremobile.repositories.PetRepository;
|
||||
import com.example.petstoremobile.utils.Resource;
|
||||
|
||||
import java.util.List;
|
||||
|
||||
import javax.inject.Inject;
|
||||
|
||||
import dagger.hilt.android.lifecycle.HiltViewModel;
|
||||
import okhttp3.MultipartBody;
|
||||
|
||||
@HiltViewModel
|
||||
public class PetViewModel extends ViewModel {
|
||||
private final PetRepository repository;
|
||||
|
||||
@Inject
|
||||
public PetViewModel(PetRepository repository) {
|
||||
this.repository = repository;
|
||||
}
|
||||
|
||||
/**
|
||||
* Fetches a paginated list of pets with filters.
|
||||
*/
|
||||
public LiveData<Resource<PageResponse<PetDTO>>> getAllPets(int page, int size, String query, String status, String species, Long storeId, String sort) {
|
||||
return repository.getAllPets(page, size, query, status, species, storeId, sort);
|
||||
}
|
||||
|
||||
/**
|
||||
* Retrieves a single pet by its ID.
|
||||
*/
|
||||
public LiveData<Resource<PetDTO>> getPetById(Long id) {
|
||||
return repository.getPetById(id);
|
||||
}
|
||||
|
||||
/**
|
||||
* Creates a new pet record.
|
||||
*/
|
||||
public LiveData<Resource<PetDTO>> createPet(PetDTO pet) {
|
||||
return repository.createPet(pet);
|
||||
}
|
||||
|
||||
/**
|
||||
* Updates an existing pet record by ID.
|
||||
*/
|
||||
public LiveData<Resource<PetDTO>> updatePet(Long id, PetDTO pet) {
|
||||
return repository.updatePet(id, pet);
|
||||
}
|
||||
|
||||
/**
|
||||
* Deletes a pet record by ID.
|
||||
*/
|
||||
public LiveData<Resource<Void>> deletePet(Long id) {
|
||||
return repository.deletePet(id);
|
||||
}
|
||||
|
||||
/**
|
||||
* Deletes multiple pet records.
|
||||
*/
|
||||
public LiveData<Resource<Void>> bulkDeletePets(List<String> ids) {
|
||||
return repository.bulkDeletePets(new BulkDeleteRequest(ids));
|
||||
}
|
||||
|
||||
/**
|
||||
* Uploads an image for a specific pet.
|
||||
*/
|
||||
public LiveData<Resource<Void>> uploadPetImage(Long id, MultipartBody.Part image) {
|
||||
return repository.uploadPetImage(id, image);
|
||||
}
|
||||
|
||||
/**
|
||||
* Deletes the image associated with a specific pet.
|
||||
*/
|
||||
public LiveData<Resource<Void>> deletePetImage(Long id) {
|
||||
return repository.deletePetImage(id);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,85 @@
|
||||
package com.example.petstoremobile.viewmodels;
|
||||
|
||||
import androidx.lifecycle.LiveData;
|
||||
import androidx.lifecycle.MutableLiveData;
|
||||
import androidx.lifecycle.ViewModel;
|
||||
|
||||
import com.example.petstoremobile.dtos.CategoryDTO;
|
||||
import com.example.petstoremobile.dtos.PageResponse;
|
||||
import com.example.petstoremobile.dtos.ProductDTO;
|
||||
import com.example.petstoremobile.repositories.CategoryRepository;
|
||||
import com.example.petstoremobile.repositories.ProductRepository;
|
||||
import com.example.petstoremobile.utils.Resource;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.List;
|
||||
|
||||
import javax.inject.Inject;
|
||||
|
||||
import dagger.hilt.android.lifecycle.HiltViewModel;
|
||||
import okhttp3.MultipartBody;
|
||||
|
||||
@HiltViewModel
|
||||
public class ProductDetailViewModel extends ViewModel {
|
||||
private final ProductRepository productRepository;
|
||||
private final CategoryRepository categoryRepository;
|
||||
|
||||
private final MutableLiveData<List<CategoryDTO>> categoryList = new MutableLiveData<>(new ArrayList<>());
|
||||
private long prodId = -1;
|
||||
private boolean isEditing = false;
|
||||
|
||||
@Inject
|
||||
public ProductDetailViewModel(ProductRepository productRepository, CategoryRepository categoryRepository) {
|
||||
this.productRepository = productRepository;
|
||||
this.categoryRepository = categoryRepository;
|
||||
}
|
||||
|
||||
public void setProdId(long id) {
|
||||
this.prodId = id;
|
||||
this.isEditing = id != -1;
|
||||
}
|
||||
|
||||
public long getProdId() {
|
||||
return prodId;
|
||||
}
|
||||
|
||||
public boolean isEditing() {
|
||||
return isEditing;
|
||||
}
|
||||
|
||||
public LiveData<Resource<PageResponse<CategoryDTO>>> loadCategories() {
|
||||
return categoryRepository.getAllCategories(0, 100);
|
||||
}
|
||||
|
||||
public LiveData<Resource<ProductDTO>> loadProduct() {
|
||||
return productRepository.getProductById(prodId);
|
||||
}
|
||||
|
||||
public LiveData<Resource<ProductDTO>> saveProduct(ProductDTO dto) {
|
||||
if (isEditing) {
|
||||
return productRepository.updateProduct(prodId, dto);
|
||||
} else {
|
||||
return productRepository.createProduct(dto);
|
||||
}
|
||||
}
|
||||
|
||||
public LiveData<Resource<Void>> deleteProduct() {
|
||||
return productRepository.deleteProduct(prodId);
|
||||
}
|
||||
|
||||
public LiveData<Resource<Void>> uploadProductImage(MultipartBody.Part image) {
|
||||
return productRepository.uploadProductImage(prodId, image);
|
||||
}
|
||||
|
||||
public LiveData<Resource<Void>> deleteProductImage() {
|
||||
return productRepository.deleteProductImage(prodId);
|
||||
}
|
||||
|
||||
public void setCategoryList(List<CategoryDTO> list) {
|
||||
categoryList.setValue(list);
|
||||
}
|
||||
|
||||
public LiveData<List<CategoryDTO>> getCategoryList() {
|
||||
return categoryList;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,60 @@
|
||||
package com.example.petstoremobile.viewmodels;
|
||||
|
||||
import androidx.lifecycle.LiveData;
|
||||
import androidx.lifecycle.MutableLiveData;
|
||||
import androidx.lifecycle.ViewModel;
|
||||
|
||||
import com.example.petstoremobile.dtos.CategoryDTO;
|
||||
import com.example.petstoremobile.dtos.ProductDTO;
|
||||
import com.example.petstoremobile.repositories.CategoryRepository;
|
||||
import com.example.petstoremobile.repositories.ProductRepository;
|
||||
import com.example.petstoremobile.utils.Resource;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.List;
|
||||
|
||||
import javax.inject.Inject;
|
||||
|
||||
import dagger.hilt.android.lifecycle.HiltViewModel;
|
||||
|
||||
@HiltViewModel
|
||||
public class ProductListViewModel extends ViewModel {
|
||||
private final ProductRepository productRepository;
|
||||
private final CategoryRepository categoryRepository;
|
||||
|
||||
private final MutableLiveData<List<ProductDTO>> products = new MutableLiveData<>(new ArrayList<>());
|
||||
private final MutableLiveData<List<CategoryDTO>> categories = new MutableLiveData<>(new ArrayList<>());
|
||||
private final MutableLiveData<Boolean> isLoading = new MutableLiveData<>(false);
|
||||
|
||||
@Inject
|
||||
public ProductListViewModel(ProductRepository productRepository, CategoryRepository categoryRepository) {
|
||||
this.productRepository = productRepository;
|
||||
this.categoryRepository = categoryRepository;
|
||||
}
|
||||
|
||||
public LiveData<List<ProductDTO>> getProducts() { return products; }
|
||||
public LiveData<List<CategoryDTO>> getCategories() { return categories; }
|
||||
public LiveData<Boolean> getIsLoading() { return isLoading; }
|
||||
|
||||
public void loadProducts(String query, Long categoryId) {
|
||||
isLoading.setValue(true);
|
||||
productRepository.getAllProducts(query, categoryId, 0, 100, "prodName").observeForever(resource -> {
|
||||
if (resource != null) {
|
||||
if (resource.status == Resource.Status.SUCCESS && resource.data != null) {
|
||||
products.setValue(resource.data.getContent());
|
||||
isLoading.setValue(false);
|
||||
} else if (resource.status == Resource.Status.ERROR) {
|
||||
isLoading.setValue(false);
|
||||
}
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
public void loadCategories() {
|
||||
categoryRepository.getAllCategories(0, 100).observeForever(resource -> {
|
||||
if (resource != null && resource.status == Resource.Status.SUCCESS && resource.data != null) {
|
||||
categories.setValue(resource.data.getContent());
|
||||
}
|
||||
});
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,78 @@
|
||||
package com.example.petstoremobile.viewmodels;
|
||||
|
||||
import androidx.lifecycle.LiveData;
|
||||
import androidx.lifecycle.MutableLiveData;
|
||||
import androidx.lifecycle.ViewModel;
|
||||
|
||||
import com.example.petstoremobile.dtos.PageResponse;
|
||||
import com.example.petstoremobile.dtos.ProductDTO;
|
||||
import com.example.petstoremobile.dtos.ProductSupplierDTO;
|
||||
import com.example.petstoremobile.dtos.SupplierDTO;
|
||||
import com.example.petstoremobile.repositories.ProductRepository;
|
||||
import com.example.petstoremobile.repositories.ProductSupplierRepository;
|
||||
import com.example.petstoremobile.repositories.SupplierRepository;
|
||||
import com.example.petstoremobile.utils.Resource;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.List;
|
||||
|
||||
import javax.inject.Inject;
|
||||
|
||||
import dagger.hilt.android.lifecycle.HiltViewModel;
|
||||
|
||||
@HiltViewModel
|
||||
public class ProductSupplierDetailViewModel extends ViewModel {
|
||||
private final ProductSupplierRepository psRepository;
|
||||
private final ProductRepository productRepository;
|
||||
private final SupplierRepository supplierRepository;
|
||||
|
||||
private boolean isEditing = false;
|
||||
private long editProductId = -1;
|
||||
private long editSupplierId = -1;
|
||||
|
||||
private final MutableLiveData<List<ProductDTO>> productList = new MutableLiveData<>(new ArrayList<>());
|
||||
private final MutableLiveData<List<SupplierDTO>> supplierList = new MutableLiveData<>(new ArrayList<>());
|
||||
|
||||
@Inject
|
||||
public ProductSupplierDetailViewModel(ProductSupplierRepository psRepository, ProductRepository productRepository, SupplierRepository supplierRepository) {
|
||||
this.psRepository = psRepository;
|
||||
this.productRepository = productRepository;
|
||||
this.supplierRepository = supplierRepository;
|
||||
}
|
||||
|
||||
public void setEditMode(long productId, long supplierId) {
|
||||
this.isEditing = true;
|
||||
this.editProductId = productId;
|
||||
this.editSupplierId = supplierId;
|
||||
}
|
||||
|
||||
public boolean isEditing() { return isEditing; }
|
||||
public long getEditProductId() { return editProductId; }
|
||||
public long getEditSupplierId() { return editSupplierId; }
|
||||
|
||||
public LiveData<Resource<PageResponse<ProductDTO>>> loadProducts() {
|
||||
return productRepository.getAllProducts(null, null, 0, 200, "prodName");
|
||||
}
|
||||
|
||||
public LiveData<Resource<PageResponse<SupplierDTO>>> loadSuppliers() {
|
||||
return supplierRepository.getAllSuppliers(0, 200, null, "supCompany");
|
||||
}
|
||||
|
||||
public LiveData<Resource<ProductSupplierDTO>> saveProductSupplier(ProductSupplierDTO dto) {
|
||||
if (isEditing) {
|
||||
return psRepository.updateProductSupplier(editProductId, editSupplierId, dto);
|
||||
} else {
|
||||
return psRepository.createProductSupplier(dto);
|
||||
}
|
||||
}
|
||||
|
||||
public LiveData<Resource<Void>> deleteProductSupplier() {
|
||||
return psRepository.deleteProductSupplier(editProductId, editSupplierId);
|
||||
}
|
||||
|
||||
public void setProductList(List<ProductDTO> list) { productList.setValue(list); }
|
||||
public LiveData<List<ProductDTO>> getProductList() { return productList; }
|
||||
|
||||
public void setSupplierList(List<SupplierDTO> list) { supplierList.setValue(list); }
|
||||
public LiveData<List<SupplierDTO>> getSupplierList() { return supplierList; }
|
||||
}
|
||||
@@ -0,0 +1,77 @@
|
||||
package com.example.petstoremobile.viewmodels;
|
||||
|
||||
import androidx.lifecycle.LiveData;
|
||||
import androidx.lifecycle.MutableLiveData;
|
||||
import androidx.lifecycle.ViewModel;
|
||||
|
||||
import com.example.petstoremobile.dtos.BulkDeleteRequest;
|
||||
import com.example.petstoremobile.dtos.ProductDTO;
|
||||
import com.example.petstoremobile.dtos.ProductSupplierDTO;
|
||||
import com.example.petstoremobile.dtos.SupplierDTO;
|
||||
import com.example.petstoremobile.repositories.ProductRepository;
|
||||
import com.example.petstoremobile.repositories.ProductSupplierRepository;
|
||||
import com.example.petstoremobile.repositories.SupplierRepository;
|
||||
import com.example.petstoremobile.utils.Resource;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.List;
|
||||
|
||||
import javax.inject.Inject;
|
||||
|
||||
import dagger.hilt.android.lifecycle.HiltViewModel;
|
||||
|
||||
@HiltViewModel
|
||||
public class ProductSupplierListViewModel extends ViewModel {
|
||||
private final ProductSupplierRepository psRepository;
|
||||
private final ProductRepository productRepository;
|
||||
private final SupplierRepository supplierRepository;
|
||||
|
||||
private final MutableLiveData<List<ProductSupplierDTO>> productSuppliers = new MutableLiveData<>(new ArrayList<>());
|
||||
private final MutableLiveData<List<ProductDTO>> products = new MutableLiveData<>(new ArrayList<>());
|
||||
private final MutableLiveData<List<SupplierDTO>> suppliers = new MutableLiveData<>(new ArrayList<>());
|
||||
private final MutableLiveData<Boolean> isLoading = new MutableLiveData<>(false);
|
||||
|
||||
@Inject
|
||||
public ProductSupplierListViewModel(ProductSupplierRepository psRepository, ProductRepository productRepository, SupplierRepository supplierRepository) {
|
||||
this.psRepository = psRepository;
|
||||
this.productRepository = productRepository;
|
||||
this.supplierRepository = supplierRepository;
|
||||
}
|
||||
|
||||
public LiveData<List<ProductSupplierDTO>> getProductSuppliers() { return productSuppliers; }
|
||||
public LiveData<List<ProductDTO>> getProducts() { return products; }
|
||||
public LiveData<List<SupplierDTO>> getSuppliers() { return suppliers; }
|
||||
public LiveData<Boolean> getIsLoading() { return isLoading; }
|
||||
|
||||
public void loadProductSuppliers(String query, Long productId, Long supplierId) {
|
||||
isLoading.setValue(true);
|
||||
psRepository.getAllProductSuppliers(0, 100, query, productId, supplierId, "productName").observeForever(resource -> {
|
||||
if (resource != null) {
|
||||
if (resource.status == Resource.Status.SUCCESS && resource.data != null) {
|
||||
productSuppliers.setValue(resource.data.getContent());
|
||||
isLoading.setValue(false);
|
||||
} else if (resource.status == Resource.Status.ERROR) {
|
||||
isLoading.setValue(false);
|
||||
}
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
public void loadFilterData() {
|
||||
productRepository.getAllProducts(null, null, 0, 100, "prodName").observeForever(resource -> {
|
||||
if (resource != null && resource.status == Resource.Status.SUCCESS && resource.data != null) {
|
||||
products.setValue(resource.data.getContent());
|
||||
}
|
||||
});
|
||||
|
||||
supplierRepository.getAllSuppliers(0, 100, null, "supCompany").observeForever(resource -> {
|
||||
if (resource != null && resource.status == Resource.Status.SUCCESS && resource.data != null) {
|
||||
suppliers.setValue(resource.data.getContent());
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
public LiveData<Resource<Void>> bulkDeleteProductSuppliers(List<String> ids) {
|
||||
return psRepository.bulkDeleteProductSuppliers(new BulkDeleteRequest(ids));
|
||||
}
|
||||
}
|
||||
@@ -1,58 +0,0 @@
|
||||
package com.example.petstoremobile.viewmodels;
|
||||
|
||||
import androidx.lifecycle.LiveData;
|
||||
import androidx.lifecycle.ViewModel;
|
||||
|
||||
import com.example.petstoremobile.dtos.BulkDeleteRequest;
|
||||
import com.example.petstoremobile.dtos.PageResponse;
|
||||
import com.example.petstoremobile.dtos.ProductSupplierDTO;
|
||||
import com.example.petstoremobile.repositories.ProductSupplierRepository;
|
||||
import com.example.petstoremobile.utils.Resource;
|
||||
|
||||
import java.util.List;
|
||||
|
||||
import javax.inject.Inject;
|
||||
|
||||
import dagger.hilt.android.lifecycle.HiltViewModel;
|
||||
|
||||
@HiltViewModel
|
||||
public class ProductSupplierViewModel extends ViewModel {
|
||||
private final ProductSupplierRepository repository;
|
||||
|
||||
@Inject
|
||||
public ProductSupplierViewModel(ProductSupplierRepository repository) {
|
||||
this.repository = repository;
|
||||
}
|
||||
|
||||
/**
|
||||
* Fetches a paginated list of all product-supplier relationships.
|
||||
*/
|
||||
public LiveData<Resource<PageResponse<ProductSupplierDTO>>> getAllProductSuppliers(int page, int size, String query, Long productId, Long supplierId, String sort) {
|
||||
return repository.getAllProductSuppliers(page, size, query, productId, supplierId, sort);
|
||||
}
|
||||
|
||||
/**
|
||||
* Creates a new product-supplier relationship.
|
||||
*/
|
||||
public LiveData<Resource<ProductSupplierDTO>> createProductSupplier(ProductSupplierDTO dto) {
|
||||
return repository.createProductSupplier(dto);
|
||||
}
|
||||
|
||||
/**
|
||||
* Updates an existing product-supplier relationship.
|
||||
*/
|
||||
public LiveData<Resource<ProductSupplierDTO>> updateProductSupplier(Long productId, Long supplierId, ProductSupplierDTO dto) {
|
||||
return repository.updateProductSupplier(productId, supplierId, dto);
|
||||
}
|
||||
|
||||
/**
|
||||
* Deletes a product-supplier relationship by product and supplier IDs.
|
||||
*/
|
||||
public LiveData<Resource<Void>> deleteProductSupplier(Long productId, Long supplierId) {
|
||||
return repository.deleteProductSupplier(productId, supplierId);
|
||||
}
|
||||
|
||||
public LiveData<Resource<Void>> bulkDeleteProductSuppliers(List<String> ids) {
|
||||
return repository.bulkDeleteProductSuppliers(new BulkDeleteRequest(ids));
|
||||
}
|
||||
}
|
||||
@@ -1,84 +0,0 @@
|
||||
package com.example.petstoremobile.viewmodels;
|
||||
|
||||
import androidx.lifecycle.LiveData;
|
||||
import androidx.lifecycle.ViewModel;
|
||||
|
||||
import com.example.petstoremobile.dtos.CategoryDTO;
|
||||
import com.example.petstoremobile.dtos.PageResponse;
|
||||
import com.example.petstoremobile.dtos.ProductDTO;
|
||||
import com.example.petstoremobile.repositories.CategoryRepository;
|
||||
import com.example.petstoremobile.repositories.ProductRepository;
|
||||
import com.example.petstoremobile.utils.Resource;
|
||||
|
||||
import javax.inject.Inject;
|
||||
|
||||
import dagger.hilt.android.lifecycle.HiltViewModel;
|
||||
import okhttp3.MultipartBody;
|
||||
|
||||
@HiltViewModel
|
||||
public class ProductViewModel extends ViewModel {
|
||||
private final ProductRepository productRepository;
|
||||
private final CategoryRepository categoryRepository;
|
||||
|
||||
@Inject
|
||||
public ProductViewModel(ProductRepository productRepository, CategoryRepository categoryRepository) {
|
||||
this.productRepository = productRepository;
|
||||
this.categoryRepository = categoryRepository;
|
||||
}
|
||||
|
||||
/**
|
||||
* Retrieves a paginated list of products, optionally filtered by a query string, category and sorted.
|
||||
*/
|
||||
public LiveData<Resource<PageResponse<ProductDTO>>> getAllProducts(String query, Long categoryId, int page, int size, String sort) {
|
||||
return productRepository.getAllProducts(query, categoryId, page, size, sort);
|
||||
}
|
||||
|
||||
/**
|
||||
* Retrieves a single product by its ID.
|
||||
*/
|
||||
public LiveData<Resource<ProductDTO>> getProductById(Long id) {
|
||||
return productRepository.getProductById(id);
|
||||
}
|
||||
|
||||
/**
|
||||
* Creates a new product.
|
||||
*/
|
||||
public LiveData<Resource<ProductDTO>> createProduct(ProductDTO product) {
|
||||
return productRepository.createProduct(product);
|
||||
}
|
||||
|
||||
/**
|
||||
* Updates an existing product by ID.
|
||||
*/
|
||||
public LiveData<Resource<ProductDTO>> updateProduct(Long id, ProductDTO product) {
|
||||
return productRepository.updateProduct(id, product);
|
||||
}
|
||||
|
||||
/**
|
||||
* Deletes a product by its ID.
|
||||
*/
|
||||
public LiveData<Resource<Void>> deleteProduct(Long id) {
|
||||
return productRepository.deleteProduct(id);
|
||||
}
|
||||
|
||||
/**
|
||||
* Uploads an image for a specific product.
|
||||
*/
|
||||
public LiveData<Resource<Void>> uploadProductImage(Long id, MultipartBody.Part image) {
|
||||
return productRepository.uploadProductImage(id, image);
|
||||
}
|
||||
|
||||
/**
|
||||
* Deletes the image associated with a specific product.
|
||||
*/
|
||||
public LiveData<Resource<Void>> deleteProductImage(Long id) {
|
||||
return productRepository.deleteProductImage(id);
|
||||
}
|
||||
|
||||
/**
|
||||
* Retrieves a paginated list of all product categories.
|
||||
*/
|
||||
public LiveData<Resource<PageResponse<CategoryDTO>>> getAllCategories(int page, int size) {
|
||||
return categoryRepository.getAllCategories(page, size);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,26 @@
|
||||
package com.example.petstoremobile.viewmodels;
|
||||
|
||||
import androidx.lifecycle.LiveData;
|
||||
import androidx.lifecycle.ViewModel;
|
||||
|
||||
import com.example.petstoremobile.dtos.PurchaseOrderDTO;
|
||||
import com.example.petstoremobile.repositories.PurchaseOrderRepository;
|
||||
import com.example.petstoremobile.utils.Resource;
|
||||
|
||||
import javax.inject.Inject;
|
||||
|
||||
import dagger.hilt.android.lifecycle.HiltViewModel;
|
||||
|
||||
@HiltViewModel
|
||||
public class PurchaseOrderDetailViewModel extends ViewModel {
|
||||
private final PurchaseOrderRepository repository;
|
||||
|
||||
@Inject
|
||||
public PurchaseOrderDetailViewModel(PurchaseOrderRepository repository) {
|
||||
this.repository = repository;
|
||||
}
|
||||
|
||||
public LiveData<Resource<PurchaseOrderDTO>> loadPurchaseOrder(long id) {
|
||||
return repository.getPurchaseOrderById(id);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,60 @@
|
||||
package com.example.petstoremobile.viewmodels;
|
||||
|
||||
import androidx.lifecycle.LiveData;
|
||||
import androidx.lifecycle.MutableLiveData;
|
||||
import androidx.lifecycle.ViewModel;
|
||||
|
||||
import com.example.petstoremobile.dtos.PurchaseOrderDTO;
|
||||
import com.example.petstoremobile.dtos.StoreDTO;
|
||||
import com.example.petstoremobile.repositories.PurchaseOrderRepository;
|
||||
import com.example.petstoremobile.repositories.StoreRepository;
|
||||
import com.example.petstoremobile.utils.Resource;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.List;
|
||||
|
||||
import javax.inject.Inject;
|
||||
|
||||
import dagger.hilt.android.lifecycle.HiltViewModel;
|
||||
|
||||
@HiltViewModel
|
||||
public class PurchaseOrderListViewModel extends ViewModel {
|
||||
private final PurchaseOrderRepository purchaseOrderRepository;
|
||||
private final StoreRepository storeRepository;
|
||||
|
||||
private final MutableLiveData<List<PurchaseOrderDTO>> purchaseOrders = new MutableLiveData<>(new ArrayList<>());
|
||||
private final MutableLiveData<List<StoreDTO>> stores = new MutableLiveData<>(new ArrayList<>());
|
||||
private final MutableLiveData<Boolean> isLoading = new MutableLiveData<>(false);
|
||||
|
||||
@Inject
|
||||
public PurchaseOrderListViewModel(PurchaseOrderRepository purchaseOrderRepository, StoreRepository storeRepository) {
|
||||
this.purchaseOrderRepository = purchaseOrderRepository;
|
||||
this.storeRepository = storeRepository;
|
||||
}
|
||||
|
||||
public LiveData<List<PurchaseOrderDTO>> getPurchaseOrders() { return purchaseOrders; }
|
||||
public LiveData<List<StoreDTO>> getStores() { return stores; }
|
||||
public LiveData<Boolean> getIsLoading() { return isLoading; }
|
||||
|
||||
public void loadPurchaseOrders(String query, Long storeId) {
|
||||
isLoading.setValue(true);
|
||||
purchaseOrderRepository.getAllPurchaseOrders(0, 100, query, storeId, "purchaseOrderId,desc").observeForever(resource -> {
|
||||
if (resource != null) {
|
||||
if (resource.status == Resource.Status.SUCCESS && resource.data != null) {
|
||||
purchaseOrders.setValue(resource.data.getContent());
|
||||
isLoading.setValue(false);
|
||||
} else if (resource.status == Resource.Status.ERROR) {
|
||||
isLoading.setValue(false);
|
||||
}
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
public void loadStores() {
|
||||
storeRepository.getAllStores(0, 100).observeForever(resource -> {
|
||||
if (resource != null && resource.status == Resource.Status.SUCCESS && resource.data != null) {
|
||||
stores.setValue(resource.data.getContent());
|
||||
}
|
||||
});
|
||||
}
|
||||
}
|
||||
@@ -1,37 +0,0 @@
|
||||
package com.example.petstoremobile.viewmodels;
|
||||
|
||||
import androidx.lifecycle.LiveData;
|
||||
import androidx.lifecycle.ViewModel;
|
||||
|
||||
import com.example.petstoremobile.dtos.PageResponse;
|
||||
import com.example.petstoremobile.dtos.PurchaseOrderDTO;
|
||||
import com.example.petstoremobile.repositories.PurchaseOrderRepository;
|
||||
import com.example.petstoremobile.utils.Resource;
|
||||
|
||||
import javax.inject.Inject;
|
||||
|
||||
import dagger.hilt.android.lifecycle.HiltViewModel;
|
||||
|
||||
@HiltViewModel
|
||||
public class PurchaseOrderViewModel extends ViewModel {
|
||||
private final PurchaseOrderRepository repository;
|
||||
|
||||
@Inject
|
||||
public PurchaseOrderViewModel(PurchaseOrderRepository repository) {
|
||||
this.repository = repository;
|
||||
}
|
||||
|
||||
/**
|
||||
* Fetches a paginated list of all purchase orders.
|
||||
*/
|
||||
public LiveData<Resource<PageResponse<PurchaseOrderDTO>>> getAllPurchaseOrders(int page, int size, String query, Long storeId, String sort) {
|
||||
return repository.getAllPurchaseOrders(page, size, query, storeId, sort);
|
||||
}
|
||||
|
||||
/**
|
||||
* Retrieves a single purchase order by its ID.
|
||||
*/
|
||||
public LiveData<Resource<PurchaseOrderDTO>> getPurchaseOrderById(Long id) {
|
||||
return repository.getPurchaseOrderById(id);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,167 @@
|
||||
package com.example.petstoremobile.viewmodels;
|
||||
|
||||
import androidx.lifecycle.LiveData;
|
||||
import androidx.lifecycle.MutableLiveData;
|
||||
import androidx.lifecycle.ViewModel;
|
||||
|
||||
import com.example.petstoremobile.dtos.PageResponse;
|
||||
import com.example.petstoremobile.dtos.SaleDTO;
|
||||
import com.example.petstoremobile.repositories.SaleRepository;
|
||||
import com.example.petstoremobile.utils.Resource;
|
||||
|
||||
import java.math.BigDecimal;
|
||||
import java.util.ArrayList;
|
||||
import java.util.HashMap;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
|
||||
import javax.inject.Inject;
|
||||
|
||||
import dagger.hilt.android.lifecycle.HiltViewModel;
|
||||
|
||||
@HiltViewModel
|
||||
public class RefundViewModel extends ViewModel {
|
||||
private final SaleRepository saleRepository;
|
||||
|
||||
private final MutableLiveData<List<SaleDTO>> allSales = new MutableLiveData<>(new ArrayList<>());
|
||||
private final MutableLiveData<SaleDTO> currentSale = new MutableLiveData<>();
|
||||
private final MutableLiveData<List<RefundItem>> availableItems = new MutableLiveData<>(new ArrayList<>());
|
||||
private final MutableLiveData<List<RefundItem>> refundCart = new MutableLiveData<>(new ArrayList<>());
|
||||
|
||||
@Inject
|
||||
public RefundViewModel(SaleRepository saleRepository) {
|
||||
this.saleRepository = saleRepository;
|
||||
}
|
||||
|
||||
public LiveData<Resource<PageResponse<SaleDTO>>> loadAllSales() {
|
||||
return saleRepository.getAllSales(0, 1000, null, null, null, "saleDate,desc");
|
||||
}
|
||||
|
||||
public void setAllSales(List<SaleDTO> sales) {
|
||||
allSales.setValue(sales);
|
||||
}
|
||||
|
||||
public List<SaleDTO> getAllSalesList() {
|
||||
return allSales.getValue();
|
||||
}
|
||||
|
||||
public void setCurrentSale(SaleDTO sale) {
|
||||
currentSale.setValue(sale);
|
||||
buildRefundableItems();
|
||||
}
|
||||
|
||||
public SaleDTO getCurrentSale() {
|
||||
return currentSale.getValue();
|
||||
}
|
||||
|
||||
public LiveData<List<RefundItem>> getAvailableItems() {
|
||||
return availableItems;
|
||||
}
|
||||
|
||||
public LiveData<List<RefundItem>> getRefundCart() {
|
||||
return refundCart;
|
||||
}
|
||||
|
||||
private void buildRefundableItems() {
|
||||
SaleDTO sale = currentSale.getValue();
|
||||
List<SaleDTO> sales = allSales.getValue();
|
||||
if (sale == null || sales == null || sale.getItems() == null) {
|
||||
availableItems.setValue(new ArrayList<>());
|
||||
return;
|
||||
}
|
||||
|
||||
Map<Long, Integer> alreadyRefunded = new HashMap<>();
|
||||
for (SaleDTO s : sales) {
|
||||
if (Boolean.TRUE.equals(s.getIsRefund())
|
||||
&& sale.getSaleId().equals(s.getOriginalSaleId())
|
||||
&& s.getItems() != null) {
|
||||
for (SaleDTO.SaleItemDTO item : s.getItems()) {
|
||||
if (item.getProdId() != null && item.getQuantity() != null) {
|
||||
alreadyRefunded.merge(item.getProdId(),
|
||||
Math.abs(item.getQuantity()), Integer::sum);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
List<RefundItem> items = new ArrayList<>();
|
||||
for (SaleDTO.SaleItemDTO item : sale.getItems()) {
|
||||
if (item.getProdId() == null || item.getQuantity() == null) continue;
|
||||
int refunded = alreadyRefunded.getOrDefault(item.getProdId(), 0);
|
||||
int remaining = item.getQuantity() - refunded;
|
||||
if (remaining > 0) {
|
||||
items.add(new RefundItem(
|
||||
item.getProdId(),
|
||||
item.getProductName() != null ? item.getProductName() : "Unknown",
|
||||
remaining,
|
||||
item.getUnitPrice()
|
||||
));
|
||||
}
|
||||
}
|
||||
availableItems.setValue(items);
|
||||
refundCart.setValue(new ArrayList<>());
|
||||
}
|
||||
|
||||
public void addToCart(RefundItem item, int qty) {
|
||||
List<RefundItem> cart = new ArrayList<>(refundCart.getValue());
|
||||
boolean merged = false;
|
||||
for (int i = 0; i < cart.size(); i++) {
|
||||
if (cart.get(i).prodId == item.prodId) {
|
||||
RefundItem existing = cart.get(i);
|
||||
cart.set(i, new RefundItem(existing.prodId, existing.productName, existing.quantity + qty, existing.unitPrice));
|
||||
merged = true;
|
||||
break;
|
||||
}
|
||||
}
|
||||
if (!merged) {
|
||||
cart.add(new RefundItem(item.prodId, item.productName, qty, item.unitPrice));
|
||||
}
|
||||
refundCart.setValue(cart);
|
||||
}
|
||||
|
||||
public void removeFromCart(RefundItem item) {
|
||||
List<RefundItem> cart = new ArrayList<>(refundCart.getValue());
|
||||
cart.remove(item);
|
||||
refundCart.setValue(cart);
|
||||
}
|
||||
|
||||
public LiveData<Resource<SaleDTO>> submitRefund(String paymentMethod) {
|
||||
SaleDTO sale = currentSale.getValue();
|
||||
List<RefundItem> cart = refundCart.getValue();
|
||||
if (sale == null || cart == null || cart.isEmpty()) return null;
|
||||
|
||||
List<SaleDTO.SaleItemDTO> items = new ArrayList<>();
|
||||
for (RefundItem item : cart) {
|
||||
items.add(new SaleDTO.SaleItemDTO(item.prodId, -item.quantity));
|
||||
}
|
||||
|
||||
SaleDTO dto = new SaleDTO(
|
||||
sale.getStoreId(),
|
||||
paymentMethod,
|
||||
items,
|
||||
true,
|
||||
sale.getSaleId(),
|
||||
null
|
||||
);
|
||||
|
||||
return saleRepository.createSale(dto);
|
||||
}
|
||||
|
||||
public static class RefundItem {
|
||||
public long prodId;
|
||||
public String productName;
|
||||
public int quantity;
|
||||
public BigDecimal unitPrice;
|
||||
|
||||
public RefundItem(long prodId, String productName, int quantity, BigDecimal unitPrice) {
|
||||
this.prodId = prodId;
|
||||
this.productName = productName;
|
||||
this.quantity = quantity;
|
||||
this.unitPrice = unitPrice;
|
||||
}
|
||||
|
||||
public BigDecimal getTotal() {
|
||||
return unitPrice != null ? unitPrice.multiply(BigDecimal.valueOf(quantity)) : BigDecimal.ZERO;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,118 @@
|
||||
package com.example.petstoremobile.viewmodels;
|
||||
|
||||
import androidx.lifecycle.LiveData;
|
||||
import androidx.lifecycle.MutableLiveData;
|
||||
import androidx.lifecycle.ViewModel;
|
||||
|
||||
import com.example.petstoremobile.dtos.DropdownDTO;
|
||||
import com.example.petstoremobile.dtos.ProductDTO;
|
||||
import com.example.petstoremobile.dtos.SaleDTO;
|
||||
import com.example.petstoremobile.repositories.CustomerRepository;
|
||||
import com.example.petstoremobile.repositories.ProductRepository;
|
||||
import com.example.petstoremobile.repositories.SaleRepository;
|
||||
import com.example.petstoremobile.repositories.StoreRepository;
|
||||
import com.example.petstoremobile.utils.Resource;
|
||||
|
||||
import java.math.BigDecimal;
|
||||
import java.util.ArrayList;
|
||||
import java.util.List;
|
||||
|
||||
import javax.inject.Inject;
|
||||
|
||||
import dagger.hilt.android.lifecycle.HiltViewModel;
|
||||
|
||||
@HiltViewModel
|
||||
public class SaleDetailViewModel extends ViewModel {
|
||||
private final SaleRepository saleRepository;
|
||||
private final StoreRepository storeRepository;
|
||||
private final CustomerRepository customerRepository;
|
||||
private final ProductRepository productRepository;
|
||||
|
||||
private long saleId = -1;
|
||||
private boolean viewOnly = false;
|
||||
|
||||
private final MutableLiveData<List<DropdownDTO>> storeList = new MutableLiveData<>(new ArrayList<>());
|
||||
private final MutableLiveData<List<DropdownDTO>> customerList = new MutableLiveData<>(new ArrayList<>());
|
||||
private final MutableLiveData<List<ProductDTO>> productList = new MutableLiveData<>(new ArrayList<>());
|
||||
private final MutableLiveData<List<SaleDTO.SaleItemDTO>> cartItems = new MutableLiveData<>(new ArrayList<>());
|
||||
private final MutableLiveData<Boolean> isLoading = new MutableLiveData<>(false);
|
||||
|
||||
@Inject
|
||||
public SaleDetailViewModel(SaleRepository saleRepository, StoreRepository storeRepository,
|
||||
CustomerRepository customerRepository, ProductRepository productRepository) {
|
||||
this.saleRepository = saleRepository;
|
||||
this.storeRepository = storeRepository;
|
||||
this.customerRepository = customerRepository;
|
||||
this.productRepository = productRepository;
|
||||
}
|
||||
|
||||
public void setSaleId(long id, boolean viewOnly) {
|
||||
this.saleId = id;
|
||||
this.viewOnly = viewOnly;
|
||||
}
|
||||
|
||||
public long getSaleId() { return saleId; }
|
||||
public boolean isViewOnly() { return viewOnly; }
|
||||
|
||||
public LiveData<Resource<SaleDTO>> loadSaleDetails() {
|
||||
return saleRepository.getSaleById(saleId);
|
||||
}
|
||||
|
||||
public LiveData<Resource<List<DropdownDTO>>> loadStores() {
|
||||
return storeRepository.getStoreDropdowns();
|
||||
}
|
||||
|
||||
public LiveData<Resource<List<DropdownDTO>>> loadCustomers() {
|
||||
return customerRepository.getCustomerDropdowns();
|
||||
}
|
||||
|
||||
public LiveData<Resource<com.example.petstoremobile.dtos.PageResponse<ProductDTO>>> loadProducts() {
|
||||
return productRepository.getAllProducts(null, null, 0, 200, null);
|
||||
}
|
||||
|
||||
public LiveData<Resource<SaleDTO>> createSale(SaleDTO sale) {
|
||||
return saleRepository.createSale(sale);
|
||||
}
|
||||
|
||||
public void setStoreList(List<DropdownDTO> list) { storeList.setValue(list); }
|
||||
public LiveData<List<DropdownDTO>> getStoreList() { return storeList; }
|
||||
|
||||
public void setCustomerList(List<DropdownDTO> list) { customerList.setValue(list); }
|
||||
public LiveData<List<DropdownDTO>> getCustomerList() { return customerList; }
|
||||
|
||||
public void setProductList(List<ProductDTO> list) { productList.setValue(list); }
|
||||
public LiveData<List<ProductDTO>> getProductList() { return productList; }
|
||||
|
||||
public void addToCart(SaleDTO.SaleItemDTO item) {
|
||||
List<SaleDTO.SaleItemDTO> currentCart = new ArrayList<>(cartItems.getValue());
|
||||
currentCart.add(item);
|
||||
cartItems.setValue(currentCart);
|
||||
}
|
||||
|
||||
public LiveData<List<SaleDTO.SaleItemDTO>> getCartItems() { return cartItems; }
|
||||
|
||||
public BigDecimal calculateSubtotal() {
|
||||
BigDecimal total = BigDecimal.ZERO;
|
||||
List<SaleDTO.SaleItemDTO> items = cartItems.getValue();
|
||||
List<ProductDTO> products = productList.getValue();
|
||||
if (items != null && products != null) {
|
||||
for (SaleDTO.SaleItemDTO item : items) {
|
||||
for (ProductDTO p : products) {
|
||||
if (p.getProdId().equals(item.getProdId()) && p.getProdPrice() != null) {
|
||||
total = total.add(p.getProdPrice().multiply(BigDecimal.valueOf(item.getQuantity())));
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
return total;
|
||||
}
|
||||
|
||||
public LiveData<Boolean> getIsLoading() {
|
||||
return isLoading;
|
||||
}
|
||||
|
||||
public void setLoading(boolean loading) {
|
||||
isLoading.setValue(loading);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,77 @@
|
||||
package com.example.petstoremobile.viewmodels;
|
||||
|
||||
import androidx.lifecycle.LiveData;
|
||||
import androidx.lifecycle.MutableLiveData;
|
||||
import androidx.lifecycle.ViewModel;
|
||||
|
||||
import com.example.petstoremobile.dtos.PageResponse;
|
||||
import com.example.petstoremobile.dtos.SaleDTO;
|
||||
import com.example.petstoremobile.dtos.StoreDTO;
|
||||
import com.example.petstoremobile.repositories.SaleRepository;
|
||||
import com.example.petstoremobile.repositories.StoreRepository;
|
||||
import com.example.petstoremobile.utils.Resource;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.List;
|
||||
|
||||
import javax.inject.Inject;
|
||||
|
||||
import dagger.hilt.android.lifecycle.HiltViewModel;
|
||||
|
||||
@HiltViewModel
|
||||
public class SaleListViewModel extends ViewModel {
|
||||
private final SaleRepository saleRepository;
|
||||
private final StoreRepository storeRepository;
|
||||
|
||||
private final MutableLiveData<List<SaleDTO>> sales = new MutableLiveData<>(new ArrayList<>());
|
||||
private final MutableLiveData<List<StoreDTO>> stores = new MutableLiveData<>(new ArrayList<>());
|
||||
private final MutableLiveData<Boolean> isLoading = new MutableLiveData<>(false);
|
||||
|
||||
private int currentPage = 0;
|
||||
private boolean isLastPage = false;
|
||||
private static final int PAGE_SIZE = 20;
|
||||
|
||||
@Inject
|
||||
public SaleListViewModel(SaleRepository saleRepository, StoreRepository storeRepository) {
|
||||
this.saleRepository = saleRepository;
|
||||
this.storeRepository = storeRepository;
|
||||
}
|
||||
|
||||
public LiveData<List<SaleDTO>> getSales() { return sales; }
|
||||
public LiveData<List<StoreDTO>> getStores() { return stores; }
|
||||
public LiveData<Boolean> getIsLoading() { return isLoading; }
|
||||
public boolean isLastPage() { return isLastPage; }
|
||||
|
||||
public void loadSales(boolean reset, String query, String paymentMethod, Long storeId) {
|
||||
if (isLoading.getValue() != null && isLoading.getValue() && !reset) return;
|
||||
|
||||
if (reset) {
|
||||
currentPage = 0;
|
||||
isLastPage = false;
|
||||
}
|
||||
|
||||
isLoading.setValue(true);
|
||||
saleRepository.getAllSales(currentPage, PAGE_SIZE, query, paymentMethod, storeId, "saleDate,desc").observeForever(resource -> {
|
||||
if (resource != null) {
|
||||
if (resource.status == Resource.Status.SUCCESS && resource.data != null) {
|
||||
List<SaleDTO> currentList = reset ? new ArrayList<>() : new ArrayList<>(sales.getValue());
|
||||
currentList.addAll(resource.data.getContent());
|
||||
sales.setValue(currentList);
|
||||
isLastPage = resource.data.isLast();
|
||||
if (!isLastPage) currentPage++;
|
||||
isLoading.setValue(false);
|
||||
} else if (resource.status == Resource.Status.ERROR) {
|
||||
isLoading.setValue(false);
|
||||
}
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
public void loadStores() {
|
||||
storeRepository.getAllStores(0, 100).observeForever(resource -> {
|
||||
if (resource != null && resource.status == Resource.Status.SUCCESS && resource.data != null) {
|
||||
stores.setValue(resource.data.getContent());
|
||||
}
|
||||
});
|
||||
}
|
||||
}
|
||||
@@ -1,35 +0,0 @@
|
||||
package com.example.petstoremobile.viewmodels;
|
||||
|
||||
import androidx.lifecycle.LiveData;
|
||||
import androidx.lifecycle.ViewModel;
|
||||
|
||||
import com.example.petstoremobile.dtos.PageResponse;
|
||||
import com.example.petstoremobile.dtos.SaleDTO;
|
||||
import com.example.petstoremobile.repositories.SaleRepository;
|
||||
import com.example.petstoremobile.utils.Resource;
|
||||
|
||||
import javax.inject.Inject;
|
||||
|
||||
import dagger.hilt.android.lifecycle.HiltViewModel;
|
||||
|
||||
@HiltViewModel
|
||||
public class SaleViewModel extends ViewModel {
|
||||
private final SaleRepository saleRepository;
|
||||
|
||||
@Inject
|
||||
public SaleViewModel(SaleRepository saleRepository) {
|
||||
this.saleRepository = saleRepository;
|
||||
}
|
||||
|
||||
public LiveData<Resource<PageResponse<SaleDTO>>> getAllSales(int page, int size, String query, String paymentMethod, Long storeId, String sortBy) {
|
||||
return saleRepository.getAllSales(page, size, query, paymentMethod, storeId, sortBy);
|
||||
}
|
||||
|
||||
public LiveData<Resource<SaleDTO>> getSaleById(Long id) {
|
||||
return saleRepository.getSaleById(id);
|
||||
}
|
||||
|
||||
public LiveData<Resource<SaleDTO>> createSale(SaleDTO sale) {
|
||||
return saleRepository.createSale(sale);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,54 @@
|
||||
package com.example.petstoremobile.viewmodels;
|
||||
|
||||
import androidx.lifecycle.LiveData;
|
||||
import androidx.lifecycle.ViewModel;
|
||||
|
||||
import com.example.petstoremobile.dtos.ServiceDTO;
|
||||
import com.example.petstoremobile.repositories.ServiceRepository;
|
||||
import com.example.petstoremobile.utils.Resource;
|
||||
|
||||
import javax.inject.Inject;
|
||||
|
||||
import dagger.hilt.android.lifecycle.HiltViewModel;
|
||||
|
||||
@HiltViewModel
|
||||
public class ServiceDetailViewModel extends ViewModel {
|
||||
private final ServiceRepository repository;
|
||||
private long serviceId = -1;
|
||||
private boolean isEditing = false;
|
||||
|
||||
@Inject
|
||||
public ServiceDetailViewModel(ServiceRepository repository) {
|
||||
this.repository = repository;
|
||||
}
|
||||
|
||||
public void setServiceId(long id) {
|
||||
this.serviceId = id;
|
||||
this.isEditing = id != -1;
|
||||
}
|
||||
|
||||
public long getServiceId() {
|
||||
return serviceId;
|
||||
}
|
||||
|
||||
public boolean isEditing() {
|
||||
return isEditing;
|
||||
}
|
||||
|
||||
public LiveData<Resource<ServiceDTO>> loadService() {
|
||||
return repository.getServiceById(serviceId);
|
||||
}
|
||||
|
||||
public LiveData<Resource<ServiceDTO>> saveService(ServiceDTO dto) {
|
||||
if (isEditing) {
|
||||
dto.setServiceId(serviceId);
|
||||
return repository.updateService(serviceId, dto);
|
||||
} else {
|
||||
return repository.createService(dto);
|
||||
}
|
||||
}
|
||||
|
||||
public LiveData<Resource<Void>> deleteService() {
|
||||
return repository.deleteService(serviceId);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,68 @@
|
||||
package com.example.petstoremobile.viewmodels;
|
||||
|
||||
import androidx.lifecycle.LiveData;
|
||||
import androidx.lifecycle.MutableLiveData;
|
||||
import androidx.lifecycle.ViewModel;
|
||||
|
||||
import com.example.petstoremobile.dtos.BulkDeleteRequest;
|
||||
import com.example.petstoremobile.dtos.PageResponse;
|
||||
import com.example.petstoremobile.dtos.ServiceDTO;
|
||||
import com.example.petstoremobile.repositories.ServiceRepository;
|
||||
import com.example.petstoremobile.utils.Resource;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.List;
|
||||
|
||||
import javax.inject.Inject;
|
||||
|
||||
import dagger.hilt.android.lifecycle.HiltViewModel;
|
||||
|
||||
@HiltViewModel
|
||||
public class ServiceListViewModel extends ViewModel {
|
||||
private final ServiceRepository repository;
|
||||
|
||||
private final MutableLiveData<List<ServiceDTO>> services = new MutableLiveData<>(new ArrayList<>());
|
||||
private final MutableLiveData<Boolean> isLoading = new MutableLiveData<>(false);
|
||||
|
||||
private int currentPage = 0;
|
||||
private boolean isLastPage = false;
|
||||
private static final int PAGE_SIZE = 20;
|
||||
|
||||
@Inject
|
||||
public ServiceListViewModel(ServiceRepository repository) {
|
||||
this.repository = repository;
|
||||
}
|
||||
|
||||
public LiveData<List<ServiceDTO>> getServices() { return services; }
|
||||
public LiveData<Boolean> getIsLoading() { return isLoading; }
|
||||
public boolean isLastPage() { return isLastPage; }
|
||||
|
||||
public void loadServices(boolean reset, String query) {
|
||||
if (isLoading.getValue() != null && isLoading.getValue() && !reset) return;
|
||||
|
||||
if (reset) {
|
||||
currentPage = 0;
|
||||
isLastPage = false;
|
||||
}
|
||||
|
||||
isLoading.setValue(true);
|
||||
repository.getAllServices(currentPage, PAGE_SIZE, query, "serviceName").observeForever(resource -> {
|
||||
if (resource != null) {
|
||||
if (resource.status == Resource.Status.SUCCESS && resource.data != null) {
|
||||
List<ServiceDTO> currentList = reset ? new ArrayList<>() : new ArrayList<>(services.getValue());
|
||||
currentList.addAll(resource.data.getContent());
|
||||
services.setValue(currentList);
|
||||
isLastPage = resource.data.isLast();
|
||||
if (!isLastPage) currentPage++;
|
||||
isLoading.setValue(false);
|
||||
} else if (resource.status == Resource.Status.ERROR) {
|
||||
isLoading.setValue(false);
|
||||
}
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
public LiveData<Resource<Void>> bulkDeleteServices(List<String> ids) {
|
||||
return repository.bulkDeleteServices(new BulkDeleteRequest(ids));
|
||||
}
|
||||
}
|
||||
@@ -1,68 +0,0 @@
|
||||
package com.example.petstoremobile.viewmodels;
|
||||
|
||||
import androidx.lifecycle.LiveData;
|
||||
import androidx.lifecycle.ViewModel;
|
||||
|
||||
import com.example.petstoremobile.dtos.BulkDeleteRequest;
|
||||
import com.example.petstoremobile.dtos.PageResponse;
|
||||
import com.example.petstoremobile.dtos.ServiceDTO;
|
||||
import com.example.petstoremobile.repositories.ServiceRepository;
|
||||
import com.example.petstoremobile.utils.Resource;
|
||||
|
||||
import java.util.List;
|
||||
|
||||
import javax.inject.Inject;
|
||||
|
||||
import dagger.hilt.android.lifecycle.HiltViewModel;
|
||||
|
||||
@HiltViewModel
|
||||
public class ServiceViewModel extends ViewModel {
|
||||
private final ServiceRepository repository;
|
||||
|
||||
@Inject
|
||||
public ServiceViewModel(ServiceRepository repository) {
|
||||
this.repository = repository;
|
||||
}
|
||||
|
||||
/**
|
||||
* Fetches a paginated list of all services.
|
||||
*/
|
||||
public LiveData<Resource<PageResponse<ServiceDTO>>> getAllServices(int page, int size, String query, String sort) {
|
||||
return repository.getAllServices(page, size, query, sort);
|
||||
}
|
||||
|
||||
/**
|
||||
* Retrieves a single service by its ID.
|
||||
*/
|
||||
public LiveData<Resource<ServiceDTO>> getServiceById(Long id) {
|
||||
return repository.getServiceById(id);
|
||||
}
|
||||
|
||||
/**
|
||||
* Creates a new service.
|
||||
*/
|
||||
public LiveData<Resource<ServiceDTO>> createService(ServiceDTO service) {
|
||||
return repository.createService(service);
|
||||
}
|
||||
|
||||
/**
|
||||
* Updates an existing service by ID.
|
||||
*/
|
||||
public LiveData<Resource<ServiceDTO>> updateService(Long id, ServiceDTO service) {
|
||||
return repository.updateService(id, service);
|
||||
}
|
||||
|
||||
/**
|
||||
* Deletes a service by ID.
|
||||
*/
|
||||
public LiveData<Resource<Void>> deleteService(Long id) {
|
||||
return repository.deleteService(id);
|
||||
}
|
||||
|
||||
/**
|
||||
* Deletes multiple services.
|
||||
*/
|
||||
public LiveData<Resource<Void>> bulkDeleteServices(List<String> ids) {
|
||||
return repository.bulkDeleteServices(new BulkDeleteRequest(ids));
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,49 @@
|
||||
package com.example.petstoremobile.viewmodels;
|
||||
|
||||
import androidx.lifecycle.LiveData;
|
||||
import androidx.lifecycle.ViewModel;
|
||||
|
||||
import com.example.petstoremobile.dtos.EmployeeDTO;
|
||||
import com.example.petstoremobile.repositories.EmployeeRepository;
|
||||
import com.example.petstoremobile.utils.Resource;
|
||||
|
||||
import javax.inject.Inject;
|
||||
|
||||
import dagger.hilt.android.lifecycle.HiltViewModel;
|
||||
|
||||
@HiltViewModel
|
||||
public class StaffDetailViewModel extends ViewModel {
|
||||
private final EmployeeRepository repository;
|
||||
private long employeeId = -1;
|
||||
private boolean isEditing = false;
|
||||
|
||||
@Inject
|
||||
public StaffDetailViewModel(EmployeeRepository repository) {
|
||||
this.repository = repository;
|
||||
}
|
||||
|
||||
public void setEmployeeId(long id, boolean isEditing) {
|
||||
this.employeeId = id;
|
||||
this.isEditing = isEditing;
|
||||
}
|
||||
|
||||
public long getEmployeeId() {
|
||||
return employeeId;
|
||||
}
|
||||
|
||||
public boolean isEditing() {
|
||||
return isEditing;
|
||||
}
|
||||
|
||||
public LiveData<Resource<EmployeeDTO>> saveEmployee(EmployeeDTO dto) {
|
||||
if (isEditing && employeeId > 0) {
|
||||
return repository.updateEmployee(employeeId, dto);
|
||||
} else {
|
||||
return repository.createEmployee(dto);
|
||||
}
|
||||
}
|
||||
|
||||
public LiveData<Resource<Void>> deleteEmployee() {
|
||||
return repository.deleteEmployee(employeeId);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,71 @@
|
||||
package com.example.petstoremobile.viewmodels;
|
||||
|
||||
import androidx.lifecycle.LiveData;
|
||||
import androidx.lifecycle.MutableLiveData;
|
||||
import androidx.lifecycle.ViewModel;
|
||||
|
||||
import com.example.petstoremobile.dtos.EmployeeDTO;
|
||||
import com.example.petstoremobile.repositories.EmployeeRepository;
|
||||
import com.example.petstoremobile.utils.Resource;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.List;
|
||||
|
||||
import javax.inject.Inject;
|
||||
|
||||
import dagger.hilt.android.lifecycle.HiltViewModel;
|
||||
|
||||
@HiltViewModel
|
||||
public class StaffListViewModel extends ViewModel {
|
||||
private final EmployeeRepository repository;
|
||||
|
||||
private final MutableLiveData<List<EmployeeDTO>> employees = new MutableLiveData<>(new ArrayList<>());
|
||||
private final MutableLiveData<List<EmployeeDTO>> filteredEmployees = new MutableLiveData<>(new ArrayList<>());
|
||||
private final MutableLiveData<Boolean> isLoading = new MutableLiveData<>(false);
|
||||
private String lastQuery = "";
|
||||
|
||||
@Inject
|
||||
public StaffListViewModel(EmployeeRepository repository) {
|
||||
this.repository = repository;
|
||||
}
|
||||
|
||||
public LiveData<List<EmployeeDTO>> getFilteredEmployees() { return filteredEmployees; }
|
||||
public LiveData<Boolean> getIsLoading() { return isLoading; }
|
||||
|
||||
public void loadStaff() {
|
||||
isLoading.setValue(true);
|
||||
repository.getAllEmployees(0, 100).observeForever(resource -> {
|
||||
if (resource != null) {
|
||||
if (resource.status == Resource.Status.SUCCESS && resource.data != null) {
|
||||
employees.setValue(resource.data.getContent());
|
||||
filter(lastQuery);
|
||||
isLoading.setValue(false);
|
||||
} else if (resource.status == Resource.Status.ERROR) {
|
||||
isLoading.setValue(false);
|
||||
}
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
public void filter(String query) {
|
||||
this.lastQuery = query;
|
||||
List<EmployeeDTO> all = employees.getValue();
|
||||
if (all == null) return;
|
||||
|
||||
if (query.isEmpty()) {
|
||||
filteredEmployees.setValue(new ArrayList<>(all));
|
||||
} else {
|
||||
List<EmployeeDTO> filtered = new ArrayList<>();
|
||||
String lower = query.toLowerCase();
|
||||
for (EmployeeDTO e : all) {
|
||||
if ((e.getFullName() != null && e.getFullName().toLowerCase().contains(lower))
|
||||
|| (e.getUsername() != null && e.getUsername().toLowerCase().contains(lower))
|
||||
|| (e.getEmail() != null && e.getEmail().toLowerCase().contains(lower))
|
||||
|| (e.getPhone() != null && e.getPhone().toLowerCase().contains(lower))) {
|
||||
filtered.add(e);
|
||||
}
|
||||
}
|
||||
filteredEmployees.setValue(filtered);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,30 +0,0 @@
|
||||
package com.example.petstoremobile.viewmodels;
|
||||
|
||||
import androidx.lifecycle.LiveData;
|
||||
import androidx.lifecycle.ViewModel;
|
||||
|
||||
import com.example.petstoremobile.dtos.PageResponse;
|
||||
import com.example.petstoremobile.dtos.StoreDTO;
|
||||
import com.example.petstoremobile.repositories.StoreRepository;
|
||||
import com.example.petstoremobile.utils.Resource;
|
||||
|
||||
import javax.inject.Inject;
|
||||
|
||||
import dagger.hilt.android.lifecycle.HiltViewModel;
|
||||
|
||||
@HiltViewModel
|
||||
public class StoreViewModel extends ViewModel {
|
||||
private final StoreRepository repository;
|
||||
|
||||
@Inject
|
||||
public StoreViewModel(StoreRepository repository) {
|
||||
this.repository = repository;
|
||||
}
|
||||
|
||||
/**
|
||||
* Fetches a paginated list of all stores.
|
||||
*/
|
||||
public LiveData<Resource<PageResponse<StoreDTO>>> getAllStores(int page, int size) {
|
||||
return repository.getAllStores(page, size);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,54 @@
|
||||
package com.example.petstoremobile.viewmodels;
|
||||
|
||||
import androidx.lifecycle.LiveData;
|
||||
import androidx.lifecycle.ViewModel;
|
||||
|
||||
import com.example.petstoremobile.dtos.SupplierDTO;
|
||||
import com.example.petstoremobile.repositories.SupplierRepository;
|
||||
import com.example.petstoremobile.utils.Resource;
|
||||
|
||||
import javax.inject.Inject;
|
||||
|
||||
import dagger.hilt.android.lifecycle.HiltViewModel;
|
||||
|
||||
@HiltViewModel
|
||||
public class SupplierDetailViewModel extends ViewModel {
|
||||
private final SupplierRepository repository;
|
||||
private long supId = -1;
|
||||
private boolean isEditing = false;
|
||||
|
||||
@Inject
|
||||
public SupplierDetailViewModel(SupplierRepository repository) {
|
||||
this.repository = repository;
|
||||
}
|
||||
|
||||
public void setSupId(long id) {
|
||||
this.supId = id;
|
||||
this.isEditing = id != -1;
|
||||
}
|
||||
|
||||
public long getSupId() {
|
||||
return supId;
|
||||
}
|
||||
|
||||
public boolean isEditing() {
|
||||
return isEditing;
|
||||
}
|
||||
|
||||
public LiveData<Resource<SupplierDTO>> loadSupplier() {
|
||||
return repository.getSupplierById(supId);
|
||||
}
|
||||
|
||||
public LiveData<Resource<SupplierDTO>> saveSupplier(SupplierDTO dto) {
|
||||
if (isEditing) {
|
||||
dto.setSupId(supId);
|
||||
return repository.updateSupplier(supId, dto);
|
||||
} else {
|
||||
return repository.createSupplier(dto);
|
||||
}
|
||||
}
|
||||
|
||||
public LiveData<Resource<Void>> deleteSupplier() {
|
||||
return repository.deleteSupplier(supId);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,51 @@
|
||||
package com.example.petstoremobile.viewmodels;
|
||||
|
||||
import androidx.lifecycle.LiveData;
|
||||
import androidx.lifecycle.MutableLiveData;
|
||||
import androidx.lifecycle.ViewModel;
|
||||
|
||||
import com.example.petstoremobile.dtos.BulkDeleteRequest;
|
||||
import com.example.petstoremobile.dtos.SupplierDTO;
|
||||
import com.example.petstoremobile.repositories.SupplierRepository;
|
||||
import com.example.petstoremobile.utils.Resource;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.List;
|
||||
|
||||
import javax.inject.Inject;
|
||||
|
||||
import dagger.hilt.android.lifecycle.HiltViewModel;
|
||||
|
||||
@HiltViewModel
|
||||
public class SupplierListViewModel extends ViewModel {
|
||||
private final SupplierRepository repository;
|
||||
|
||||
private final MutableLiveData<List<SupplierDTO>> suppliers = new MutableLiveData<>(new ArrayList<>());
|
||||
private final MutableLiveData<Boolean> isLoading = new MutableLiveData<>(false);
|
||||
|
||||
@Inject
|
||||
public SupplierListViewModel(SupplierRepository repository) {
|
||||
this.repository = repository;
|
||||
}
|
||||
|
||||
public LiveData<List<SupplierDTO>> getSuppliers() { return suppliers; }
|
||||
public LiveData<Boolean> getIsLoading() { return isLoading; }
|
||||
|
||||
public void loadSuppliers(String query) {
|
||||
isLoading.setValue(true);
|
||||
repository.getAllSuppliers(0, 100, query, "supCompany").observeForever(resource -> {
|
||||
if (resource != null) {
|
||||
if (resource.status == Resource.Status.SUCCESS && resource.data != null) {
|
||||
suppliers.setValue(resource.data.getContent());
|
||||
isLoading.setValue(false);
|
||||
} else if (resource.status == Resource.Status.ERROR) {
|
||||
isLoading.setValue(false);
|
||||
}
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
public LiveData<Resource<Void>> bulkDeleteSuppliers(List<String> ids) {
|
||||
return repository.bulkDeleteSuppliers(new BulkDeleteRequest(ids));
|
||||
}
|
||||
}
|
||||
@@ -1,68 +0,0 @@
|
||||
package com.example.petstoremobile.viewmodels;
|
||||
|
||||
import androidx.lifecycle.LiveData;
|
||||
import androidx.lifecycle.ViewModel;
|
||||
|
||||
import com.example.petstoremobile.dtos.BulkDeleteRequest;
|
||||
import com.example.petstoremobile.dtos.PageResponse;
|
||||
import com.example.petstoremobile.dtos.SupplierDTO;
|
||||
import com.example.petstoremobile.repositories.SupplierRepository;
|
||||
import com.example.petstoremobile.utils.Resource;
|
||||
|
||||
import java.util.List;
|
||||
|
||||
import javax.inject.Inject;
|
||||
|
||||
import dagger.hilt.android.lifecycle.HiltViewModel;
|
||||
|
||||
@HiltViewModel
|
||||
public class SupplierViewModel extends ViewModel {
|
||||
private final SupplierRepository repository;
|
||||
|
||||
@Inject
|
||||
public SupplierViewModel(SupplierRepository repository) {
|
||||
this.repository = repository;
|
||||
}
|
||||
|
||||
/**
|
||||
* Fetches a paginated list of all suppliers.
|
||||
*/
|
||||
public LiveData<Resource<PageResponse<SupplierDTO>>> getAllSuppliers(int page, int size, String query, String sort) {
|
||||
return repository.getAllSuppliers(page, size, query, sort);
|
||||
}
|
||||
|
||||
/**
|
||||
* Retrieves a single supplier by its ID.
|
||||
*/
|
||||
public LiveData<Resource<SupplierDTO>> getSupplierById(Long id) {
|
||||
return repository.getSupplierById(id);
|
||||
}
|
||||
|
||||
/**
|
||||
* Creates a new supplier record.
|
||||
*/
|
||||
public LiveData<Resource<SupplierDTO>> createSupplier(SupplierDTO supplier) {
|
||||
return repository.createSupplier(supplier);
|
||||
}
|
||||
|
||||
/**
|
||||
* Updates an existing supplier record by ID.
|
||||
*/
|
||||
public LiveData<Resource<SupplierDTO>> updateSupplier(Long id, SupplierDTO supplier) {
|
||||
return repository.updateSupplier(id, supplier);
|
||||
}
|
||||
|
||||
/**
|
||||
* Deletes a supplier record by ID.
|
||||
*/
|
||||
public LiveData<Resource<Void>> deleteSupplier(Long id) {
|
||||
return repository.deleteSupplier(id);
|
||||
}
|
||||
|
||||
/**
|
||||
* Deletes multiple supplier records.
|
||||
*/
|
||||
public LiveData<Resource<Void>> bulkDeleteSuppliers(List<String> ids) {
|
||||
return repository.bulkDeleteSuppliers(new BulkDeleteRequest(ids));
|
||||
}
|
||||
}
|
||||
@@ -1,27 +0,0 @@
|
||||
package com.example.petstoremobile.viewmodels;
|
||||
|
||||
import androidx.lifecycle.LiveData;
|
||||
import androidx.lifecycle.ViewModel;
|
||||
|
||||
import com.example.petstoremobile.dtos.PageResponse;
|
||||
import com.example.petstoremobile.dtos.UserDTO;
|
||||
import com.example.petstoremobile.repositories.UserRepository;
|
||||
import com.example.petstoremobile.utils.Resource;
|
||||
|
||||
import javax.inject.Inject;
|
||||
|
||||
import dagger.hilt.android.lifecycle.HiltViewModel;
|
||||
|
||||
@HiltViewModel
|
||||
public class UserViewModel extends ViewModel {
|
||||
private final UserRepository userRepository;
|
||||
|
||||
@Inject
|
||||
public UserViewModel(UserRepository userRepository) {
|
||||
this.userRepository = userRepository;
|
||||
}
|
||||
|
||||
public LiveData<Resource<PageResponse<UserDTO>>> getUsers(String role, int page, int size) {
|
||||
return userRepository.getUsers(role, page, size);
|
||||
}
|
||||
}
|
||||
40
android/app/src/main/res/layout/dialog_full_screen_image.xml
Normal file
40
android/app/src/main/res/layout/dialog_full_screen_image.xml
Normal file
@@ -0,0 +1,40 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<FrameLayout xmlns:android="http://schemas.android.com/apk/res/android"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="match_parent"
|
||||
android:background="@android:color/black">
|
||||
|
||||
<ImageView
|
||||
android:id="@+id/ivFullScreen"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="match_parent"
|
||||
android:scaleType="fitCenter" />
|
||||
|
||||
<LinearLayout
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_gravity="top|end"
|
||||
android:layout_margin="16dp"
|
||||
android:orientation="horizontal">
|
||||
|
||||
<ImageButton
|
||||
android:id="@+id/btnDownload"
|
||||
android:layout_width="48dp"
|
||||
android:layout_height="48dp"
|
||||
android:background="@android:color/transparent"
|
||||
android:src="@android:drawable/stat_sys_download"
|
||||
android:contentDescription="Download"
|
||||
android:tint="@android:color/white" />
|
||||
|
||||
<ImageButton
|
||||
android:id="@+id/btnClose"
|
||||
android:layout_width="48dp"
|
||||
android:layout_height="48dp"
|
||||
android:layout_marginLeft="8dp"
|
||||
android:background="@android:color/transparent"
|
||||
android:src="@android:drawable/ic_menu_close_clear_cancel"
|
||||
android:contentDescription="Close"
|
||||
android:tint="@android:color/white" />
|
||||
</LinearLayout>
|
||||
|
||||
</FrameLayout>
|
||||
@@ -1,214 +1,228 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
|
||||
<FrameLayout xmlns:android="http://schemas.android.com/apk/res/android"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="match_parent"
|
||||
android:orientation="vertical"
|
||||
android:background="@color/background_grey">
|
||||
android:layout_height="match_parent">
|
||||
|
||||
<LinearLayout
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="56dp"
|
||||
android:background="@color/primary_dark"
|
||||
android:gravity="center_vertical"
|
||||
android:paddingStart="16dp"
|
||||
android:paddingEnd="16dp"
|
||||
android:orientation="horizontal">
|
||||
|
||||
<TextView
|
||||
android:id="@+id/tvAdoptionMode"
|
||||
android:layout_width="0dp"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_weight="1"
|
||||
android:text="Add Adoption"
|
||||
android:textColor="@color/white"
|
||||
android:textSize="20sp"
|
||||
android:textStyle="bold"/>
|
||||
|
||||
<Button
|
||||
android:id="@+id/btnDeleteAdoption"
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_marginStart="8dp"
|
||||
android:backgroundTint="@color/accent_coral"
|
||||
android:text="Delete"
|
||||
android:textColor="@color/white"/>
|
||||
|
||||
</LinearLayout>
|
||||
|
||||
<ScrollView
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="0dp"
|
||||
android:layout_weight="1">
|
||||
android:layout_height="match_parent"
|
||||
android:orientation="vertical"
|
||||
android:background="@color/background_grey">
|
||||
|
||||
<LinearLayout
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:orientation="vertical"
|
||||
android:padding="24dp">
|
||||
android:layout_height="56dp"
|
||||
android:background="@color/primary_dark"
|
||||
android:gravity="center_vertical"
|
||||
android:paddingStart="16dp"
|
||||
android:paddingEnd="16dp"
|
||||
android:orientation="horizontal">
|
||||
|
||||
<TextView
|
||||
android:id="@+id/tvAdoptionMode"
|
||||
android:layout_width="0dp"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_weight="1"
|
||||
android:text="Add Adoption"
|
||||
android:textColor="@color/white"
|
||||
android:textSize="20sp"
|
||||
android:textStyle="bold"/>
|
||||
|
||||
<Button
|
||||
android:id="@+id/btnDeleteAdoption"
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_marginStart="8dp"
|
||||
android:backgroundTint="@color/accent_coral"
|
||||
android:text="Delete"
|
||||
android:textColor="@color/white"/>
|
||||
|
||||
</LinearLayout>
|
||||
|
||||
<ScrollView
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="0dp"
|
||||
android:layout_weight="1">
|
||||
|
||||
<LinearLayout
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:orientation="vertical"
|
||||
android:background="@drawable/rounded_card"
|
||||
android:padding="16dp"
|
||||
android:layout_marginBottom="16dp">
|
||||
android:padding="24dp">
|
||||
|
||||
<TextView
|
||||
android:id="@+id/tvAdoptionId"
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content"
|
||||
android:text="ID: #0"
|
||||
android:textColor="@color/text_light"
|
||||
android:textSize="11sp"
|
||||
android:textStyle="italic"
|
||||
android:layout_gravity="end"
|
||||
android:layout_marginBottom="8dp"/>
|
||||
|
||||
<!-- Customer -->
|
||||
<TextView
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content"
|
||||
android:text="Customer"
|
||||
android:textColor="@color/text_dark"
|
||||
android:textSize="12sp"
|
||||
android:layout_marginBottom="4dp"/>
|
||||
|
||||
<Spinner
|
||||
android:id="@+id/spinnerAdoptionCustomer"
|
||||
<LinearLayout
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_marginBottom="16dp"/>
|
||||
android:orientation="vertical"
|
||||
android:background="@drawable/rounded_card"
|
||||
android:padding="16dp"
|
||||
android:layout_marginBottom="16dp">
|
||||
|
||||
<!-- Pet -->
|
||||
<TextView
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content"
|
||||
android:text="Pet"
|
||||
android:textColor="@color/text_dark"
|
||||
android:textSize="12sp"
|
||||
android:layout_marginBottom="4dp"/>
|
||||
<TextView
|
||||
android:id="@+id/tvAdoptionId"
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content"
|
||||
android:text="ID: #0"
|
||||
android:textColor="@color/text_light"
|
||||
android:textSize="11sp"
|
||||
android:textStyle="italic"
|
||||
android:layout_gravity="end"
|
||||
android:layout_marginBottom="8dp"/>
|
||||
|
||||
<Spinner
|
||||
android:id="@+id/spinnerAdoptionPet"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_marginBottom="16dp"/>
|
||||
<!-- Customer -->
|
||||
<TextView
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content"
|
||||
android:text="Customer"
|
||||
android:textColor="@color/text_dark"
|
||||
android:textSize="12sp"
|
||||
android:layout_marginBottom="4dp"/>
|
||||
|
||||
<!-- Employee -->
|
||||
<TextView
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content"
|
||||
android:text="Handled By (Staff)"
|
||||
android:textColor="@color/text_dark"
|
||||
android:textSize="12sp"
|
||||
android:layout_marginBottom="4dp"/>
|
||||
<Spinner
|
||||
android:id="@+id/spinnerAdoptionCustomer"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_marginBottom="16dp"/>
|
||||
|
||||
<Spinner
|
||||
android:id="@+id/spinnerAdoptionEmployee"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_marginBottom="16dp"/>
|
||||
<!-- Pet -->
|
||||
<TextView
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content"
|
||||
android:text="Pet"
|
||||
android:textColor="@color/text_dark"
|
||||
android:textSize="12sp"
|
||||
android:layout_marginBottom="4dp"/>
|
||||
|
||||
<TextView
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content"
|
||||
android:text="Source Store"
|
||||
android:textColor="@color/text_dark"
|
||||
android:textSize="12sp"
|
||||
android:layout_marginBottom="4dp"/>
|
||||
<Spinner
|
||||
android:id="@+id/spinnerAdoptionPet"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_marginBottom="16dp"/>
|
||||
|
||||
<Spinner
|
||||
android:id="@+id/spinnerAdoptionStore"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_marginBottom="16dp"/>
|
||||
<!-- Employee -->
|
||||
<TextView
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content"
|
||||
android:text="Handled By (Staff)"
|
||||
android:textColor="@color/text_dark"
|
||||
android:textSize="12sp"
|
||||
android:layout_marginBottom="4dp"/>
|
||||
|
||||
<!-- Adoption Date -->
|
||||
<TextView
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content"
|
||||
android:text="Adoption Date"
|
||||
android:textColor="@color/text_dark"
|
||||
android:textSize="12sp"
|
||||
android:layout_marginBottom="4dp"/>
|
||||
<Spinner
|
||||
android:id="@+id/spinnerAdoptionEmployee"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_marginBottom="16dp"/>
|
||||
|
||||
<EditText
|
||||
android:id="@+id/etAdoptionDate"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:hint="Tap to select date"
|
||||
android:inputType="none"
|
||||
android:focusable="false"
|
||||
android:clickable="true"
|
||||
android:drawableEnd="@android:drawable/ic_menu_my_calendar"
|
||||
android:layout_marginBottom="16dp"/>
|
||||
<TextView
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content"
|
||||
android:text="Source Store"
|
||||
android:textColor="@color/text_dark"
|
||||
android:textSize="12sp"
|
||||
android:layout_marginBottom="4dp"/>
|
||||
|
||||
<!-- Adoption Fee -->
|
||||
<TextView
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content"
|
||||
android:text="Adoption Fee"
|
||||
android:textColor="@color/text_dark"
|
||||
android:textSize="12sp"
|
||||
android:layout_marginBottom="4dp"/>
|
||||
<Spinner
|
||||
android:id="@+id/spinnerAdoptionStore"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_marginBottom="16dp"/>
|
||||
|
||||
<EditText
|
||||
android:id="@+id/etAdoptionFee"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:hint="0.00"
|
||||
android:inputType="numberDecimal"
|
||||
android:layout_marginBottom="16dp"/>
|
||||
<!-- Adoption Date -->
|
||||
<TextView
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content"
|
||||
android:text="Adoption Date"
|
||||
android:textColor="@color/text_dark"
|
||||
android:textSize="12sp"
|
||||
android:layout_marginBottom="4dp"/>
|
||||
|
||||
<!-- Status -->
|
||||
<TextView
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content"
|
||||
android:text="Status"
|
||||
android:textColor="@color/text_dark"
|
||||
android:textSize="12sp"
|
||||
android:layout_marginBottom="4dp"/>
|
||||
<EditText
|
||||
android:id="@+id/etAdoptionDate"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:hint="Tap to select date"
|
||||
android:inputType="none"
|
||||
android:focusable="false"
|
||||
android:clickable="true"
|
||||
android:drawableEnd="@android:drawable/ic_menu_my_calendar"
|
||||
android:layout_marginBottom="16dp"/>
|
||||
|
||||
<Spinner
|
||||
android:id="@+id/spinnerAdoptionStatus"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_marginBottom="8dp"/>
|
||||
<!-- Adoption Fee -->
|
||||
<TextView
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content"
|
||||
android:text="Adoption Fee"
|
||||
android:textColor="@color/text_dark"
|
||||
android:textSize="12sp"
|
||||
android:layout_marginBottom="4dp"/>
|
||||
|
||||
<EditText
|
||||
android:id="@+id/etAdoptionFee"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:hint="0.00"
|
||||
android:inputType="numberDecimal"
|
||||
android:layout_marginBottom="16dp"/>
|
||||
|
||||
<!-- Status -->
|
||||
<TextView
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content"
|
||||
android:text="Status"
|
||||
android:textColor="@color/text_dark"
|
||||
android:textSize="12sp"
|
||||
android:layout_marginBottom="4dp"/>
|
||||
|
||||
<Spinner
|
||||
android:id="@+id/spinnerAdoptionStatus"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_marginBottom="8dp"/>
|
||||
|
||||
</LinearLayout>
|
||||
|
||||
</LinearLayout>
|
||||
|
||||
</ScrollView>
|
||||
|
||||
<LinearLayout
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:orientation="horizontal"
|
||||
android:background="@color/white"
|
||||
android:padding="16dp">
|
||||
|
||||
<Button
|
||||
android:id="@+id/btnAdoptionBack"
|
||||
android:layout_width="0dp"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_weight="1"
|
||||
android:layout_marginEnd="8dp"
|
||||
android:text="Back"
|
||||
android:backgroundTint="@color/primary_medium"
|
||||
android:textColor="@color/white"/>
|
||||
|
||||
<Button
|
||||
android:id="@+id/btnSaveAdoption"
|
||||
android:layout_width="0dp"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_weight="1"
|
||||
android:layout_marginStart="8dp"
|
||||
android:text="Save"
|
||||
android:backgroundTint="@color/accent_coral"
|
||||
android:textColor="@color/white"/>
|
||||
|
||||
</LinearLayout>
|
||||
|
||||
</ScrollView>
|
||||
|
||||
<LinearLayout
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:orientation="horizontal"
|
||||
android:background="@color/white"
|
||||
android:padding="16dp">
|
||||
|
||||
<Button
|
||||
android:id="@+id/btnAdoptionBack"
|
||||
android:layout_width="0dp"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_weight="1"
|
||||
android:layout_marginEnd="8dp"
|
||||
android:text="Back"
|
||||
android:backgroundTint="@color/primary_medium"
|
||||
android:textColor="@color/white"/>
|
||||
|
||||
<Button
|
||||
android:id="@+id/btnSaveAdoption"
|
||||
android:layout_width="0dp"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_weight="1"
|
||||
android:layout_marginStart="8dp"
|
||||
android:text="Save"
|
||||
android:backgroundTint="@color/accent_coral"
|
||||
android:textColor="@color/white"/>
|
||||
|
||||
</LinearLayout>
|
||||
|
||||
</LinearLayout>
|
||||
<ProgressBar
|
||||
android:id="@+id/progressBar"
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_gravity="center"
|
||||
android:visibility="gone"
|
||||
android:indeterminateTint="@color/accent_coral"/>
|
||||
|
||||
</FrameLayout>
|
||||
@@ -1,318 +1,332 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
|
||||
<FrameLayout xmlns:android="http://schemas.android.com/apk/res/android"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="match_parent"
|
||||
android:orientation="vertical"
|
||||
android:background="@color/background_grey">
|
||||
android:layout_height="match_parent">
|
||||
|
||||
<LinearLayout
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="56dp"
|
||||
android:background="@color/primary_dark"
|
||||
android:gravity="center_vertical"
|
||||
android:paddingStart="16dp"
|
||||
android:paddingEnd="16dp"
|
||||
android:orientation="horizontal">
|
||||
|
||||
<ImageButton
|
||||
android:id="@+id/btnHamburgerAnalytics"
|
||||
android:layout_width="48dp"
|
||||
android:layout_height="48dp"
|
||||
android:src="@drawable/baseline_menu_36"
|
||||
android:background="?attr/selectableItemBackgroundBorderless"
|
||||
android:contentDescription="Open menu"/>
|
||||
|
||||
<TextView
|
||||
android:layout_width="0dp"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_weight="1"
|
||||
android:text="Analytics"
|
||||
android:textColor="@color/white"
|
||||
android:textSize="20sp"
|
||||
android:textStyle="bold"/>
|
||||
|
||||
<Button
|
||||
android:id="@+id/btnRefreshAnalytics"
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content"
|
||||
android:text="Refresh"
|
||||
android:backgroundTint="@color/accent_coral"
|
||||
android:textColor="@color/white"/>
|
||||
|
||||
</LinearLayout>
|
||||
|
||||
<ScrollView
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="match_parent">
|
||||
android:layout_height="match_parent"
|
||||
android:orientation="vertical"
|
||||
android:background="@color/background_grey">
|
||||
|
||||
<LinearLayout
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:orientation="vertical"
|
||||
android:padding="16dp">
|
||||
android:layout_height="56dp"
|
||||
android:background="@color/primary_dark"
|
||||
android:gravity="center_vertical"
|
||||
android:paddingStart="16dp"
|
||||
android:paddingEnd="16dp"
|
||||
android:orientation="horizontal">
|
||||
|
||||
<!-- Summary Cards Row 1 -->
|
||||
<LinearLayout
|
||||
android:layout_width="match_parent"
|
||||
<ImageButton
|
||||
android:id="@+id/btnHamburgerAnalytics"
|
||||
android:layout_width="48dp"
|
||||
android:layout_height="48dp"
|
||||
android:src="@drawable/baseline_menu_36"
|
||||
android:background="?attr/selectableItemBackgroundBorderless"
|
||||
android:contentDescription="Open menu"/>
|
||||
|
||||
<TextView
|
||||
android:layout_width="0dp"
|
||||
android:layout_height="wrap_content"
|
||||
android:orientation="horizontal"
|
||||
android:layout_marginBottom="8dp">
|
||||
android:layout_weight="1"
|
||||
android:text="Analytics"
|
||||
android:textColor="@color/white"
|
||||
android:textSize="20sp"
|
||||
android:textStyle="bold"/>
|
||||
|
||||
<LinearLayout
|
||||
android:layout_width="0dp"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_weight="1"
|
||||
android:layout_marginEnd="4dp"
|
||||
android:orientation="vertical"
|
||||
android:background="@drawable/rounded_card"
|
||||
android:padding="16dp">
|
||||
|
||||
<TextView
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content"
|
||||
android:text="Total Revenue"
|
||||
android:textColor="@color/text_light"
|
||||
android:textSize="11sp"/>
|
||||
|
||||
<TextView
|
||||
android:id="@+id/tvTotalRevenue"
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content"
|
||||
android:text="$0.00"
|
||||
android:textColor="@color/accent_coral"
|
||||
android:textSize="20sp"
|
||||
android:textStyle="bold"
|
||||
android:layout_marginTop="4dp"/>
|
||||
|
||||
</LinearLayout>
|
||||
|
||||
<LinearLayout
|
||||
android:layout_width="0dp"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_weight="1"
|
||||
android:layout_marginStart="4dp"
|
||||
android:orientation="vertical"
|
||||
android:background="@drawable/rounded_card"
|
||||
android:padding="16dp">
|
||||
|
||||
<TextView
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content"
|
||||
android:text="Transactions"
|
||||
android:textColor="@color/text_light"
|
||||
android:textSize="11sp"/>
|
||||
|
||||
<TextView
|
||||
android:id="@+id/tvTotalTransactions"
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content"
|
||||
android:text="0"
|
||||
android:textColor="@color/primary_dark"
|
||||
android:textSize="20sp"
|
||||
android:textStyle="bold"
|
||||
android:layout_marginTop="4dp"/>
|
||||
|
||||
</LinearLayout>
|
||||
|
||||
</LinearLayout>
|
||||
|
||||
<!-- Summary Cards Row 2 -->
|
||||
<LinearLayout
|
||||
android:layout_width="match_parent"
|
||||
<Button
|
||||
android:id="@+id/btnRefreshAnalytics"
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content"
|
||||
android:orientation="horizontal"
|
||||
android:layout_marginBottom="16dp">
|
||||
|
||||
<LinearLayout
|
||||
android:layout_width="0dp"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_weight="1"
|
||||
android:layout_marginEnd="4dp"
|
||||
android:orientation="vertical"
|
||||
android:background="@drawable/rounded_card"
|
||||
android:padding="16dp">
|
||||
|
||||
<TextView
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content"
|
||||
android:text="Avg Transaction"
|
||||
android:textColor="@color/text_light"
|
||||
android:textSize="11sp"/>
|
||||
|
||||
<TextView
|
||||
android:id="@+id/tvAvgTransaction"
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content"
|
||||
android:text="$0.00"
|
||||
android:textColor="@color/primary_dark"
|
||||
android:textSize="20sp"
|
||||
android:textStyle="bold"
|
||||
android:layout_marginTop="4dp"/>
|
||||
|
||||
</LinearLayout>
|
||||
|
||||
<LinearLayout
|
||||
android:layout_width="0dp"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_weight="1"
|
||||
android:layout_marginStart="4dp"
|
||||
android:orientation="vertical"
|
||||
android:background="@drawable/rounded_card"
|
||||
android:padding="16dp">
|
||||
|
||||
<TextView
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content"
|
||||
android:text="Items Sold"
|
||||
android:textColor="@color/text_light"
|
||||
android:textSize="11sp"/>
|
||||
|
||||
<TextView
|
||||
android:id="@+id/tvTotalItems"
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content"
|
||||
android:text="0"
|
||||
android:textColor="@color/primary_dark"
|
||||
android:textSize="20sp"
|
||||
android:textStyle="bold"
|
||||
android:layout_marginTop="4dp"/>
|
||||
|
||||
</LinearLayout>
|
||||
|
||||
</LinearLayout>
|
||||
|
||||
<!-- Top Products by Revenue -->
|
||||
<LinearLayout
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:orientation="vertical"
|
||||
android:background="@drawable/rounded_card"
|
||||
android:padding="16dp"
|
||||
android:layout_marginBottom="16dp">
|
||||
|
||||
<TextView
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content"
|
||||
android:text="Top Products by Revenue"
|
||||
android:textColor="@color/text_dark"
|
||||
android:textSize="14sp"
|
||||
android:textStyle="bold"
|
||||
android:layout_marginBottom="12dp"/>
|
||||
|
||||
<LinearLayout
|
||||
android:id="@+id/llTopRevenue"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:orientation="vertical"/>
|
||||
|
||||
</LinearLayout>
|
||||
|
||||
<!-- Top Products by Quantity -->
|
||||
<LinearLayout
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:orientation="vertical"
|
||||
android:background="@drawable/rounded_card"
|
||||
android:padding="16dp"
|
||||
android:layout_marginBottom="16dp">
|
||||
|
||||
<TextView
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content"
|
||||
android:text="Top Products by Quantity"
|
||||
android:textColor="@color/text_dark"
|
||||
android:textSize="14sp"
|
||||
android:textStyle="bold"
|
||||
android:layout_marginBottom="12dp"/>
|
||||
|
||||
<LinearLayout
|
||||
android:id="@+id/llTopQuantity"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:orientation="vertical"/>
|
||||
|
||||
</LinearLayout>
|
||||
|
||||
<!-- Payment Methods -->
|
||||
<LinearLayout
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:orientation="vertical"
|
||||
android:background="@drawable/rounded_card"
|
||||
android:padding="16dp"
|
||||
android:layout_marginBottom="16dp">
|
||||
|
||||
<TextView
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content"
|
||||
android:text="Payment Methods"
|
||||
android:textColor="@color/text_dark"
|
||||
android:textSize="14sp"
|
||||
android:textStyle="bold"
|
||||
android:layout_marginBottom="12dp"/>
|
||||
|
||||
<LinearLayout
|
||||
android:id="@+id/llPaymentMethods"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:orientation="vertical"/>
|
||||
|
||||
</LinearLayout>
|
||||
|
||||
<!-- Employee Performance -->
|
||||
<LinearLayout
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:orientation="vertical"
|
||||
android:background="@drawable/rounded_card"
|
||||
android:padding="16dp"
|
||||
android:layout_marginBottom="16dp">
|
||||
|
||||
<TextView
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content"
|
||||
android:text="Employee Performance"
|
||||
android:textColor="@color/text_dark"
|
||||
android:textSize="14sp"
|
||||
android:textStyle="bold"
|
||||
android:layout_marginBottom="12dp"/>
|
||||
|
||||
<LinearLayout
|
||||
android:id="@+id/llEmployeePerformance"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:orientation="vertical"/>
|
||||
|
||||
</LinearLayout>
|
||||
|
||||
<!-- Daily Revenue -->
|
||||
<LinearLayout
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:orientation="vertical"
|
||||
android:background="@drawable/rounded_card"
|
||||
android:padding="16dp"
|
||||
android:layout_marginBottom="16dp">
|
||||
|
||||
<TextView
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content"
|
||||
android:text="Daily Revenue (Last 7 Days)"
|
||||
android:textColor="@color/text_dark"
|
||||
android:textSize="14sp"
|
||||
android:textStyle="bold"
|
||||
android:layout_marginBottom="12dp"/>
|
||||
|
||||
<LinearLayout
|
||||
android:id="@+id/llDailyRevenue"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:orientation="vertical"/>
|
||||
|
||||
</LinearLayout>
|
||||
android:text="Refresh"
|
||||
android:backgroundTint="@color/accent_coral"
|
||||
android:textColor="@color/white"/>
|
||||
|
||||
</LinearLayout>
|
||||
|
||||
</ScrollView>
|
||||
<ScrollView
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="match_parent">
|
||||
|
||||
</LinearLayout>
|
||||
<LinearLayout
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:orientation="vertical"
|
||||
android:padding="16dp">
|
||||
|
||||
<!-- Summary Cards Row 1 -->
|
||||
<LinearLayout
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:orientation="horizontal"
|
||||
android:layout_marginBottom="8dp">
|
||||
|
||||
<LinearLayout
|
||||
android:layout_width="0dp"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_weight="1"
|
||||
android:layout_marginEnd="4dp"
|
||||
android:orientation="vertical"
|
||||
android:background="@drawable/rounded_card"
|
||||
android:padding="16dp">
|
||||
|
||||
<TextView
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content"
|
||||
android:text="Total Revenue"
|
||||
android:textColor="@color/text_light"
|
||||
android:textSize="11sp"/>
|
||||
|
||||
<TextView
|
||||
android:id="@+id/tvTotalRevenue"
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content"
|
||||
android:text="$0.00"
|
||||
android:textColor="@color/accent_coral"
|
||||
android:textSize="20sp"
|
||||
android:textStyle="bold"
|
||||
android:layout_marginTop="4dp"/>
|
||||
|
||||
</LinearLayout>
|
||||
|
||||
<LinearLayout
|
||||
android:layout_width="0dp"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_weight="1"
|
||||
android:layout_marginStart="4dp"
|
||||
android:orientation="vertical"
|
||||
android:background="@drawable/rounded_card"
|
||||
android:padding="16dp">
|
||||
|
||||
<TextView
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content"
|
||||
android:text="Transactions"
|
||||
android:textColor="@color/text_light"
|
||||
android:textSize="11sp"/>
|
||||
|
||||
<TextView
|
||||
android:id="@+id/tvTotalTransactions"
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content"
|
||||
android:text="0"
|
||||
android:textColor="@color/primary_dark"
|
||||
android:textSize="20sp"
|
||||
android:textStyle="bold"
|
||||
android:layout_marginTop="4dp"/>
|
||||
|
||||
</LinearLayout>
|
||||
|
||||
</LinearLayout>
|
||||
|
||||
<!-- Summary Cards Row 2 -->
|
||||
<LinearLayout
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:orientation="horizontal"
|
||||
android:layout_marginBottom="16dp">
|
||||
|
||||
<LinearLayout
|
||||
android:layout_width="0dp"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_weight="1"
|
||||
android:layout_marginEnd="4dp"
|
||||
android:orientation="vertical"
|
||||
android:background="@drawable/rounded_card"
|
||||
android:padding="16dp">
|
||||
|
||||
<TextView
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content"
|
||||
android:text="Avg Transaction"
|
||||
android:textColor="@color/text_light"
|
||||
android:textSize="11sp"/>
|
||||
|
||||
<TextView
|
||||
android:id="@+id/tvAvgTransaction"
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content"
|
||||
android:text="$0.00"
|
||||
android:textColor="@color/primary_dark"
|
||||
android:textSize="20sp"
|
||||
android:textStyle="bold"
|
||||
android:layout_marginTop="4dp"/>
|
||||
|
||||
</LinearLayout>
|
||||
|
||||
<LinearLayout
|
||||
android:layout_width="0dp"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_weight="1"
|
||||
android:layout_marginStart="4dp"
|
||||
android:orientation="vertical"
|
||||
android:background="@drawable/rounded_card"
|
||||
android:padding="16dp">
|
||||
|
||||
<TextView
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content"
|
||||
android:text="Items Sold"
|
||||
android:textColor="@color/text_light"
|
||||
android:textSize="11sp"/>
|
||||
|
||||
<TextView
|
||||
android:id="@+id/tvTotalItems"
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content"
|
||||
android:text="0"
|
||||
android:textColor="@color/primary_dark"
|
||||
android:textSize="20sp"
|
||||
android:textStyle="bold"
|
||||
android:layout_marginTop="4dp"/>
|
||||
|
||||
</LinearLayout>
|
||||
|
||||
</LinearLayout>
|
||||
|
||||
<!-- Top Products by Revenue -->
|
||||
<LinearLayout
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:orientation="vertical"
|
||||
android:background="@drawable/rounded_card"
|
||||
android:padding="16dp"
|
||||
android:layout_marginBottom="16dp">
|
||||
|
||||
<TextView
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content"
|
||||
android:text="Top Products by Revenue"
|
||||
android:textColor="@color/text_dark"
|
||||
android:textSize="14sp"
|
||||
android:textStyle="bold"
|
||||
android:layout_marginBottom="12dp"/>
|
||||
|
||||
<LinearLayout
|
||||
android:id="@+id/llTopRevenue"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:orientation="vertical"/>
|
||||
|
||||
</LinearLayout>
|
||||
|
||||
<!-- Top Products by Quantity -->
|
||||
<LinearLayout
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:orientation="vertical"
|
||||
android:background="@drawable/rounded_card"
|
||||
android:padding="16dp"
|
||||
android:layout_marginBottom="16dp">
|
||||
|
||||
<TextView
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content"
|
||||
android:text="Top Products by Quantity"
|
||||
android:textColor="@color/text_dark"
|
||||
android:textSize="14sp"
|
||||
android:textStyle="bold"
|
||||
android:layout_marginBottom="12dp"/>
|
||||
|
||||
<LinearLayout
|
||||
android:id="@+id/llTopQuantity"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:orientation="vertical"/>
|
||||
|
||||
</LinearLayout>
|
||||
|
||||
<!-- Payment Methods -->
|
||||
<LinearLayout
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:orientation="vertical"
|
||||
android:background="@drawable/rounded_card"
|
||||
android:padding="16dp"
|
||||
android:layout_marginBottom="16dp">
|
||||
|
||||
<TextView
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content"
|
||||
android:text="Payment Methods"
|
||||
android:textColor="@color/text_dark"
|
||||
android:textSize="14sp"
|
||||
android:textStyle="bold"
|
||||
android:layout_marginBottom="12dp"/>
|
||||
|
||||
<LinearLayout
|
||||
android:id="@+id/llPaymentMethods"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:orientation="vertical"/>
|
||||
|
||||
</LinearLayout>
|
||||
|
||||
<!-- Employee Performance -->
|
||||
<LinearLayout
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:orientation="vertical"
|
||||
android:background="@drawable/rounded_card"
|
||||
android:padding="16dp"
|
||||
android:layout_marginBottom="16dp">
|
||||
|
||||
<TextView
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content"
|
||||
android:text="Employee Performance"
|
||||
android:textColor="@color/text_dark"
|
||||
android:textSize="14sp"
|
||||
android:textStyle="bold"
|
||||
android:layout_marginBottom="12dp"/>
|
||||
|
||||
<LinearLayout
|
||||
android:id="@+id/llEmployeePerformance"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:orientation="vertical"/>
|
||||
|
||||
</LinearLayout>
|
||||
|
||||
<!-- Daily Revenue -->
|
||||
<LinearLayout
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:orientation="vertical"
|
||||
android:background="@drawable/rounded_card"
|
||||
android:padding="16dp"
|
||||
android:layout_marginBottom="16dp">
|
||||
|
||||
<TextView
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content"
|
||||
android:text="Daily Revenue (Last 7 Days)"
|
||||
android:textColor="@color/text_dark"
|
||||
android:textSize="14sp"
|
||||
android:textStyle="bold"
|
||||
android:layout_marginBottom="12dp"/>
|
||||
|
||||
<LinearLayout
|
||||
android:id="@+id/llDailyRevenue"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:orientation="vertical"/>
|
||||
|
||||
</LinearLayout>
|
||||
|
||||
</LinearLayout>
|
||||
|
||||
</ScrollView>
|
||||
|
||||
</LinearLayout>
|
||||
|
||||
<ProgressBar
|
||||
android:id="@+id/progressBar"
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_gravity="center"
|
||||
android:visibility="gone"
|
||||
android:indeterminateTint="@color/accent_coral"/>
|
||||
|
||||
</FrameLayout>
|
||||
@@ -1,266 +1,282 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
|
||||
<FrameLayout xmlns:android="http://schemas.android.com/apk/res/android"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="match_parent"
|
||||
android:orientation="vertical"
|
||||
android:background="@color/background_grey">
|
||||
android:layout_height="match_parent">
|
||||
|
||||
<LinearLayout
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="56dp"
|
||||
android:background="@color/primary_dark"
|
||||
android:gravity="center_vertical"
|
||||
android:paddingStart="16dp"
|
||||
android:paddingEnd="16dp"
|
||||
android:orientation="horizontal">
|
||||
|
||||
<TextView
|
||||
android:id="@+id/tvApptMode"
|
||||
android:layout_width="0dp"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_weight="1"
|
||||
android:text="Add Appointment"
|
||||
android:textColor="@color/white"
|
||||
android:textSize="20sp"
|
||||
android:textStyle="bold"/>
|
||||
|
||||
<Button
|
||||
android:id="@+id/btnDeleteAppointment"
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_marginStart="8dp"
|
||||
android:backgroundTint="@color/accent_coral"
|
||||
android:text="Delete"
|
||||
android:textColor="@color/white" />
|
||||
|
||||
</LinearLayout>
|
||||
|
||||
<ScrollView
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="0dp"
|
||||
android:layout_weight="1">
|
||||
android:layout_height="match_parent"
|
||||
android:orientation="vertical"
|
||||
android:background="@color/background_grey">
|
||||
|
||||
<LinearLayout
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:orientation="vertical"
|
||||
android:padding="24dp">
|
||||
android:layout_height="56dp"
|
||||
android:background="@color/primary_dark"
|
||||
android:gravity="center_vertical"
|
||||
android:paddingStart="16dp"
|
||||
android:paddingEnd="16dp"
|
||||
android:orientation="horizontal">
|
||||
|
||||
<TextView
|
||||
android:id="@+id/tvApptMode"
|
||||
android:layout_width="0dp"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_weight="1"
|
||||
android:text="Add Appointment"
|
||||
android:textColor="@color/white"
|
||||
android:textSize="20sp"
|
||||
android:textStyle="bold"/>
|
||||
|
||||
<Button
|
||||
android:id="@+id/btnDeleteAppointment"
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_marginStart="8dp"
|
||||
android:backgroundTint="@color/accent_coral"
|
||||
android:text="Delete"
|
||||
android:textColor="@color/white" />
|
||||
|
||||
</LinearLayout>
|
||||
|
||||
<ScrollView
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="0dp"
|
||||
android:layout_weight="1">
|
||||
|
||||
<LinearLayout
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:orientation="vertical"
|
||||
android:background="@drawable/rounded_card"
|
||||
android:padding="16dp"
|
||||
android:layout_marginBottom="16dp">
|
||||
|
||||
<TextView
|
||||
android:id="@+id/tvAppointmentId"
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content"
|
||||
android:text="ID: #0"
|
||||
android:textColor="@color/text_light"
|
||||
android:textSize="11sp"
|
||||
android:textStyle="italic"
|
||||
android:layout_gravity="end"
|
||||
android:layout_marginBottom="8dp"/>
|
||||
|
||||
|
||||
|
||||
<!-- Customer -->
|
||||
<TextView
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content"
|
||||
android:text="Customer"
|
||||
android:textColor="@color/text_dark"
|
||||
android:textSize="12sp"
|
||||
android:layout_marginBottom="4dp"/>
|
||||
|
||||
<Spinner
|
||||
android:id="@+id/spinnerCustomer"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_marginBottom="16dp"/>
|
||||
|
||||
<!-- Store -->
|
||||
<TextView
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content"
|
||||
android:text="Store"
|
||||
android:textColor="@color/text_dark"
|
||||
android:textSize="12sp"
|
||||
android:layout_marginBottom="4dp"/>
|
||||
|
||||
<Spinner
|
||||
android:id="@+id/spinnerStore"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_marginBottom="16dp"/>
|
||||
|
||||
<!-- Pet -->
|
||||
|
||||
<TextView
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content"
|
||||
android:text="Pet"
|
||||
android:textColor="@color/text_dark"
|
||||
android:textSize="12sp"
|
||||
android:layout_marginBottom="4dp"/>
|
||||
|
||||
|
||||
<Spinner
|
||||
android:id="@+id/spinnerPet"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_marginBottom="16dp"/>
|
||||
|
||||
<!-- Service -->
|
||||
|
||||
<TextView
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content"
|
||||
android:text="Service"
|
||||
android:textColor="@color/text_dark"
|
||||
android:textSize="12sp"
|
||||
android:layout_marginBottom="4dp"/>
|
||||
|
||||
<Spinner
|
||||
android:id="@+id/spinnerService"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_marginBottom="16dp"/>
|
||||
|
||||
<!-- Staff -->
|
||||
<TextView
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content"
|
||||
android:text="Staff"
|
||||
android:textColor="@color/text_dark"
|
||||
android:textSize="12sp"
|
||||
android:layout_marginBottom="4dp"/>
|
||||
|
||||
<Spinner
|
||||
android:id="@+id/spinnerStaff"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_marginBottom="16dp"/>
|
||||
|
||||
<!-- Appointment Date -->
|
||||
|
||||
<TextView
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content"
|
||||
android:text="Appointment Date"
|
||||
android:textColor="@color/text_dark"
|
||||
android:textSize="12sp"
|
||||
android:layout_marginBottom="4dp"/>
|
||||
|
||||
<EditText
|
||||
android:id="@+id/etAppointmentDate"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:hint="Tap to select date"
|
||||
android:inputType="none"
|
||||
android:focusable="false"
|
||||
android:clickable="true"
|
||||
android:drawableEnd="@android:drawable/ic_menu_my_calendar"
|
||||
android:layout_marginBottom="16dp"/>
|
||||
|
||||
<!-- Appointment Time-->
|
||||
<TextView
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content"
|
||||
android:text="Appointment Time"
|
||||
android:textColor="@color/text_dark"
|
||||
android:textSize="12sp"
|
||||
android:layout_marginBottom="4dp"/>
|
||||
android:padding="24dp">
|
||||
|
||||
<LinearLayout
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:orientation="horizontal"
|
||||
android:layout_marginBottom="16dp"
|
||||
android:gravity="center_vertical">
|
||||
android:orientation="vertical"
|
||||
android:background="@drawable/rounded_card"
|
||||
android:padding="16dp"
|
||||
android:layout_marginBottom="16dp">
|
||||
|
||||
<TextView
|
||||
android:id="@+id/tvAppointmentId"
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content"
|
||||
android:text="Hour:"
|
||||
android:textColor="@color/text_dark"
|
||||
android:textSize="13sp"
|
||||
android:layout_marginEnd="6dp"/>
|
||||
|
||||
<Spinner
|
||||
android:id="@+id/spinnerHour"
|
||||
android:layout_width="0dp"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_weight="1"
|
||||
android:layout_marginEnd="16dp"/>
|
||||
android:text="ID: #0"
|
||||
android:textColor="@color/text_light"
|
||||
android:textSize="11sp"
|
||||
android:textStyle="italic"
|
||||
android:layout_gravity="end"
|
||||
android:layout_marginBottom="8dp"/>
|
||||
|
||||
<!-- Customer -->
|
||||
<TextView
|
||||
android:id="@+id/tvLabelCustomer"
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content"
|
||||
android:text="Min:"
|
||||
android:text="Customer"
|
||||
android:textColor="@color/text_dark"
|
||||
android:textSize="13sp"
|
||||
android:layout_marginEnd="6dp"/>
|
||||
android:textSize="12sp"
|
||||
android:layout_marginBottom="4dp"/>
|
||||
|
||||
<Spinner
|
||||
android:id="@+id/spinnerMinute"
|
||||
android:layout_width="0dp"
|
||||
android:id="@+id/spinnerCustomer"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_weight="1"/>
|
||||
android:layout_marginBottom="16dp"/>
|
||||
|
||||
<!-- Pet -->
|
||||
<TextView
|
||||
android:id="@+id/tvLabelPet"
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content"
|
||||
android:text="Pet"
|
||||
android:textColor="@color/text_dark"
|
||||
android:textSize="12sp"
|
||||
android:layout_marginBottom="4dp"/>
|
||||
|
||||
<Spinner
|
||||
android:id="@+id/spinnerPet"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_marginBottom="16dp"/>
|
||||
|
||||
<!-- Store -->
|
||||
<TextView
|
||||
android:id="@+id/tvLabelStore"
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content"
|
||||
android:text="Store"
|
||||
android:textColor="@color/text_dark"
|
||||
android:textSize="12sp"
|
||||
android:layout_marginBottom="4dp"/>
|
||||
|
||||
<Spinner
|
||||
android:id="@+id/spinnerStore"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_marginBottom="16dp"/>
|
||||
|
||||
<!-- Staff -->
|
||||
<TextView
|
||||
android:id="@+id/tvLabelStaff"
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content"
|
||||
android:text="Staff"
|
||||
android:textColor="@color/text_dark"
|
||||
android:textSize="12sp"
|
||||
android:layout_marginBottom="4dp"/>
|
||||
|
||||
<Spinner
|
||||
android:id="@+id/spinnerStaff"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_marginBottom="16dp"/>
|
||||
|
||||
<!-- Service -->
|
||||
<TextView
|
||||
android:id="@+id/tvLabelService"
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content"
|
||||
android:text="Service"
|
||||
android:textColor="@color/text_dark"
|
||||
android:textSize="12sp"
|
||||
android:layout_marginBottom="4dp"/>
|
||||
|
||||
<Spinner
|
||||
android:id="@+id/spinnerService"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_marginBottom="16dp"/>
|
||||
|
||||
<!-- Appointment Date -->
|
||||
<TextView
|
||||
android:id="@+id/tvLabelDate"
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content"
|
||||
android:text="Appointment Date"
|
||||
android:textColor="@color/text_dark"
|
||||
android:textSize="12sp"
|
||||
android:layout_marginBottom="4dp"/>
|
||||
|
||||
<EditText
|
||||
android:id="@+id/etAppointmentDate"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:hint="Tap to select date"
|
||||
android:inputType="none"
|
||||
android:focusable="false"
|
||||
android:clickable="true"
|
||||
android:drawableEnd="@android:drawable/ic_menu_my_calendar"
|
||||
android:layout_marginBottom="16dp"/>
|
||||
|
||||
<!-- Appointment Time-->
|
||||
<TextView
|
||||
android:id="@+id/tvLabelTime"
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content"
|
||||
android:text="Appointment Time"
|
||||
android:textColor="@color/text_dark"
|
||||
android:textSize="12sp"
|
||||
android:layout_marginBottom="4dp"/>
|
||||
|
||||
<LinearLayout
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:orientation="horizontal"
|
||||
android:layout_marginBottom="16dp"
|
||||
android:gravity="center_vertical">
|
||||
|
||||
<TextView
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content"
|
||||
android:text="Hour:"
|
||||
android:textColor="@color/text_dark"
|
||||
android:textSize="13sp"
|
||||
android:layout_marginEnd="6dp"/>
|
||||
|
||||
<Spinner
|
||||
android:id="@+id/spinnerHour"
|
||||
android:layout_width="0dp"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_weight="1"
|
||||
android:layout_marginEnd="16dp"/>
|
||||
|
||||
<TextView
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content"
|
||||
android:text="Min:"
|
||||
android:textColor="@color/text_dark"
|
||||
android:textSize="13sp"
|
||||
android:layout_marginEnd="6dp"/>
|
||||
|
||||
<Spinner
|
||||
android:id="@+id/spinnerMinute"
|
||||
android:layout_width="0dp"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_weight="1"/>
|
||||
|
||||
</LinearLayout>
|
||||
|
||||
<!-- Status -->
|
||||
<TextView
|
||||
android:id="@+id/tvLabelStatus"
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content"
|
||||
android:text="Status"
|
||||
android:textColor="@color/text_dark"
|
||||
android:textSize="12sp"
|
||||
android:layout_marginBottom="4dp"/>
|
||||
|
||||
<Spinner
|
||||
android:id="@+id/spinnerAppointmentStatus"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_marginBottom="8dp"/>
|
||||
|
||||
</LinearLayout>
|
||||
|
||||
<!-- Status -->
|
||||
<TextView
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content"
|
||||
android:text="Status"
|
||||
android:textColor="@color/text_dark"
|
||||
android:textSize="12sp"
|
||||
android:layout_marginBottom="4dp"/>
|
||||
|
||||
<Spinner
|
||||
android:id="@+id/spinnerAppointmentStatus"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_marginBottom="8dp"/>
|
||||
|
||||
</LinearLayout>
|
||||
|
||||
</ScrollView>
|
||||
|
||||
<LinearLayout
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:orientation="horizontal"
|
||||
android:background="@color/white"
|
||||
android:padding="16dp">
|
||||
|
||||
<Button
|
||||
android:id="@+id/btnApptBack"
|
||||
android:layout_width="0dp"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_weight="1"
|
||||
android:layout_marginEnd="8dp"
|
||||
android:text="Back"
|
||||
android:backgroundTint="@color/primary_medium"
|
||||
android:textColor="@color/white"/>
|
||||
|
||||
<Button
|
||||
android:id="@+id/btnSaveAppointment"
|
||||
android:layout_width="0dp"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_weight="1"
|
||||
android:layout_marginStart="8dp"
|
||||
android:text="Save"
|
||||
android:backgroundTint="@color/accent_coral"
|
||||
android:textColor="@color/white"/>
|
||||
|
||||
</LinearLayout>
|
||||
|
||||
</ScrollView>
|
||||
|
||||
<LinearLayout
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:orientation="horizontal"
|
||||
android:background="@color/white"
|
||||
android:padding="16dp">
|
||||
|
||||
<Button
|
||||
android:id="@+id/btnApptBack"
|
||||
android:layout_width="0dp"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_weight="1"
|
||||
android:layout_marginEnd="8dp"
|
||||
android:text="Back"
|
||||
android:backgroundTint="@color/primary_medium"
|
||||
android:textColor="@color/white"/>
|
||||
|
||||
<Button
|
||||
android:id="@+id/btnSaveAppointment"
|
||||
android:layout_width="0dp"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_weight="1"
|
||||
android:layout_marginStart="8dp"
|
||||
android:text="Save"
|
||||
android:backgroundTint="@color/accent_coral"
|
||||
android:textColor="@color/white"/>
|
||||
|
||||
</LinearLayout>
|
||||
|
||||
</LinearLayout>
|
||||
<ProgressBar
|
||||
android:id="@+id/progressBar"
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_gravity="center"
|
||||
android:visibility="gone"
|
||||
android:indeterminateTint="@color/accent_coral"/>
|
||||
|
||||
</FrameLayout>
|
||||
@@ -1,153 +1,240 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<androidx.drawerlayout.widget.DrawerLayout
|
||||
xmlns:android="http://schemas.android.com/apk/res/android"
|
||||
xmlns:app="http://schemas.android.com/apk/res-auto"
|
||||
android:id="@+id/chatDrawerLayout"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="match_parent">
|
||||
|
||||
<LinearLayout
|
||||
<RelativeLayout
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="match_parent"
|
||||
android:orientation="vertical"
|
||||
android:background="@color/background_grey">
|
||||
|
||||
<LinearLayout
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="56dp"
|
||||
android:background="@color/primary_dark"
|
||||
android:gravity="center_vertical"
|
||||
android:paddingStart="16dp"
|
||||
android:paddingEnd="16dp">
|
||||
android:layout_height="match_parent"
|
||||
android:orientation="vertical">
|
||||
|
||||
<ImageButton
|
||||
android:id="@+id/btnHamburger"
|
||||
android:layout_width="48dp"
|
||||
android:layout_height="48dp"
|
||||
android:src="@drawable/baseline_menu_36"
|
||||
android:background="?attr/selectableItemBackgroundBorderless"
|
||||
android:contentDescription="Open menu"/>
|
||||
<LinearLayout
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="56dp"
|
||||
android:background="@color/primary_dark"
|
||||
android:gravity="center_vertical"
|
||||
android:paddingStart="16dp"
|
||||
android:paddingEnd="16dp">
|
||||
|
||||
<TextView
|
||||
android:id="@+id/tvChatTitle"
|
||||
<ImageButton
|
||||
android:id="@+id/btnHamburger"
|
||||
android:layout_width="48dp"
|
||||
android:layout_height="48dp"
|
||||
android:src="@drawable/baseline_menu_36"
|
||||
android:background="?attr/selectableItemBackgroundBorderless"
|
||||
android:contentDescription="Open menu"/>
|
||||
|
||||
<TextView
|
||||
android:id="@+id/tvChatTitle"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:text="Customer Chat"
|
||||
android:textColor="@color/white"
|
||||
android:textSize="20sp"
|
||||
android:textStyle="bold"
|
||||
android:paddingStart="8dp"
|
||||
android:paddingEnd="8dp"/>
|
||||
|
||||
</LinearLayout>
|
||||
|
||||
<androidx.recyclerview.widget.RecyclerView
|
||||
android:id="@+id/rvMessages"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="0dp"
|
||||
android:layout_weight="1"
|
||||
android:padding="8dp"
|
||||
android:clipToPadding="false" />
|
||||
|
||||
<LinearLayout
|
||||
android:id="@+id/layoutAttachmentPreview"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:text="Customer Chat"
|
||||
android:textColor="@color/white"
|
||||
android:textSize="20sp"
|
||||
android:textStyle="bold"
|
||||
android:paddingStart="8dp"
|
||||
android:paddingEnd="8dp"/>
|
||||
android:orientation="horizontal"
|
||||
android:padding="8dp"
|
||||
android:background="#E0E0E0"
|
||||
android:gravity="center_vertical"
|
||||
android:visibility="gone">
|
||||
|
||||
<ImageView
|
||||
android:id="@+id/ivPreview"
|
||||
android:layout_width="48dp"
|
||||
android:layout_height="48dp"
|
||||
android:scaleType="centerCrop"
|
||||
android:layout_marginEnd="8dp"
|
||||
android:visibility="gone"/>
|
||||
|
||||
<TextView
|
||||
android:id="@+id/tvPreviewName"
|
||||
android:layout_width="0dp"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_weight="1"
|
||||
android:ellipsize="middle"
|
||||
android:singleLine="true"
|
||||
android:textColor="@color/text_dark"/>
|
||||
|
||||
<ImageButton
|
||||
android:id="@+id/btnRemoveAttachment"
|
||||
android:layout_width="40dp"
|
||||
android:layout_height="40dp"
|
||||
android:src="@android:drawable/ic_menu_close_clear_cancel"
|
||||
android:background="?attr/selectableItemBackgroundBorderless"
|
||||
android:contentDescription="Remove attachment"/>
|
||||
|
||||
</LinearLayout>
|
||||
|
||||
<LinearLayout
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:orientation="horizontal"
|
||||
android:padding="8dp"
|
||||
android:gravity="center_vertical"
|
||||
android:background="@color/white">
|
||||
|
||||
<ImageButton
|
||||
android:id="@+id/btnAttach"
|
||||
android:layout_width="48dp"
|
||||
android:layout_height="48dp"
|
||||
android:src="@android:drawable/ic_menu_add"
|
||||
android:background="?attr/selectableItemBackgroundBorderless"
|
||||
android:contentDescription="Attach file"
|
||||
android:layout_marginEnd="4dp"/>
|
||||
|
||||
<EditText
|
||||
android:id="@+id/etMessage"
|
||||
android:layout_width="0dp"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_weight="1"
|
||||
android:hint="Type a message..."
|
||||
android:inputType="text"
|
||||
android:imeOptions="actionSend"
|
||||
android:layout_marginEnd="8dp"
|
||||
android:textColor="@color/text_dark"/>
|
||||
|
||||
<Button
|
||||
android:id="@+id/btnSend"
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content"
|
||||
android:text="Send"
|
||||
android:backgroundTint="@color/accent_coral"
|
||||
android:textColor="@color/white"/>
|
||||
|
||||
</LinearLayout>
|
||||
|
||||
</LinearLayout>
|
||||
|
||||
<androidx.recyclerview.widget.RecyclerView
|
||||
android:id="@+id/rvMessages"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="0dp"
|
||||
android:layout_weight="1"
|
||||
android:padding="8dp"
|
||||
android:clipToPadding="false" />
|
||||
|
||||
<LinearLayout
|
||||
android:id="@+id/layoutAttachmentPreview"
|
||||
android:layout_width="match_parent"
|
||||
<ProgressBar
|
||||
android:id="@+id/progressBar"
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content"
|
||||
android:orientation="horizontal"
|
||||
android:padding="8dp"
|
||||
android:background="#E0E0E0"
|
||||
android:gravity="center_vertical"
|
||||
android:visibility="gone">
|
||||
android:layout_centerInParent="true"
|
||||
android:visibility="gone"
|
||||
android:indeterminateTint="@color/accent_coral"/>
|
||||
|
||||
<ImageView
|
||||
android:id="@+id/ivPreview"
|
||||
android:layout_width="48dp"
|
||||
android:layout_height="48dp"
|
||||
android:scaleType="centerCrop"
|
||||
android:layout_marginEnd="8dp"
|
||||
android:visibility="gone"/>
|
||||
</RelativeLayout>
|
||||
|
||||
<TextView
|
||||
android:id="@+id/tvPreviewName"
|
||||
android:layout_width="0dp"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_weight="1"
|
||||
android:ellipsize="middle"
|
||||
android:singleLine="true"
|
||||
android:textColor="@color/text_dark"/>
|
||||
|
||||
<ImageButton
|
||||
android:id="@+id/btnRemoveAttachment"
|
||||
android:layout_width="40dp"
|
||||
android:layout_height="40dp"
|
||||
android:src="@android:drawable/ic_menu_close_clear_cancel"
|
||||
android:background="?attr/selectableItemBackgroundBorderless"
|
||||
android:contentDescription="Remove attachment"/>
|
||||
|
||||
</LinearLayout>
|
||||
|
||||
<LinearLayout
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:orientation="horizontal"
|
||||
android:padding="8dp"
|
||||
android:gravity="center_vertical"
|
||||
android:background="@color/white">
|
||||
|
||||
<ImageButton
|
||||
android:id="@+id/btnAttach"
|
||||
android:layout_width="48dp"
|
||||
android:layout_height="48dp"
|
||||
android:src="@android:drawable/ic_menu_add"
|
||||
android:background="?attr/selectableItemBackgroundBorderless"
|
||||
android:contentDescription="Attach file"
|
||||
android:layout_marginEnd="4dp"/>
|
||||
|
||||
<EditText
|
||||
android:id="@+id/etMessage"
|
||||
android:layout_width="0dp"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_weight="1"
|
||||
android:hint="Type a message..."
|
||||
android:inputType="text"
|
||||
android:imeOptions="actionSend"
|
||||
android:layout_marginEnd="8dp"
|
||||
android:textColor="@color/text_dark"/>
|
||||
|
||||
<Button
|
||||
android:id="@+id/btnSend"
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content"
|
||||
android:text="Send"
|
||||
android:backgroundTint="@color/accent_coral"
|
||||
android:textColor="@color/white"/>
|
||||
|
||||
</LinearLayout>
|
||||
|
||||
</LinearLayout>
|
||||
|
||||
<LinearLayout
|
||||
<androidx.core.widget.NestedScrollView
|
||||
android:id="@+id/chatListDrawer"
|
||||
android:layout_width="260dp"
|
||||
android:layout_height="match_parent"
|
||||
android:layout_gravity="start"
|
||||
android:orientation="vertical"
|
||||
android:background="@color/primary_dark"
|
||||
android:padding="16dp">
|
||||
android:background="@color/primary_dark">
|
||||
|
||||
<TextView
|
||||
<LinearLayout
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:text="Active Chats"
|
||||
android:textColor="@color/white"
|
||||
android:textSize="18sp"
|
||||
android:textStyle="bold"
|
||||
android:layout_marginBottom="16dp"/>
|
||||
android:orientation="vertical"
|
||||
android:paddingTop="24dp">
|
||||
|
||||
<androidx.recyclerview.widget.RecyclerView
|
||||
android:id="@+id/rvChatList"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="match_parent"/>
|
||||
<LinearLayout
|
||||
android:id="@+id/headerActiveChats"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:orientation="horizontal"
|
||||
android:gravity="center_vertical"
|
||||
android:clickable="true"
|
||||
android:focusable="true"
|
||||
android:background="?attr/selectableItemBackground"
|
||||
android:paddingStart="16dp"
|
||||
android:paddingEnd="16dp"
|
||||
android:paddingBottom="8dp">
|
||||
|
||||
</LinearLayout>
|
||||
<TextView
|
||||
android:layout_width="0dp"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_weight="1"
|
||||
android:text="ACTIVE CHATS"
|
||||
android:textColor="@color/text_light"
|
||||
android:textSize="11sp"
|
||||
android:letterSpacing="0.15"
|
||||
android:textStyle="bold"/>
|
||||
|
||||
<ImageView
|
||||
android:id="@+id/ivActiveChevron"
|
||||
android:layout_width="20dp"
|
||||
android:layout_height="20dp"
|
||||
android:src="@android:drawable/arrow_up_float"
|
||||
app:tint="@color/text_light"/>
|
||||
</LinearLayout>
|
||||
|
||||
<androidx.recyclerview.widget.RecyclerView
|
||||
android:id="@+id/rvActiveChats"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:nestedScrollingEnabled="false"
|
||||
android:layout_marginBottom="16dp"
|
||||
android:paddingStart="8dp"
|
||||
android:paddingEnd="8dp"/>
|
||||
|
||||
<LinearLayout
|
||||
android:id="@+id/headerClosedChats"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:orientation="horizontal"
|
||||
android:gravity="center_vertical"
|
||||
android:clickable="true"
|
||||
android:focusable="true"
|
||||
android:background="?attr/selectableItemBackground"
|
||||
android:paddingStart="16dp"
|
||||
android:paddingEnd="16dp"
|
||||
android:paddingTop="8dp"
|
||||
android:paddingBottom="8dp">
|
||||
|
||||
<TextView
|
||||
android:layout_width="0dp"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_weight="1"
|
||||
android:text="CLOSED CHATS"
|
||||
android:textColor="@color/text_light"
|
||||
android:textSize="11sp"
|
||||
android:letterSpacing="0.15"
|
||||
android:textStyle="bold"/>
|
||||
|
||||
<ImageView
|
||||
android:id="@+id/ivClosedChevron"
|
||||
android:layout_width="20dp"
|
||||
android:layout_height="20dp"
|
||||
android:src="@android:drawable/arrow_down_float"
|
||||
app:tint="@color/text_light"/>
|
||||
</LinearLayout>
|
||||
|
||||
<androidx.recyclerview.widget.RecyclerView
|
||||
android:id="@+id/rvClosedChats"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:nestedScrollingEnabled="false"
|
||||
android:visibility="gone"
|
||||
android:paddingStart="8dp"
|
||||
android:paddingEnd="8dp"/>
|
||||
|
||||
</LinearLayout>
|
||||
</androidx.core.widget.NestedScrollView>
|
||||
|
||||
</androidx.drawerlayout.widget.DrawerLayout>
|
||||
@@ -1,155 +1,169 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
|
||||
<FrameLayout xmlns:android="http://schemas.android.com/apk/res/android"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="match_parent"
|
||||
android:orientation="vertical"
|
||||
android:background="@color/background_grey">
|
||||
android:layout_height="match_parent">
|
||||
|
||||
<!-- Header -->
|
||||
<LinearLayout
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="56dp"
|
||||
android:background="@color/primary_dark"
|
||||
android:gravity="center_vertical"
|
||||
android:paddingStart="16dp"
|
||||
android:paddingEnd="16dp"
|
||||
android:orientation="horizontal">
|
||||
|
||||
<TextView
|
||||
android:id="@+id/tvInventoryMode"
|
||||
android:layout_width="0dp"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_weight="1"
|
||||
android:text="Add Inventory"
|
||||
android:textColor="@color/white"
|
||||
android:textSize="20sp"
|
||||
android:textStyle="bold"/>
|
||||
|
||||
<Button
|
||||
android:id="@+id/btnDeleteInventory"
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content"
|
||||
android:backgroundTint="@color/accent_coral"
|
||||
android:text="Delete"
|
||||
android:textColor="@color/white"
|
||||
android:visibility="gone"/>
|
||||
|
||||
</LinearLayout>
|
||||
|
||||
<ScrollView
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="0dp"
|
||||
android:layout_weight="1">
|
||||
android:layout_height="match_parent"
|
||||
android:orientation="vertical"
|
||||
android:background="@color/background_grey">
|
||||
|
||||
<!-- Header -->
|
||||
<LinearLayout
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:orientation="vertical"
|
||||
android:padding="24dp">
|
||||
android:layout_height="56dp"
|
||||
android:background="@color/primary_dark"
|
||||
android:gravity="center_vertical"
|
||||
android:paddingStart="16dp"
|
||||
android:paddingEnd="16dp"
|
||||
android:orientation="horizontal">
|
||||
|
||||
<TextView
|
||||
android:id="@+id/tvInventoryMode"
|
||||
android:layout_width="0dp"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_weight="1"
|
||||
android:text="Add Inventory"
|
||||
android:textColor="@color/white"
|
||||
android:textSize="20sp"
|
||||
android:textStyle="bold"/>
|
||||
|
||||
<Button
|
||||
android:id="@+id/btnDeleteInventory"
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content"
|
||||
android:backgroundTint="@color/accent_coral"
|
||||
android:text="Delete"
|
||||
android:textColor="@color/white"
|
||||
android:visibility="gone"/>
|
||||
|
||||
</LinearLayout>
|
||||
|
||||
<ScrollView
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="0dp"
|
||||
android:layout_weight="1">
|
||||
|
||||
<LinearLayout
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:orientation="vertical"
|
||||
android:background="@drawable/rounded_card"
|
||||
android:padding="16dp">
|
||||
android:padding="24dp">
|
||||
|
||||
<!-- Inventory ID — edit mode only -->
|
||||
<TextView
|
||||
android:id="@+id/tvInventoryId"
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content"
|
||||
android:text="Inventory ID: —"
|
||||
android:textColor="@color/text_light"
|
||||
android:textSize="11sp"
|
||||
android:textStyle="italic"
|
||||
android:layout_gravity="end"
|
||||
android:layout_marginBottom="12dp"
|
||||
android:visibility="gone"/>
|
||||
|
||||
<!-- Store selection label -->
|
||||
<TextView
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content"
|
||||
android:text="Store"
|
||||
android:textColor="@color/text_dark"
|
||||
android:textSize="12sp"
|
||||
android:layout_marginBottom="4dp"/>
|
||||
|
||||
<!-- Store Spinner -->
|
||||
<Spinner
|
||||
android:id="@+id/spinnerInventoryStore"
|
||||
<LinearLayout
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_marginBottom="16dp"/>
|
||||
android:orientation="vertical"
|
||||
android:background="@drawable/rounded_card"
|
||||
android:padding="16dp">
|
||||
|
||||
<!-- Product selection label -->
|
||||
<TextView
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content"
|
||||
android:text="Product"
|
||||
android:textColor="@color/text_dark"
|
||||
android:textSize="12sp"
|
||||
android:layout_marginBottom="4dp"/>
|
||||
<!-- Inventory ID — edit mode only -->
|
||||
<TextView
|
||||
android:id="@+id/tvInventoryId"
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content"
|
||||
android:text="Inventory ID: —"
|
||||
android:textColor="@color/text_light"
|
||||
android:textSize="11sp"
|
||||
android:textStyle="italic"
|
||||
android:layout_gravity="end"
|
||||
android:layout_marginBottom="12dp"
|
||||
android:visibility="gone"/>
|
||||
|
||||
<!-- Product Spinner -->
|
||||
<Spinner
|
||||
android:id="@+id/spinnerInventoryProduct"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_marginBottom="16dp"/>
|
||||
<!-- Store selection label -->
|
||||
<TextView
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content"
|
||||
android:text="Store"
|
||||
android:textColor="@color/text_dark"
|
||||
android:textSize="12sp"
|
||||
android:layout_marginBottom="4dp"/>
|
||||
|
||||
<!-- Quantity label -->
|
||||
<TextView
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content"
|
||||
android:text="Quantity"
|
||||
android:textColor="@color/text_dark"
|
||||
android:textSize="12sp"
|
||||
android:layout_marginBottom="4dp"/>
|
||||
<!-- Store Spinner -->
|
||||
<Spinner
|
||||
android:id="@+id/spinnerInventoryStore"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_marginBottom="16dp"/>
|
||||
|
||||
<!-- Quantity input -->
|
||||
<EditText
|
||||
android:id="@+id/etQuantity"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:hint="Enter quantity"
|
||||
android:inputType="number"/>
|
||||
<!-- Product selection label -->
|
||||
<TextView
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content"
|
||||
android:text="Product"
|
||||
android:textColor="@color/text_dark"
|
||||
android:textSize="12sp"
|
||||
android:layout_marginBottom="4dp"/>
|
||||
|
||||
<!-- Product Spinner -->
|
||||
<Spinner
|
||||
android:id="@+id/spinnerInventoryProduct"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_marginBottom="16dp"/>
|
||||
|
||||
<!-- Quantity label -->
|
||||
<TextView
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content"
|
||||
android:text="Quantity"
|
||||
android:textColor="@color/text_dark"
|
||||
android:textSize="12sp"
|
||||
android:layout_marginBottom="4dp"/>
|
||||
|
||||
<!-- Quantity input -->
|
||||
<EditText
|
||||
android:id="@+id/etQuantity"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:hint="Enter quantity"
|
||||
android:inputType="number"/>
|
||||
|
||||
</LinearLayout>
|
||||
|
||||
</LinearLayout>
|
||||
|
||||
</ScrollView>
|
||||
|
||||
<!-- Bottom buttons -->
|
||||
<LinearLayout
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:orientation="horizontal"
|
||||
android:background="@color/white"
|
||||
android:padding="16dp">
|
||||
|
||||
<Button
|
||||
android:id="@+id/btnInventoryBack"
|
||||
android:layout_width="0dp"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_weight="1"
|
||||
android:layout_marginEnd="8dp"
|
||||
android:text="Back"
|
||||
android:backgroundTint="@color/primary_medium"
|
||||
android:textColor="@color/white"/>
|
||||
|
||||
<Button
|
||||
android:id="@+id/btnSaveInventory"
|
||||
android:layout_width="0dp"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_weight="1"
|
||||
android:layout_marginStart="8dp"
|
||||
android:text="Save"
|
||||
android:backgroundTint="@color/accent_coral"
|
||||
android:textColor="@color/white"/>
|
||||
|
||||
</LinearLayout>
|
||||
|
||||
</ScrollView>
|
||||
|
||||
<!-- Bottom buttons -->
|
||||
<LinearLayout
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:orientation="horizontal"
|
||||
android:background="@color/white"
|
||||
android:padding="16dp">
|
||||
|
||||
<Button
|
||||
android:id="@+id/btnInventoryBack"
|
||||
android:layout_width="0dp"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_weight="1"
|
||||
android:layout_marginEnd="8dp"
|
||||
android:text="Back"
|
||||
android:backgroundTint="@color/primary_medium"
|
||||
android:textColor="@color/white"/>
|
||||
|
||||
<Button
|
||||
android:id="@+id/btnSaveInventory"
|
||||
android:layout_width="0dp"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_weight="1"
|
||||
android:layout_marginStart="8dp"
|
||||
android:text="Save"
|
||||
android:backgroundTint="@color/accent_coral"
|
||||
android:textColor="@color/white"/>
|
||||
|
||||
</LinearLayout>
|
||||
|
||||
</LinearLayout>
|
||||
<ProgressBar
|
||||
android:id="@+id/progressBar"
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_gravity="center"
|
||||
android:visibility="gone"
|
||||
android:indeterminateTint="@color/accent_coral"/>
|
||||
|
||||
</FrameLayout>
|
||||
@@ -1,229 +1,244 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
|
||||
<FrameLayout xmlns:android="http://schemas.android.com/apk/res/android"
|
||||
xmlns:app="http://schemas.android.com/apk/res-auto"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="match_parent"
|
||||
android:orientation="vertical"
|
||||
android:background="@color/background_grey">
|
||||
android:layout_height="match_parent">
|
||||
|
||||
<LinearLayout
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="56dp"
|
||||
android:background="@color/primary_dark"
|
||||
android:gravity="center_vertical"
|
||||
android:paddingStart="16dp"
|
||||
android:paddingEnd="16dp"
|
||||
android:orientation="horizontal">
|
||||
|
||||
<TextView
|
||||
android:id="@+id/tvMode"
|
||||
android:layout_width="0dp"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_weight="1"
|
||||
android:text="Add Pet"
|
||||
android:textColor="@color/white"
|
||||
android:textSize="20sp"
|
||||
android:textStyle="bold" />
|
||||
|
||||
<Button
|
||||
android:id="@+id/btnDeletePet"
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_marginStart="8dp"
|
||||
android:backgroundTint="@color/accent_coral"
|
||||
android:text="Delete"
|
||||
android:textColor="@color/white" />
|
||||
|
||||
</LinearLayout>
|
||||
|
||||
<ScrollView
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="0dp"
|
||||
android:layout_weight="1">
|
||||
android:layout_height="match_parent"
|
||||
android:orientation="vertical"
|
||||
android:background="@color/background_grey">
|
||||
|
||||
<LinearLayout
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:orientation="vertical"
|
||||
android:padding="24dp">
|
||||
android:layout_height="56dp"
|
||||
android:background="@color/primary_dark"
|
||||
android:gravity="center_vertical"
|
||||
android:paddingStart="16dp"
|
||||
android:paddingEnd="16dp"
|
||||
android:orientation="horizontal">
|
||||
|
||||
<TextView
|
||||
android:id="@+id/tvMode"
|
||||
android:layout_width="0dp"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_weight="1"
|
||||
android:text="Add Pet"
|
||||
android:textColor="@color/white"
|
||||
android:textSize="20sp"
|
||||
android:textStyle="bold" />
|
||||
|
||||
<Button
|
||||
android:id="@+id/btnDeletePet"
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_marginStart="8dp"
|
||||
android:backgroundTint="@color/accent_coral"
|
||||
android:text="Delete"
|
||||
android:textColor="@color/white" />
|
||||
|
||||
</LinearLayout>
|
||||
|
||||
<ScrollView
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="0dp"
|
||||
android:layout_weight="1">
|
||||
|
||||
<LinearLayout
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:orientation="vertical"
|
||||
android:background="@drawable/rounded_card"
|
||||
android:padding="16dp"
|
||||
android:layout_marginBottom="16dp">
|
||||
android:padding="24dp">
|
||||
|
||||
<TextView
|
||||
android:id="@+id/tvPetId"
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content"
|
||||
android:text="Pet ID: #0"
|
||||
android:textColor="@color/text_light"
|
||||
android:textSize="11sp"
|
||||
android:textStyle="italic"
|
||||
android:layout_gravity="end"
|
||||
android:layout_marginBottom="8dp"/>
|
||||
|
||||
<TextView
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content"
|
||||
android:text="Pet Name"
|
||||
android:textColor="@color/text_dark"
|
||||
android:textSize="12sp"
|
||||
android:layout_marginBottom="4dp"/>
|
||||
|
||||
<EditText
|
||||
android:id="@+id/etPetName"
|
||||
<LinearLayout
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:hint="Enter pet name"
|
||||
android:inputType="text"
|
||||
android:layout_marginBottom="16dp"
|
||||
android:textColor="@color/text_dark"/>
|
||||
android:orientation="vertical"
|
||||
android:background="@drawable/rounded_card"
|
||||
android:padding="16dp"
|
||||
android:layout_marginBottom="16dp">
|
||||
|
||||
<TextView
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content"
|
||||
android:text="Species"
|
||||
android:textColor="@color/text_dark"
|
||||
android:textSize="12sp"
|
||||
android:layout_marginBottom="4dp"/>
|
||||
<TextView
|
||||
android:id="@+id/tvPetId"
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content"
|
||||
android:text="Pet ID: #0"
|
||||
android:textColor="@color/text_light"
|
||||
android:textSize="11sp"
|
||||
android:textStyle="italic"
|
||||
android:layout_gravity="end"
|
||||
android:layout_marginBottom="8dp"/>
|
||||
|
||||
<EditText
|
||||
android:id="@+id/etPetSpecies"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:hint="e.g. Dog, Cat, Bird"
|
||||
android:inputType="text"
|
||||
android:layout_marginBottom="16dp"
|
||||
android:textColor="@color/text_dark"/>
|
||||
<TextView
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content"
|
||||
android:text="Pet Name"
|
||||
android:textColor="@color/text_dark"
|
||||
android:textSize="12sp"
|
||||
android:layout_marginBottom="4dp"/>
|
||||
|
||||
<TextView
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content"
|
||||
android:text="Breed"
|
||||
android:textColor="@color/text_dark"
|
||||
android:textSize="12sp"
|
||||
android:layout_marginBottom="4dp"/>
|
||||
<EditText
|
||||
android:id="@+id/etPetName"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:hint="Enter pet name"
|
||||
android:inputType="text"
|
||||
android:layout_marginBottom="16dp"
|
||||
android:textColor="@color/text_dark"/>
|
||||
|
||||
<EditText
|
||||
android:id="@+id/etPetBreed"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:hint="Enter breed"
|
||||
android:inputType="text"
|
||||
android:layout_marginBottom="16dp"
|
||||
android:textColor="@color/text_dark"/>
|
||||
<TextView
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content"
|
||||
android:text="Species"
|
||||
android:textColor="@color/text_dark"
|
||||
android:textSize="12sp"
|
||||
android:layout_marginBottom="4dp"/>
|
||||
|
||||
<TextView
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content"
|
||||
android:text="Age"
|
||||
android:textColor="@color/text_dark"
|
||||
android:textSize="12sp"
|
||||
android:layout_marginBottom="4dp"/>
|
||||
<EditText
|
||||
android:id="@+id/etPetSpecies"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:hint="e.g. Dog, Cat, Bird"
|
||||
android:inputType="text"
|
||||
android:layout_marginBottom="16dp"
|
||||
android:textColor="@color/text_dark"/>
|
||||
|
||||
<EditText
|
||||
android:id="@+id/etPetAge"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:hint="Enter age"
|
||||
android:inputType="number"
|
||||
android:layout_marginBottom="16dp"
|
||||
android:textColor="@color/text_dark"/>
|
||||
<TextView
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content"
|
||||
android:text="Breed"
|
||||
android:textColor="@color/text_dark"
|
||||
android:textSize="12sp"
|
||||
android:layout_marginBottom="4dp"/>
|
||||
|
||||
<TextView
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content"
|
||||
android:text="Price"
|
||||
android:textColor="@color/text_dark"
|
||||
android:textSize="12sp"
|
||||
android:layout_marginBottom="4dp"/>
|
||||
<EditText
|
||||
android:id="@+id/etPetBreed"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:hint="Enter breed"
|
||||
android:inputType="text"
|
||||
android:layout_marginBottom="16dp"
|
||||
android:textColor="@color/text_dark"/>
|
||||
|
||||
<EditText
|
||||
android:id="@+id/etPetPrice"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:hint="Enter price"
|
||||
android:inputType="numberDecimal"
|
||||
android:layout_marginBottom="16dp"
|
||||
android:textColor="@color/text_dark"/>
|
||||
<TextView
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content"
|
||||
android:text="Age"
|
||||
android:textColor="@color/text_dark"
|
||||
android:textSize="12sp"
|
||||
android:layout_marginBottom="4dp"/>
|
||||
|
||||
<TextView
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content"
|
||||
android:text="Status"
|
||||
android:textColor="@color/text_dark"
|
||||
android:textSize="12sp"
|
||||
android:layout_marginBottom="4dp"/>
|
||||
<EditText
|
||||
android:id="@+id/etPetAge"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:hint="Enter age"
|
||||
android:inputType="number"
|
||||
android:layout_marginBottom="16dp"
|
||||
android:textColor="@color/text_dark"/>
|
||||
|
||||
<Spinner
|
||||
android:id="@+id/spinnerPetStatus"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_marginBottom="16dp"/>
|
||||
<TextView
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content"
|
||||
android:text="Price"
|
||||
android:textColor="@color/text_dark"
|
||||
android:textSize="12sp"
|
||||
android:layout_marginBottom="4dp"/>
|
||||
|
||||
<TextView
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content"
|
||||
android:text="Owner"
|
||||
android:textColor="@color/text_dark"
|
||||
android:textSize="12sp"
|
||||
android:layout_marginBottom="4dp"/>
|
||||
<EditText
|
||||
android:id="@+id/etPetPrice"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:hint="Enter price"
|
||||
android:inputType="numberDecimal"
|
||||
android:layout_marginBottom="16dp"
|
||||
android:textColor="@color/text_dark"/>
|
||||
|
||||
<Spinner
|
||||
android:id="@+id/spinnerCustomer"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_marginBottom="16dp"/>
|
||||
<TextView
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content"
|
||||
android:text="Status"
|
||||
android:textColor="@color/text_dark"
|
||||
android:textSize="12sp"
|
||||
android:layout_marginBottom="4dp"/>
|
||||
|
||||
<TextView
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content"
|
||||
android:text="Store"
|
||||
android:textColor="@color/text_dark"
|
||||
android:textSize="12sp"
|
||||
android:layout_marginBottom="4dp"/>
|
||||
<Spinner
|
||||
android:id="@+id/spinnerPetStatus"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_marginBottom="16dp"/>
|
||||
|
||||
<Spinner
|
||||
android:id="@+id/spinnerStore"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"/>
|
||||
<TextView
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content"
|
||||
android:text="Owner"
|
||||
android:textColor="@color/text_dark"
|
||||
android:textSize="12sp"
|
||||
android:layout_marginBottom="4dp"/>
|
||||
|
||||
<Spinner
|
||||
android:id="@+id/spinnerCustomer"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_marginBottom="16dp"/>
|
||||
|
||||
<TextView
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content"
|
||||
android:text="Store"
|
||||
android:textColor="@color/text_dark"
|
||||
android:textSize="12sp"
|
||||
android:layout_marginBottom="4dp"/>
|
||||
|
||||
<Spinner
|
||||
android:id="@+id/spinnerStore"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"/>
|
||||
|
||||
</LinearLayout>
|
||||
|
||||
</LinearLayout>
|
||||
|
||||
</ScrollView>
|
||||
|
||||
<LinearLayout
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:orientation="horizontal"
|
||||
android:background="@color/white"
|
||||
android:padding="16dp">
|
||||
|
||||
<Button
|
||||
android:id="@+id/btnBack"
|
||||
android:layout_width="0dp"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_weight="1"
|
||||
android:layout_marginEnd="8dp"
|
||||
android:text="Back"
|
||||
android:backgroundTint="@color/primary_medium"
|
||||
android:textColor="@color/white"/>
|
||||
|
||||
<Button
|
||||
android:id="@+id/btnSavePet"
|
||||
android:layout_width="0dp"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_marginStart="8dp"
|
||||
android:layout_weight="1"
|
||||
android:backgroundTint="@color/accent_coral"
|
||||
android:text="Save"
|
||||
android:textColor="@color/white" />
|
||||
|
||||
</LinearLayout>
|
||||
|
||||
</ScrollView>
|
||||
|
||||
<LinearLayout
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:orientation="horizontal"
|
||||
android:background="@color/white"
|
||||
android:padding="16dp">
|
||||
|
||||
<Button
|
||||
android:id="@+id/btnBack"
|
||||
android:layout_width="0dp"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_weight="1"
|
||||
android:layout_marginEnd="8dp"
|
||||
android:text="Back"
|
||||
android:backgroundTint="@color/primary_medium"
|
||||
android:textColor="@color/white"/>
|
||||
|
||||
<Button
|
||||
android:id="@+id/btnSavePet"
|
||||
android:layout_width="0dp"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_marginStart="8dp"
|
||||
android:layout_weight="1"
|
||||
android:backgroundTint="@color/accent_coral"
|
||||
android:text="Save"
|
||||
android:textColor="@color/white" />
|
||||
|
||||
</LinearLayout>
|
||||
|
||||
</LinearLayout>
|
||||
<ProgressBar
|
||||
android:id="@+id/progressBar"
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_gravity="center"
|
||||
android:visibility="gone"
|
||||
android:indeterminateTint="@color/accent_coral"/>
|
||||
|
||||
</FrameLayout>
|
||||
@@ -1,306 +1,320 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
|
||||
<FrameLayout xmlns:android="http://schemas.android.com/apk/res/android"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="match_parent"
|
||||
android:orientation="vertical"
|
||||
android:background="@color/background_grey">
|
||||
android:layout_height="match_parent">
|
||||
|
||||
<LinearLayout
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="0dp"
|
||||
android:layout_weight="1"
|
||||
android:layout_height="match_parent"
|
||||
android:orientation="vertical"
|
||||
android:background="@color/primary_dark">
|
||||
|
||||
<LinearLayout
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="56dp"
|
||||
android:orientation="horizontal"
|
||||
android:gravity="center_vertical"
|
||||
android:paddingStart="16dp"
|
||||
android:paddingEnd="16dp">
|
||||
|
||||
<TextView
|
||||
android:layout_width="0dp"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_weight="1"
|
||||
android:text="Pet Profile"
|
||||
android:textColor="@color/white"
|
||||
android:textSize="20sp"
|
||||
android:textStyle="bold"/>
|
||||
|
||||
<Button
|
||||
android:id="@+id/btnEditPet"
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content"
|
||||
android:text="Edit"
|
||||
android:backgroundTint="@color/accent_coral"
|
||||
android:textColor="@color/white"/>
|
||||
|
||||
</LinearLayout>
|
||||
android:background="@color/background_grey">
|
||||
|
||||
<LinearLayout
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="0dp"
|
||||
android:layout_weight="1"
|
||||
android:orientation="vertical"
|
||||
android:gravity="center">
|
||||
android:background="@color/primary_dark">
|
||||
|
||||
<ImageView
|
||||
android:id="@+id/imgPet"
|
||||
android:layout_width="146dp"
|
||||
android:layout_height="140dp"
|
||||
android:layout_marginBottom="12dp"
|
||||
android:background="@drawable/circle_image"
|
||||
android:clipToOutline="true"
|
||||
android:scaleType="centerCrop"
|
||||
android:src="@drawable/placeholder" />
|
||||
<LinearLayout
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="56dp"
|
||||
android:orientation="horizontal"
|
||||
android:gravity="center_vertical"
|
||||
android:paddingStart="16dp"
|
||||
android:paddingEnd="16dp">
|
||||
|
||||
<Button
|
||||
android:id="@+id/btnChangePhoto"
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content"
|
||||
android:text="Change Photo"
|
||||
style="@style/Widget.Material3.Button.TextButton"
|
||||
android:textColor="@color/text_light"
|
||||
android:layout_marginBottom="8dp"/>
|
||||
<TextView
|
||||
android:layout_width="0dp"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_weight="1"
|
||||
android:text="Pet Profile"
|
||||
android:textColor="@color/white"
|
||||
android:textSize="20sp"
|
||||
android:textStyle="bold"/>
|
||||
|
||||
<TextView
|
||||
android:id="@+id/tvPetName"
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content"
|
||||
android:text="NAME"
|
||||
android:textColor="@color/white"
|
||||
android:textSize="26sp"
|
||||
android:textStyle="bold"/>
|
||||
<Button
|
||||
android:id="@+id/btnEditPet"
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content"
|
||||
android:text="Edit"
|
||||
android:backgroundTint="@color/accent_coral"
|
||||
android:textColor="@color/white"/>
|
||||
|
||||
</LinearLayout>
|
||||
|
||||
<LinearLayout
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="0dp"
|
||||
android:layout_weight="1"
|
||||
android:orientation="vertical"
|
||||
android:gravity="center">
|
||||
|
||||
<ImageView
|
||||
android:id="@+id/imgPet"
|
||||
android:layout_width="146dp"
|
||||
android:layout_height="140dp"
|
||||
android:layout_marginBottom="12dp"
|
||||
android:background="@drawable/circle_image"
|
||||
android:clipToOutline="true"
|
||||
android:scaleType="centerCrop"
|
||||
android:src="@drawable/placeholder" />
|
||||
|
||||
<Button
|
||||
android:id="@+id/btnChangePhoto"
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content"
|
||||
android:text="Change Photo"
|
||||
style="@style/Widget.Material3.Button.TextButton"
|
||||
android:textColor="@color/text_light"
|
||||
android:layout_marginBottom="8dp"/>
|
||||
|
||||
<TextView
|
||||
android:id="@+id/tvPetName"
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content"
|
||||
android:text="NAME"
|
||||
android:textColor="@color/white"
|
||||
android:textSize="26sp"
|
||||
android:textStyle="bold"/>
|
||||
|
||||
</LinearLayout>
|
||||
|
||||
</LinearLayout>
|
||||
|
||||
</LinearLayout>
|
||||
<ScrollView
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="0dp"
|
||||
android:layout_weight="1">
|
||||
|
||||
<ScrollView
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="0dp"
|
||||
android:layout_weight="1">
|
||||
<LinearLayout
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:orientation="vertical"
|
||||
android:padding="16dp">
|
||||
|
||||
<LinearLayout
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="120dp"
|
||||
android:orientation="horizontal"
|
||||
android:layout_marginBottom="8dp">
|
||||
|
||||
<LinearLayout
|
||||
android:layout_width="0dp"
|
||||
android:layout_height="match_parent"
|
||||
android:layout_weight="1"
|
||||
android:orientation="vertical"
|
||||
android:gravity="center"
|
||||
android:background="@color/white"
|
||||
android:layout_marginEnd="8dp">
|
||||
|
||||
<TextView
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content"
|
||||
android:text="SPECIES"
|
||||
android:textSize="11sp"
|
||||
android:textColor="#888888"
|
||||
android:textAllCaps="true"
|
||||
android:layout_marginBottom="4dp"/>
|
||||
|
||||
<TextView
|
||||
android:id="@+id/tvPetSpecies"
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content"
|
||||
android:text="Dog"
|
||||
android:textSize="20sp"
|
||||
android:textStyle="bold"
|
||||
android:textColor="@color/text_dark"/>
|
||||
|
||||
</LinearLayout>
|
||||
|
||||
<LinearLayout
|
||||
android:layout_width="0dp"
|
||||
android:layout_height="match_parent"
|
||||
android:layout_weight="1"
|
||||
android:orientation="vertical"
|
||||
android:gravity="center"
|
||||
android:background="@color/white"
|
||||
android:layout_marginStart="8dp">
|
||||
|
||||
<TextView
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content"
|
||||
android:text="BREED"
|
||||
android:textSize="11sp"
|
||||
android:textColor="#888888"
|
||||
android:textAllCaps="true"
|
||||
android:layout_marginBottom="4dp"/>
|
||||
|
||||
<TextView
|
||||
android:id="@+id/tvPetBreed"
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content"
|
||||
android:text="Labrador"
|
||||
android:textSize="20sp"
|
||||
android:textStyle="bold"
|
||||
android:textColor="@color/text_dark"/>
|
||||
|
||||
</LinearLayout>
|
||||
|
||||
</LinearLayout>
|
||||
|
||||
<LinearLayout
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="120dp"
|
||||
android:orientation="horizontal"
|
||||
android:layout_marginTop="8dp">
|
||||
|
||||
<LinearLayout
|
||||
android:layout_width="0dp"
|
||||
android:layout_height="match_parent"
|
||||
android:layout_weight="1"
|
||||
android:orientation="vertical"
|
||||
android:gravity="center"
|
||||
android:background="@color/white"
|
||||
android:layout_marginEnd="8dp">
|
||||
|
||||
<TextView
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content"
|
||||
android:text="AGE"
|
||||
android:textSize="11sp"
|
||||
android:textColor="#888888"
|
||||
android:textAllCaps="true"
|
||||
android:layout_marginBottom="4dp"/>
|
||||
|
||||
<TextView
|
||||
android:id="@+id/tvPetAge"
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content"
|
||||
android:text="2 yr(s)"
|
||||
android:textSize="20sp"
|
||||
android:textStyle="bold"
|
||||
android:textColor="@color/text_dark"/>
|
||||
|
||||
</LinearLayout>
|
||||
|
||||
<LinearLayout
|
||||
android:layout_width="0dp"
|
||||
android:layout_height="match_parent"
|
||||
android:layout_weight="1"
|
||||
android:orientation="vertical"
|
||||
android:gravity="center"
|
||||
android:background="@color/white"
|
||||
android:layout_marginStart="8dp">
|
||||
|
||||
<TextView
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content"
|
||||
android:text="PRICE"
|
||||
android:textSize="11sp"
|
||||
android:textColor="#888888"
|
||||
android:textAllCaps="true"
|
||||
android:layout_marginBottom="4dp"/>
|
||||
|
||||
<TextView
|
||||
android:id="@+id/tvPetPrice"
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content"
|
||||
android:text="$500.00"
|
||||
android:textSize="20sp"
|
||||
android:textStyle="bold"
|
||||
android:textColor="@color/accent_coral"/>
|
||||
|
||||
</LinearLayout>
|
||||
|
||||
</LinearLayout>
|
||||
|
||||
<LinearLayout
|
||||
android:id="@+id/layoutPetOwner"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:orientation="vertical"
|
||||
android:gravity="center"
|
||||
android:background="@color/white"
|
||||
android:layout_marginTop="16dp"
|
||||
android:padding="16dp"
|
||||
android:visibility="gone">
|
||||
|
||||
<TextView
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content"
|
||||
android:text="OWNER"
|
||||
android:textSize="11sp"
|
||||
android:textColor="#888888"
|
||||
android:textAllCaps="true"
|
||||
android:layout_marginBottom="4dp"/>
|
||||
|
||||
<TextView
|
||||
android:id="@+id/tvPetOwner"
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content"
|
||||
android:text="No Owner"
|
||||
android:textSize="20sp"
|
||||
android:textStyle="bold"
|
||||
android:textColor="@color/text_dark"/>
|
||||
|
||||
</LinearLayout>
|
||||
|
||||
<LinearLayout
|
||||
android:id="@+id/layoutPetStore"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:orientation="vertical"
|
||||
android:gravity="center"
|
||||
android:background="@color/white"
|
||||
android:layout_marginTop="16dp"
|
||||
android:padding="16dp"
|
||||
android:visibility="gone">
|
||||
|
||||
<TextView
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content"
|
||||
android:text="STORE"
|
||||
android:textSize="11sp"
|
||||
android:textColor="#888888"
|
||||
android:textAllCaps="true"
|
||||
android:layout_marginBottom="4dp"/>
|
||||
|
||||
<TextView
|
||||
android:id="@+id/tvPetStore"
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content"
|
||||
android:text="No Store"
|
||||
android:textSize="20sp"
|
||||
android:textStyle="bold"
|
||||
android:textColor="@color/text_dark"/>
|
||||
|
||||
</LinearLayout>
|
||||
|
||||
</LinearLayout>
|
||||
|
||||
</ScrollView>
|
||||
|
||||
<LinearLayout
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:orientation="vertical"
|
||||
android:orientation="horizontal"
|
||||
android:padding="16dp">
|
||||
|
||||
<LinearLayout
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="120dp"
|
||||
android:orientation="horizontal"
|
||||
android:layout_marginBottom="8dp">
|
||||
|
||||
<LinearLayout
|
||||
android:layout_width="0dp"
|
||||
android:layout_height="match_parent"
|
||||
android:layout_weight="1"
|
||||
android:orientation="vertical"
|
||||
android:gravity="center"
|
||||
android:background="@color/white"
|
||||
android:layout_marginEnd="8dp">
|
||||
|
||||
<TextView
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content"
|
||||
android:text="SPECIES"
|
||||
android:textSize="11sp"
|
||||
android:textColor="#888888"
|
||||
android:textAllCaps="true"
|
||||
android:layout_marginBottom="4dp"/>
|
||||
|
||||
<TextView
|
||||
android:id="@+id/tvPetSpecies"
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content"
|
||||
android:text="Dog"
|
||||
android:textSize="20sp"
|
||||
android:textStyle="bold"
|
||||
android:textColor="@color/text_dark"/>
|
||||
|
||||
</LinearLayout>
|
||||
|
||||
<LinearLayout
|
||||
android:layout_width="0dp"
|
||||
android:layout_height="match_parent"
|
||||
android:layout_weight="1"
|
||||
android:orientation="vertical"
|
||||
android:gravity="center"
|
||||
android:background="@color/white"
|
||||
android:layout_marginStart="8dp">
|
||||
|
||||
<TextView
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content"
|
||||
android:text="BREED"
|
||||
android:textSize="11sp"
|
||||
android:textColor="#888888"
|
||||
android:textAllCaps="true"
|
||||
android:layout_marginBottom="4dp"/>
|
||||
|
||||
<TextView
|
||||
android:id="@+id/tvPetBreed"
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content"
|
||||
android:text="Labrador"
|
||||
android:textSize="20sp"
|
||||
android:textStyle="bold"
|
||||
android:textColor="@color/text_dark"/>
|
||||
|
||||
</LinearLayout>
|
||||
|
||||
</LinearLayout>
|
||||
|
||||
<LinearLayout
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="120dp"
|
||||
android:orientation="horizontal"
|
||||
android:layout_marginTop="8dp">
|
||||
|
||||
<LinearLayout
|
||||
android:layout_width="0dp"
|
||||
android:layout_height="match_parent"
|
||||
android:layout_weight="1"
|
||||
android:orientation="vertical"
|
||||
android:gravity="center"
|
||||
android:background="@color/white"
|
||||
android:layout_marginEnd="8dp">
|
||||
|
||||
<TextView
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content"
|
||||
android:text="AGE"
|
||||
android:textSize="11sp"
|
||||
android:textColor="#888888"
|
||||
android:textAllCaps="true"
|
||||
android:layout_marginBottom="4dp"/>
|
||||
|
||||
<TextView
|
||||
android:id="@+id/tvPetAge"
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content"
|
||||
android:text="2 yr(s)"
|
||||
android:textSize="20sp"
|
||||
android:textStyle="bold"
|
||||
android:textColor="@color/text_dark"/>
|
||||
|
||||
</LinearLayout>
|
||||
|
||||
<LinearLayout
|
||||
android:layout_width="0dp"
|
||||
android:layout_height="match_parent"
|
||||
android:layout_weight="1"
|
||||
android:orientation="vertical"
|
||||
android:gravity="center"
|
||||
android:background="@color/white"
|
||||
android:layout_marginStart="8dp">
|
||||
|
||||
<TextView
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content"
|
||||
android:text="PRICE"
|
||||
android:textSize="11sp"
|
||||
android:textColor="#888888"
|
||||
android:textAllCaps="true"
|
||||
android:layout_marginBottom="4dp"/>
|
||||
|
||||
<TextView
|
||||
android:id="@+id/tvPetPrice"
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content"
|
||||
android:text="$500.00"
|
||||
android:textSize="20sp"
|
||||
android:textStyle="bold"
|
||||
android:textColor="@color/accent_coral"/>
|
||||
|
||||
</LinearLayout>
|
||||
|
||||
</LinearLayout>
|
||||
|
||||
<LinearLayout
|
||||
android:id="@+id/layoutPetOwner"
|
||||
<Button
|
||||
android:id="@+id/btnBack"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:orientation="vertical"
|
||||
android:gravity="center"
|
||||
android:background="@color/white"
|
||||
android:layout_marginTop="16dp"
|
||||
android:padding="16dp"
|
||||
android:visibility="gone">
|
||||
|
||||
<TextView
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content"
|
||||
android:text="OWNER"
|
||||
android:textSize="11sp"
|
||||
android:textColor="#888888"
|
||||
android:textAllCaps="true"
|
||||
android:layout_marginBottom="4dp"/>
|
||||
|
||||
<TextView
|
||||
android:id="@+id/tvPetOwner"
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content"
|
||||
android:text="No Owner"
|
||||
android:textSize="20sp"
|
||||
android:textStyle="bold"
|
||||
android:textColor="@color/text_dark"/>
|
||||
|
||||
</LinearLayout>
|
||||
|
||||
<LinearLayout
|
||||
android:id="@+id/layoutPetStore"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:orientation="vertical"
|
||||
android:gravity="center"
|
||||
android:background="@color/white"
|
||||
android:layout_marginTop="16dp"
|
||||
android:padding="16dp"
|
||||
android:visibility="gone">
|
||||
|
||||
<TextView
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content"
|
||||
android:text="STORE"
|
||||
android:textSize="11sp"
|
||||
android:textColor="#888888"
|
||||
android:textAllCaps="true"
|
||||
android:layout_marginBottom="4dp"/>
|
||||
|
||||
<TextView
|
||||
android:id="@+id/tvPetStore"
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content"
|
||||
android:text="No Store"
|
||||
android:textSize="20sp"
|
||||
android:textStyle="bold"
|
||||
android:textColor="@color/text_dark"/>
|
||||
|
||||
</LinearLayout>
|
||||
android:text="Back"
|
||||
android:backgroundTint="@color/primary_medium"
|
||||
android:textColor="@color/white"/>
|
||||
|
||||
</LinearLayout>
|
||||
|
||||
</ScrollView>
|
||||
|
||||
<LinearLayout
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:orientation="horizontal"
|
||||
android:padding="16dp">
|
||||
|
||||
<Button
|
||||
android:id="@+id/btnBack"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:text="Back"
|
||||
android:backgroundTint="@color/primary_medium"
|
||||
android:textColor="@color/white"/>
|
||||
|
||||
</LinearLayout>
|
||||
|
||||
</LinearLayout>
|
||||
<ProgressBar
|
||||
android:id="@+id/progressBar"
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_gravity="center"
|
||||
android:visibility="gone"
|
||||
android:indeterminateTint="@color/accent_coral"/>
|
||||
|
||||
</FrameLayout>
|
||||
@@ -1,195 +1,209 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
|
||||
<FrameLayout xmlns:android="http://schemas.android.com/apk/res/android"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="match_parent"
|
||||
android:orientation="vertical"
|
||||
android:background="@color/background_grey">
|
||||
android:layout_height="match_parent">
|
||||
|
||||
<LinearLayout
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="56dp"
|
||||
android:background="@color/primary_dark"
|
||||
android:gravity="center_vertical"
|
||||
android:paddingStart="16dp"
|
||||
android:paddingEnd="16dp"
|
||||
android:orientation="horizontal">
|
||||
|
||||
<TextView
|
||||
android:id="@+id/tvProductMode"
|
||||
android:layout_width="0dp"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_weight="1"
|
||||
android:text="Add Product"
|
||||
android:textColor="@color/white"
|
||||
android:textSize="20sp"
|
||||
android:textStyle="bold"/>
|
||||
|
||||
<Button
|
||||
android:id="@+id/btnDeleteProduct"
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content"
|
||||
android:backgroundTint="@color/accent_coral"
|
||||
android:text="Delete"
|
||||
android:textColor="@color/white"/>
|
||||
|
||||
</LinearLayout>
|
||||
|
||||
<ScrollView
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="0dp"
|
||||
android:layout_weight="1">
|
||||
android:layout_height="match_parent"
|
||||
android:orientation="vertical"
|
||||
android:background="@color/background_grey">
|
||||
|
||||
<LinearLayout
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:orientation="vertical"
|
||||
android:padding="24dp">
|
||||
android:layout_height="56dp"
|
||||
android:background="@color/primary_dark"
|
||||
android:gravity="center_vertical"
|
||||
android:paddingStart="16dp"
|
||||
android:paddingEnd="16dp"
|
||||
android:orientation="horizontal">
|
||||
|
||||
<TextView
|
||||
android:id="@+id/tvProductMode"
|
||||
android:layout_width="0dp"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_weight="1"
|
||||
android:text="Add Product"
|
||||
android:textColor="@color/white"
|
||||
android:textSize="20sp"
|
||||
android:textStyle="bold"/>
|
||||
|
||||
<Button
|
||||
android:id="@+id/btnDeleteProduct"
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content"
|
||||
android:backgroundTint="@color/accent_coral"
|
||||
android:text="Delete"
|
||||
android:textColor="@color/white"/>
|
||||
|
||||
</LinearLayout>
|
||||
|
||||
<ScrollView
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="0dp"
|
||||
android:layout_weight="1">
|
||||
|
||||
<LinearLayout
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:orientation="vertical"
|
||||
android:background="@drawable/rounded_card"
|
||||
android:padding="16dp"
|
||||
android:layout_marginBottom="16dp">
|
||||
android:padding="24dp">
|
||||
|
||||
<TextView
|
||||
android:id="@+id/tvProductId"
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content"
|
||||
android:text="ID: #0"
|
||||
android:textColor="@color/text_light"
|
||||
android:textSize="11sp"
|
||||
android:textStyle="italic"
|
||||
android:layout_gravity="end"
|
||||
android:layout_marginBottom="8dp"/>
|
||||
|
||||
<!-- Product Image -->
|
||||
<TextView
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content"
|
||||
android:text="Product Image"
|
||||
android:textColor="@color/text_dark"
|
||||
android:textSize="12sp"
|
||||
android:layout_marginBottom="4dp"/>
|
||||
|
||||
<FrameLayout
|
||||
<LinearLayout
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="200dp"
|
||||
android:layout_height="wrap_content"
|
||||
android:orientation="vertical"
|
||||
android:background="@drawable/rounded_card"
|
||||
android:padding="16dp"
|
||||
android:layout_marginBottom="16dp">
|
||||
|
||||
<ImageView
|
||||
android:id="@+id/ivProductImage"
|
||||
<TextView
|
||||
android:id="@+id/tvProductId"
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content"
|
||||
android:text="ID: #0"
|
||||
android:textColor="@color/text_light"
|
||||
android:textSize="11sp"
|
||||
android:textStyle="italic"
|
||||
android:layout_gravity="end"
|
||||
android:layout_marginBottom="8dp"/>
|
||||
|
||||
<!-- Product Image -->
|
||||
<TextView
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content"
|
||||
android:text="Product Image"
|
||||
android:textColor="@color/text_dark"
|
||||
android:textSize="12sp"
|
||||
android:layout_marginBottom="4dp"/>
|
||||
|
||||
<FrameLayout
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="match_parent"
|
||||
android:scaleType="centerCrop"
|
||||
android:src="@drawable/placeholder2"
|
||||
android:background="@color/text_light"
|
||||
android:clickable="true"
|
||||
android:focusable="true"/>
|
||||
android:layout_height="200dp"
|
||||
android:layout_marginBottom="16dp">
|
||||
|
||||
</FrameLayout>
|
||||
<ImageView
|
||||
android:id="@+id/ivProductImage"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="match_parent"
|
||||
android:scaleType="centerCrop"
|
||||
android:src="@drawable/placeholder2"
|
||||
android:background="@color/text_light"
|
||||
android:clickable="true"
|
||||
android:focusable="true"/>
|
||||
|
||||
<!-- Product Name -->
|
||||
<TextView
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content"
|
||||
android:text="Product Name"
|
||||
android:textColor="@color/text_dark"
|
||||
android:textSize="12sp"
|
||||
android:layout_marginBottom="4dp"/>
|
||||
</FrameLayout>
|
||||
|
||||
<EditText
|
||||
android:id="@+id/etProductName"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:hint="Enter product name"
|
||||
android:inputType="text"
|
||||
android:layout_marginBottom="16dp"/>
|
||||
<!-- Product Name -->
|
||||
<TextView
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content"
|
||||
android:text="Product Name"
|
||||
android:textColor="@color/text_dark"
|
||||
android:textSize="12sp"
|
||||
android:layout_marginBottom="4dp"/>
|
||||
|
||||
<!-- Category -->
|
||||
<TextView
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content"
|
||||
android:text="Category"
|
||||
android:textColor="@color/text_dark"
|
||||
android:textSize="12sp"
|
||||
android:layout_marginBottom="4dp"/>
|
||||
<EditText
|
||||
android:id="@+id/etProductName"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:hint="Enter product name"
|
||||
android:inputType="text"
|
||||
android:layout_marginBottom="16dp"/>
|
||||
|
||||
<Spinner
|
||||
android:id="@+id/spinnerProductCategory"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_marginBottom="16dp"/>
|
||||
<!-- Category -->
|
||||
<TextView
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content"
|
||||
android:text="Category"
|
||||
android:textColor="@color/text_dark"
|
||||
android:textSize="12sp"
|
||||
android:layout_marginBottom="4dp"/>
|
||||
|
||||
<!-- Description -->
|
||||
<TextView
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content"
|
||||
android:text="Description"
|
||||
android:textColor="@color/text_dark"
|
||||
android:textSize="12sp"
|
||||
android:layout_marginBottom="4dp"/>
|
||||
<Spinner
|
||||
android:id="@+id/spinnerProductCategory"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_marginBottom="16dp"/>
|
||||
|
||||
<EditText
|
||||
android:id="@+id/etProductDesc"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:hint="Enter description"
|
||||
android:inputType="textMultiLine"
|
||||
android:minLines="2"
|
||||
android:layout_marginBottom="16dp"/>
|
||||
<!-- Description -->
|
||||
<TextView
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content"
|
||||
android:text="Description"
|
||||
android:textColor="@color/text_dark"
|
||||
android:textSize="12sp"
|
||||
android:layout_marginBottom="4dp"/>
|
||||
|
||||
<!-- Price -->
|
||||
<TextView
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content"
|
||||
android:text="Price"
|
||||
android:textColor="@color/text_dark"
|
||||
android:textSize="12sp"
|
||||
android:layout_marginBottom="4dp"/>
|
||||
<EditText
|
||||
android:id="@+id/etProductDesc"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:hint="Enter description"
|
||||
android:inputType="textMultiLine"
|
||||
android:minLines="2"
|
||||
android:layout_marginBottom="16dp"/>
|
||||
|
||||
<EditText
|
||||
android:id="@+id/etProductPrice"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:hint="0.00"
|
||||
android:inputType="numberDecimal"
|
||||
android:layout_marginBottom="8dp"/>
|
||||
<!-- Price -->
|
||||
<TextView
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content"
|
||||
android:text="Price"
|
||||
android:textColor="@color/text_dark"
|
||||
android:textSize="12sp"
|
||||
android:layout_marginBottom="4dp"/>
|
||||
|
||||
<EditText
|
||||
android:id="@+id/etProductPrice"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:hint="0.00"
|
||||
android:inputType="numberDecimal"
|
||||
android:layout_marginBottom="8dp"/>
|
||||
|
||||
</LinearLayout>
|
||||
|
||||
</LinearLayout>
|
||||
|
||||
</ScrollView>
|
||||
|
||||
<LinearLayout
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:orientation="horizontal"
|
||||
android:background="@color/white"
|
||||
android:padding="16dp">
|
||||
|
||||
<Button
|
||||
android:id="@+id/btnProductBack"
|
||||
android:layout_width="0dp"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_weight="1"
|
||||
android:layout_marginEnd="8dp"
|
||||
android:text="Back"
|
||||
android:backgroundTint="@color/primary_medium"
|
||||
android:textColor="@color/white"/>
|
||||
|
||||
<Button
|
||||
android:id="@+id/btnSaveProduct"
|
||||
android:layout_width="0dp"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_weight="1"
|
||||
android:layout_marginStart="8dp"
|
||||
android:text="Save"
|
||||
android:backgroundTint="@color/accent_coral"
|
||||
android:textColor="@color/white"/>
|
||||
|
||||
</LinearLayout>
|
||||
|
||||
</ScrollView>
|
||||
|
||||
<LinearLayout
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:orientation="horizontal"
|
||||
android:background="@color/white"
|
||||
android:padding="16dp">
|
||||
|
||||
<Button
|
||||
android:id="@+id/btnProductBack"
|
||||
android:layout_width="0dp"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_weight="1"
|
||||
android:layout_marginEnd="8dp"
|
||||
android:text="Back"
|
||||
android:backgroundTint="@color/primary_medium"
|
||||
android:textColor="@color/white"/>
|
||||
|
||||
<Button
|
||||
android:id="@+id/btnSaveProduct"
|
||||
android:layout_width="0dp"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_weight="1"
|
||||
android:layout_marginStart="8dp"
|
||||
android:text="Save"
|
||||
android:backgroundTint="@color/accent_coral"
|
||||
android:textColor="@color/white"/>
|
||||
|
||||
</LinearLayout>
|
||||
|
||||
</LinearLayout>
|
||||
<ProgressBar
|
||||
android:id="@+id/progressBar"
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_gravity="center"
|
||||
android:visibility="gone"
|
||||
android:indeterminateTint="@color/accent_coral"/>
|
||||
|
||||
</FrameLayout>
|
||||
@@ -1,138 +1,152 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
|
||||
<FrameLayout xmlns:android="http://schemas.android.com/apk/res/android"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="match_parent"
|
||||
android:orientation="vertical"
|
||||
android:background="@color/background_grey">
|
||||
android:layout_height="match_parent">
|
||||
|
||||
<LinearLayout
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="56dp"
|
||||
android:background="@color/primary_dark"
|
||||
android:gravity="center_vertical"
|
||||
android:paddingStart="16dp"
|
||||
android:paddingEnd="16dp"
|
||||
android:orientation="horizontal">
|
||||
|
||||
<TextView
|
||||
android:id="@+id/tvPSMode"
|
||||
android:layout_width="0dp"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_weight="1"
|
||||
android:text="Add Product Supplier"
|
||||
android:textColor="@color/white"
|
||||
android:textSize="20sp"
|
||||
android:textStyle="bold"/>
|
||||
|
||||
<Button
|
||||
android:id="@+id/btnDeletePS"
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content"
|
||||
android:backgroundTint="@color/accent_coral"
|
||||
android:text="Delete"
|
||||
android:textColor="@color/white"/>
|
||||
|
||||
</LinearLayout>
|
||||
|
||||
<ScrollView
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="0dp"
|
||||
android:layout_weight="1">
|
||||
android:layout_height="match_parent"
|
||||
android:orientation="vertical"
|
||||
android:background="@color/background_grey">
|
||||
|
||||
<LinearLayout
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:orientation="vertical"
|
||||
android:padding="24dp">
|
||||
android:layout_height="56dp"
|
||||
android:background="@color/primary_dark"
|
||||
android:gravity="center_vertical"
|
||||
android:paddingStart="16dp"
|
||||
android:paddingEnd="16dp"
|
||||
android:orientation="horizontal">
|
||||
|
||||
<TextView
|
||||
android:id="@+id/tvPSMode"
|
||||
android:layout_width="0dp"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_weight="1"
|
||||
android:text="Add Product Supplier"
|
||||
android:textColor="@color/white"
|
||||
android:textSize="20sp"
|
||||
android:textStyle="bold"/>
|
||||
|
||||
<Button
|
||||
android:id="@+id/btnDeletePS"
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content"
|
||||
android:backgroundTint="@color/accent_coral"
|
||||
android:text="Delete"
|
||||
android:textColor="@color/white"/>
|
||||
|
||||
</LinearLayout>
|
||||
|
||||
<ScrollView
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="0dp"
|
||||
android:layout_weight="1">
|
||||
|
||||
<LinearLayout
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:orientation="vertical"
|
||||
android:background="@drawable/rounded_card"
|
||||
android:padding="16dp"
|
||||
android:layout_marginBottom="16dp">
|
||||
android:padding="24dp">
|
||||
|
||||
<!-- Product -->
|
||||
<TextView
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content"
|
||||
android:text="Product"
|
||||
android:textColor="@color/text_dark"
|
||||
android:textSize="12sp"
|
||||
android:layout_marginBottom="4dp"/>
|
||||
|
||||
<Spinner
|
||||
android:id="@+id/spinnerPSProduct"
|
||||
<LinearLayout
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_marginBottom="16dp"/>
|
||||
android:orientation="vertical"
|
||||
android:background="@drawable/rounded_card"
|
||||
android:padding="16dp"
|
||||
android:layout_marginBottom="16dp">
|
||||
|
||||
<!-- Supplier -->
|
||||
<TextView
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content"
|
||||
android:text="Supplier"
|
||||
android:textColor="@color/text_dark"
|
||||
android:textSize="12sp"
|
||||
android:layout_marginBottom="4dp"/>
|
||||
<!-- Product -->
|
||||
<TextView
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content"
|
||||
android:text="Product"
|
||||
android:textColor="@color/text_dark"
|
||||
android:textSize="12sp"
|
||||
android:layout_marginBottom="4dp"/>
|
||||
|
||||
<Spinner
|
||||
android:id="@+id/spinnerPSSupplier"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_marginBottom="16dp"/>
|
||||
<Spinner
|
||||
android:id="@+id/spinnerPSProduct"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_marginBottom="16dp"/>
|
||||
|
||||
<!-- Cost -->
|
||||
<TextView
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content"
|
||||
android:text="Cost"
|
||||
android:textColor="@color/text_dark"
|
||||
android:textSize="12sp"
|
||||
android:layout_marginBottom="4dp"/>
|
||||
<!-- Supplier -->
|
||||
<TextView
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content"
|
||||
android:text="Supplier"
|
||||
android:textColor="@color/text_dark"
|
||||
android:textSize="12sp"
|
||||
android:layout_marginBottom="4dp"/>
|
||||
|
||||
<EditText
|
||||
android:id="@+id/etPSCost"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:hint="0.00"
|
||||
android:inputType="numberDecimal"
|
||||
android:layout_marginBottom="8dp"/>
|
||||
<Spinner
|
||||
android:id="@+id/spinnerPSSupplier"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_marginBottom="16dp"/>
|
||||
|
||||
<!-- Cost -->
|
||||
<TextView
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content"
|
||||
android:text="Cost"
|
||||
android:textColor="@color/text_dark"
|
||||
android:textSize="12sp"
|
||||
android:layout_marginBottom="4dp"/>
|
||||
|
||||
<EditText
|
||||
android:id="@+id/etPSCost"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:hint="0.00"
|
||||
android:inputType="numberDecimal"
|
||||
android:layout_marginBottom="8dp"/>
|
||||
|
||||
</LinearLayout>
|
||||
|
||||
</LinearLayout>
|
||||
|
||||
</ScrollView>
|
||||
|
||||
<LinearLayout
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:orientation="horizontal"
|
||||
android:background="@color/white"
|
||||
android:padding="16dp">
|
||||
|
||||
<Button
|
||||
android:id="@+id/btnPSBack"
|
||||
android:layout_width="0dp"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_weight="1"
|
||||
android:layout_marginEnd="8dp"
|
||||
android:text="Back"
|
||||
android:backgroundTint="@color/primary_medium"
|
||||
android:textColor="@color/white"/>
|
||||
|
||||
<Button
|
||||
android:id="@+id/btnSavePS"
|
||||
android:layout_width="0dp"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_weight="1"
|
||||
android:layout_marginStart="8dp"
|
||||
android:text="Save"
|
||||
android:backgroundTint="@color/accent_coral"
|
||||
android:textColor="@color/white"/>
|
||||
|
||||
</LinearLayout>
|
||||
|
||||
</ScrollView>
|
||||
|
||||
<LinearLayout
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:orientation="horizontal"
|
||||
android:background="@color/white"
|
||||
android:padding="16dp">
|
||||
|
||||
<Button
|
||||
android:id="@+id/btnPSBack"
|
||||
android:layout_width="0dp"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_weight="1"
|
||||
android:layout_marginEnd="8dp"
|
||||
android:text="Back"
|
||||
android:backgroundTint="@color/primary_medium"
|
||||
android:textColor="@color/white"/>
|
||||
|
||||
<Button
|
||||
android:id="@+id/btnSavePS"
|
||||
android:layout_width="0dp"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_weight="1"
|
||||
android:layout_marginStart="8dp"
|
||||
android:text="Save"
|
||||
android:backgroundTint="@color/accent_coral"
|
||||
android:textColor="@color/white"/>
|
||||
|
||||
</LinearLayout>
|
||||
|
||||
</LinearLayout>
|
||||
<ProgressBar
|
||||
android:id="@+id/progressBar"
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_gravity="center"
|
||||
android:visibility="gone"
|
||||
android:indeterminateTint="@color/accent_coral"/>
|
||||
|
||||
</FrameLayout>
|
||||
@@ -1,200 +1,214 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<ScrollView xmlns:android="http://schemas.android.com/apk/res/android"
|
||||
<FrameLayout xmlns:android="http://schemas.android.com/apk/res/android"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="match_parent"
|
||||
android:background="@color/background_grey">
|
||||
android:layout_height="match_parent">
|
||||
|
||||
<LinearLayout
|
||||
<ScrollView
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:orientation="vertical">
|
||||
android:layout_height="match_parent"
|
||||
android:background="@color/background_grey">
|
||||
|
||||
<LinearLayout
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:orientation="vertical"
|
||||
android:background="@color/primary_dark"
|
||||
android:gravity="center_horizontal"
|
||||
android:paddingTop="32dp"
|
||||
android:paddingBottom="32dp">
|
||||
android:orientation="vertical">
|
||||
|
||||
<ImageView
|
||||
android:id="@+id/imgProfile"
|
||||
android:layout_width="171dp"
|
||||
android:layout_height="166dp"
|
||||
android:layout_marginBottom="8dp"
|
||||
android:background="@drawable/circle_image"
|
||||
android:clipToOutline="true"
|
||||
android:scaleType="centerCrop"
|
||||
android:src="@drawable/placeholder" />
|
||||
<LinearLayout
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:orientation="vertical"
|
||||
android:background="@color/primary_dark"
|
||||
android:gravity="center_horizontal"
|
||||
android:paddingTop="32dp"
|
||||
android:paddingBottom="32dp">
|
||||
|
||||
<ImageView
|
||||
android:id="@+id/imgProfile"
|
||||
android:layout_width="171dp"
|
||||
android:layout_height="166dp"
|
||||
android:layout_marginBottom="8dp"
|
||||
android:background="@drawable/circle_image"
|
||||
android:clipToOutline="true"
|
||||
android:scaleType="centerCrop"
|
||||
android:src="@drawable/placeholder" />
|
||||
|
||||
<Button
|
||||
android:id="@+id/btnChangePhoto"
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content"
|
||||
android:text="Change Photo"
|
||||
style="@style/Widget.Material3.Button.TextButton"
|
||||
android:textColor="@color/text_light"/>
|
||||
|
||||
<TextView
|
||||
android:id="@+id/tvProfileName"
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_marginTop="8dp"
|
||||
android:text="First Last"
|
||||
android:textColor="@color/white"
|
||||
android:textSize="22sp"
|
||||
android:textStyle="bold" />
|
||||
|
||||
</LinearLayout>
|
||||
|
||||
<LinearLayout
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:orientation="vertical"
|
||||
android:background="@color/white"
|
||||
android:layout_marginStart="16dp"
|
||||
android:layout_marginEnd="16dp"
|
||||
android:layout_marginTop="16dp"
|
||||
android:layout_marginBottom="16dp"
|
||||
android:padding="16dp">
|
||||
|
||||
<LinearLayout
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:orientation="horizontal"
|
||||
android:gravity="center_vertical"
|
||||
android:paddingBottom="12dp">
|
||||
|
||||
<LinearLayout
|
||||
android:layout_width="0dp"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_weight="1"
|
||||
android:orientation="vertical">
|
||||
|
||||
<TextView
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content"
|
||||
android:text="Email"
|
||||
android:textSize="12sp"
|
||||
android:textColor="#888888"/>
|
||||
|
||||
<TextView
|
||||
android:id="@+id/tvProfileEmail"
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content"
|
||||
android:text="No email loaded"
|
||||
android:textColor="@color/text_dark"
|
||||
android:textSize="16sp" />
|
||||
|
||||
</LinearLayout>
|
||||
|
||||
<Button
|
||||
android:id="@+id/btnEditEmail"
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content"
|
||||
android:text="Edit"
|
||||
android:textColor="@color/accent_coral"
|
||||
style="@style/Widget.Material3.Button.TextButton"/>
|
||||
|
||||
</LinearLayout>
|
||||
|
||||
<View
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="1dp"
|
||||
android:background="#F0F0F0"/>
|
||||
|
||||
<LinearLayout
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:orientation="horizontal"
|
||||
android:gravity="center_vertical"
|
||||
android:paddingTop="12dp"
|
||||
android:paddingBottom="12dp">
|
||||
|
||||
<LinearLayout
|
||||
android:layout_width="0dp"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_weight="1"
|
||||
android:orientation="vertical">
|
||||
|
||||
<TextView
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content"
|
||||
android:text="Phone"
|
||||
android:textSize="12sp"
|
||||
android:textColor="#888888"/>
|
||||
|
||||
<TextView
|
||||
android:id="@+id/tvProfilePhone"
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content"
|
||||
android:text="No phone loaded"
|
||||
android:textColor="@color/text_dark"
|
||||
android:textSize="16sp" />
|
||||
|
||||
</LinearLayout>
|
||||
|
||||
<Button
|
||||
android:id="@+id/btnEditPhone"
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content"
|
||||
android:text="Edit"
|
||||
android:textColor="@color/accent_coral"
|
||||
style="@style/Widget.Material3.Button.TextButton"/>
|
||||
|
||||
</LinearLayout>
|
||||
|
||||
<View
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="1dp"
|
||||
android:background="#F0F0F0"/>
|
||||
|
||||
<LinearLayout
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:orientation="horizontal"
|
||||
android:gravity="center_vertical"
|
||||
android:paddingTop="12dp">
|
||||
|
||||
<LinearLayout
|
||||
android:layout_width="0dp"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_weight="1"
|
||||
android:orientation="vertical">
|
||||
|
||||
<TextView
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content"
|
||||
android:text="Role"
|
||||
android:textSize="12sp"
|
||||
android:textColor="#888888"/>
|
||||
|
||||
<TextView
|
||||
android:id="@+id/tvProfileRole"
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content"
|
||||
android:text="No role loaded"
|
||||
android:textSize="16sp"
|
||||
android:textColor="@color/accent_coral"/>
|
||||
|
||||
</LinearLayout>
|
||||
|
||||
</LinearLayout>
|
||||
|
||||
</LinearLayout>
|
||||
|
||||
<Button
|
||||
android:id="@+id/btnChangePhoto"
|
||||
android:layout_width="wrap_content"
|
||||
android:id="@+id/btnLogout"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:text="Change Photo"
|
||||
style="@style/Widget.Material3.Button.TextButton"
|
||||
android:textColor="@color/text_light"/>
|
||||
|
||||
<TextView
|
||||
android:id="@+id/tvProfileName"
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_marginTop="8dp"
|
||||
android:text="First Last"
|
||||
android:textColor="@color/white"
|
||||
android:textSize="22sp"
|
||||
android:textStyle="bold" />
|
||||
android:layout_marginStart="16dp"
|
||||
android:layout_marginEnd="16dp"
|
||||
android:layout_marginBottom="24dp"
|
||||
android:text="Log Out"
|
||||
android:backgroundTint="@color/accent_coral"
|
||||
android:textColor="@color/white"/>
|
||||
|
||||
</LinearLayout>
|
||||
|
||||
<LinearLayout
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:orientation="vertical"
|
||||
android:background="@color/white"
|
||||
android:layout_marginStart="16dp"
|
||||
android:layout_marginEnd="16dp"
|
||||
android:layout_marginTop="16dp"
|
||||
android:layout_marginBottom="16dp"
|
||||
android:padding="16dp">
|
||||
</ScrollView>
|
||||
|
||||
<LinearLayout
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:orientation="horizontal"
|
||||
android:gravity="center_vertical"
|
||||
android:paddingBottom="12dp">
|
||||
<ProgressBar
|
||||
android:id="@+id/progressBar"
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_gravity="center"
|
||||
android:visibility="gone"
|
||||
android:indeterminateTint="@color/accent_coral"/>
|
||||
|
||||
<LinearLayout
|
||||
android:layout_width="0dp"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_weight="1"
|
||||
android:orientation="vertical">
|
||||
|
||||
<TextView
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content"
|
||||
android:text="Email"
|
||||
android:textSize="12sp"
|
||||
android:textColor="#888888"/>
|
||||
|
||||
<TextView
|
||||
android:id="@+id/tvProfileEmail"
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content"
|
||||
android:text="No email loaded"
|
||||
android:textColor="@color/text_dark"
|
||||
android:textSize="16sp" />
|
||||
|
||||
</LinearLayout>
|
||||
|
||||
<Button
|
||||
android:id="@+id/btnEditEmail"
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content"
|
||||
android:text="Edit"
|
||||
android:textColor="@color/accent_coral"
|
||||
style="@style/Widget.Material3.Button.TextButton"/>
|
||||
|
||||
</LinearLayout>
|
||||
|
||||
<View
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="1dp"
|
||||
android:background="#F0F0F0"/>
|
||||
|
||||
<LinearLayout
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:orientation="horizontal"
|
||||
android:gravity="center_vertical"
|
||||
android:paddingTop="12dp"
|
||||
android:paddingBottom="12dp">
|
||||
|
||||
<LinearLayout
|
||||
android:layout_width="0dp"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_weight="1"
|
||||
android:orientation="vertical">
|
||||
|
||||
<TextView
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content"
|
||||
android:text="Phone"
|
||||
android:textSize="12sp"
|
||||
android:textColor="#888888"/>
|
||||
|
||||
<TextView
|
||||
android:id="@+id/tvProfilePhone"
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content"
|
||||
android:text="No phone loaded"
|
||||
android:textColor="@color/text_dark"
|
||||
android:textSize="16sp" />
|
||||
|
||||
</LinearLayout>
|
||||
|
||||
<Button
|
||||
android:id="@+id/btnEditPhone"
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content"
|
||||
android:text="Edit"
|
||||
android:textColor="@color/accent_coral"
|
||||
style="@style/Widget.Material3.Button.TextButton"/>
|
||||
|
||||
</LinearLayout>
|
||||
|
||||
<View
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="1dp"
|
||||
android:background="#F0F0F0"/>
|
||||
|
||||
<LinearLayout
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:orientation="horizontal"
|
||||
android:gravity="center_vertical"
|
||||
android:paddingTop="12dp">
|
||||
|
||||
<LinearLayout
|
||||
android:layout_width="0dp"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_weight="1"
|
||||
android:orientation="vertical">
|
||||
|
||||
<TextView
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content"
|
||||
android:text="Role"
|
||||
android:textSize="12sp"
|
||||
android:textColor="#888888"/>
|
||||
|
||||
<TextView
|
||||
android:id="@+id/tvProfileRole"
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content"
|
||||
android:text="No role loaded"
|
||||
android:textSize="16sp"
|
||||
android:textColor="@color/accent_coral"/>
|
||||
|
||||
</LinearLayout>
|
||||
|
||||
</LinearLayout>
|
||||
|
||||
</LinearLayout>
|
||||
|
||||
<Button
|
||||
android:id="@+id/btnLogout"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_marginStart="16dp"
|
||||
android:layout_marginEnd="16dp"
|
||||
android:layout_marginBottom="24dp"
|
||||
android:text="Log Out"
|
||||
android:backgroundTint="@color/accent_coral"
|
||||
android:textColor="@color/white"/>
|
||||
|
||||
</LinearLayout>
|
||||
|
||||
</ScrollView>
|
||||
</FrameLayout>
|
||||
@@ -1,140 +1,154 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
|
||||
<FrameLayout xmlns:android="http://schemas.android.com/apk/res/android"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="match_parent"
|
||||
android:orientation="vertical"
|
||||
android:background="@color/background_grey">
|
||||
android:layout_height="match_parent">
|
||||
|
||||
<LinearLayout
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="56dp"
|
||||
android:background="@color/primary_dark"
|
||||
android:gravity="center_vertical"
|
||||
android:paddingStart="16dp"
|
||||
android:paddingEnd="16dp"
|
||||
android:orientation="horizontal">
|
||||
|
||||
<TextView
|
||||
android:layout_width="0dp"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_weight="1"
|
||||
android:text="Purchase Order Details"
|
||||
android:textColor="@color/white"
|
||||
android:textSize="20sp"
|
||||
android:textStyle="bold"/>
|
||||
|
||||
</LinearLayout>
|
||||
|
||||
<ScrollView
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="0dp"
|
||||
android:layout_weight="1">
|
||||
android:layout_height="match_parent"
|
||||
android:orientation="vertical"
|
||||
android:background="@color/background_grey">
|
||||
|
||||
<LinearLayout
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:orientation="vertical"
|
||||
android:padding="24dp">
|
||||
android:layout_height="56dp"
|
||||
android:background="@color/primary_dark"
|
||||
android:gravity="center_vertical"
|
||||
android:paddingStart="16dp"
|
||||
android:paddingEnd="16dp"
|
||||
android:orientation="horizontal">
|
||||
|
||||
<TextView
|
||||
android:layout_width="0dp"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_weight="1"
|
||||
android:text="Purchase Order Details"
|
||||
android:textColor="@color/white"
|
||||
android:textSize="20sp"
|
||||
android:textStyle="bold"/>
|
||||
|
||||
</LinearLayout>
|
||||
|
||||
<ScrollView
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="0dp"
|
||||
android:layout_weight="1">
|
||||
|
||||
<LinearLayout
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:orientation="vertical"
|
||||
android:background="@drawable/rounded_card"
|
||||
android:padding="16dp"
|
||||
android:layout_marginBottom="16dp">
|
||||
android:padding="24dp">
|
||||
|
||||
<TextView
|
||||
android:id="@+id/tvPODetailId"
|
||||
android:layout_width="wrap_content"
|
||||
<LinearLayout
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:textColor="@color/text_light"
|
||||
android:textSize="11sp"
|
||||
android:textStyle="italic"
|
||||
android:layout_gravity="end"
|
||||
android:layout_marginBottom="16dp"/>
|
||||
android:orientation="vertical"
|
||||
android:background="@drawable/rounded_card"
|
||||
android:padding="16dp"
|
||||
android:layout_marginBottom="16dp">
|
||||
|
||||
<TextView
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content"
|
||||
android:text="Supplier"
|
||||
android:textColor="@color/text_light"
|
||||
android:textSize="12sp"/>
|
||||
<TextView
|
||||
android:id="@+id/tvPODetailId"
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content"
|
||||
android:textColor="@color/text_light"
|
||||
android:textSize="11sp"
|
||||
android:textStyle="italic"
|
||||
android:layout_gravity="end"
|
||||
android:layout_marginBottom="16dp"/>
|
||||
|
||||
<TextView
|
||||
android:id="@+id/tvPODetailSupplier"
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content"
|
||||
android:textColor="@color/text_dark"
|
||||
android:textSize="16sp"
|
||||
android:textStyle="bold"
|
||||
android:layout_marginBottom="16dp"/>
|
||||
<TextView
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content"
|
||||
android:text="Supplier"
|
||||
android:textColor="@color/text_light"
|
||||
android:textSize="12sp"/>
|
||||
|
||||
<TextView
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content"
|
||||
android:text="Store"
|
||||
android:textColor="@color/text_light"
|
||||
android:textSize="12sp"/>
|
||||
<TextView
|
||||
android:id="@+id/tvPODetailSupplier"
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content"
|
||||
android:textColor="@color/text_dark"
|
||||
android:textSize="16sp"
|
||||
android:textStyle="bold"
|
||||
android:layout_marginBottom="16dp"/>
|
||||
|
||||
<TextView
|
||||
android:id="@+id/tvPODetailStore"
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content"
|
||||
android:textColor="@color/text_dark"
|
||||
android:textSize="16sp"
|
||||
android:textStyle="bold"
|
||||
android:layout_marginBottom="16dp"/>
|
||||
<TextView
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content"
|
||||
android:text="Store"
|
||||
android:textColor="@color/text_light"
|
||||
android:textSize="12sp"/>
|
||||
|
||||
<TextView
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content"
|
||||
android:text="Order Date"
|
||||
android:textColor="@color/text_light"
|
||||
android:textSize="12sp"/>
|
||||
<TextView
|
||||
android:id="@+id/tvPODetailStore"
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content"
|
||||
android:textColor="@color/text_dark"
|
||||
android:textSize="16sp"
|
||||
android:textStyle="bold"
|
||||
android:layout_marginBottom="16dp"/>
|
||||
|
||||
<TextView
|
||||
android:id="@+id/tvPODetailDate"
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content"
|
||||
android:textColor="@color/text_dark"
|
||||
android:textSize="15sp"
|
||||
android:layout_marginBottom="16dp"/>
|
||||
<TextView
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content"
|
||||
android:text="Order Date"
|
||||
android:textColor="@color/text_light"
|
||||
android:textSize="12sp"/>
|
||||
|
||||
<TextView
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content"
|
||||
android:text="Status"
|
||||
android:textColor="@color/text_light"
|
||||
android:textSize="12sp"/>
|
||||
<TextView
|
||||
android:id="@+id/tvPODetailDate"
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content"
|
||||
android:textColor="@color/text_dark"
|
||||
android:textSize="15sp"
|
||||
android:layout_marginBottom="16dp"/>
|
||||
|
||||
<TextView
|
||||
android:id="@+id/tvPODetailStatus"
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content"
|
||||
android:textSize="15sp"
|
||||
android:layout_marginBottom="16dp"/>
|
||||
<TextView
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content"
|
||||
android:text="Status"
|
||||
android:textColor="@color/text_light"
|
||||
android:textSize="12sp"/>
|
||||
|
||||
<TextView
|
||||
android:id="@+id/tvPODetailStatus"
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content"
|
||||
android:textSize="15sp"
|
||||
android:layout_marginBottom="16dp"/>
|
||||
|
||||
</LinearLayout>
|
||||
|
||||
</LinearLayout>
|
||||
|
||||
</LinearLayout>
|
||||
</ScrollView>
|
||||
|
||||
</ScrollView>
|
||||
|
||||
<LinearLayout
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:background="@color/white"
|
||||
android:padding="16dp">
|
||||
|
||||
<Button
|
||||
android:id="@+id/btnPOBack"
|
||||
<LinearLayout
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:text="Back"
|
||||
android:backgroundTint="@color/primary_medium"
|
||||
android:textColor="@color/white"/>
|
||||
android:background="@color/white"
|
||||
android:padding="16dp">
|
||||
|
||||
<Button
|
||||
android:id="@+id/btnPOBack"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:text="Back"
|
||||
android:backgroundTint="@color/primary_medium"
|
||||
android:textColor="@color/white"/>
|
||||
|
||||
</LinearLayout>
|
||||
|
||||
</LinearLayout>
|
||||
|
||||
</LinearLayout>
|
||||
<ProgressBar
|
||||
android:id="@+id/progressBar"
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_gravity="center"
|
||||
android:visibility="gone"
|
||||
android:indeterminateTint="@color/accent_coral"/>
|
||||
|
||||
</FrameLayout>
|
||||
@@ -1,222 +1,236 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
|
||||
<FrameLayout xmlns:android="http://schemas.android.com/apk/res/android"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="match_parent"
|
||||
android:orientation="vertical"
|
||||
android:background="@color/background_grey">
|
||||
android:layout_height="match_parent">
|
||||
|
||||
<LinearLayout
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="56dp"
|
||||
android:background="@color/primary_dark"
|
||||
android:gravity="center_vertical"
|
||||
android:paddingStart="16dp"
|
||||
android:paddingEnd="16dp"
|
||||
android:orientation="horizontal">
|
||||
|
||||
<TextView
|
||||
android:layout_width="0dp"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_weight="1"
|
||||
android:text="Process Refund"
|
||||
android:textColor="@color/white"
|
||||
android:textSize="20sp"
|
||||
android:textStyle="bold"/>
|
||||
|
||||
</LinearLayout>
|
||||
|
||||
<ScrollView
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="0dp"
|
||||
android:layout_weight="1">
|
||||
android:layout_height="match_parent"
|
||||
android:orientation="vertical"
|
||||
android:background="@color/background_grey">
|
||||
|
||||
<LinearLayout
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:orientation="vertical"
|
||||
android:padding="16dp">
|
||||
android:layout_height="56dp"
|
||||
android:background="@color/primary_dark"
|
||||
android:gravity="center_vertical"
|
||||
android:paddingStart="16dp"
|
||||
android:paddingEnd="16dp"
|
||||
android:orientation="horizontal">
|
||||
|
||||
<!-- Load Sale Card -->
|
||||
<LinearLayout
|
||||
android:layout_width="match_parent"
|
||||
<TextView
|
||||
android:layout_width="0dp"
|
||||
android:layout_height="wrap_content"
|
||||
android:orientation="vertical"
|
||||
android:background="@drawable/rounded_card"
|
||||
android:padding="16dp"
|
||||
android:layout_marginBottom="16dp">
|
||||
|
||||
<TextView
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content"
|
||||
android:text="Load Original Sale"
|
||||
android:textColor="@color/text_dark"
|
||||
android:textSize="14sp"
|
||||
android:textStyle="bold"
|
||||
android:layout_marginBottom="8dp"/>
|
||||
|
||||
<LinearLayout
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:orientation="horizontal"
|
||||
android:gravity="center_vertical">
|
||||
|
||||
<EditText
|
||||
android:id="@+id/etRefundSaleId"
|
||||
android:layout_width="0dp"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_weight="1"
|
||||
android:hint="Enter Sale ID"
|
||||
android:inputType="number"
|
||||
android:layout_marginEnd="8dp"/>
|
||||
|
||||
<Button
|
||||
android:id="@+id/btnLoadSale"
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content"
|
||||
android:text="Load"
|
||||
android:backgroundTint="@color/primary_medium"
|
||||
android:textColor="@color/white"/>
|
||||
|
||||
</LinearLayout>
|
||||
|
||||
<TextView
|
||||
android:id="@+id/tvSaleInfo"
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content"
|
||||
android:textColor="@color/text_light"
|
||||
android:textSize="12sp"
|
||||
android:layout_marginTop="8dp"
|
||||
android:visibility="gone"/>
|
||||
|
||||
</LinearLayout>
|
||||
|
||||
<!-- Original Sale Items Card -->
|
||||
<LinearLayout
|
||||
android:id="@+id/cardOriginalItems"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:orientation="vertical"
|
||||
android:background="@drawable/rounded_card"
|
||||
android:padding="16dp"
|
||||
android:layout_marginBottom="16dp"
|
||||
android:visibility="gone">
|
||||
|
||||
<TextView
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content"
|
||||
android:text="Original Sale Items"
|
||||
android:textColor="@color/text_dark"
|
||||
android:textSize="14sp"
|
||||
android:textStyle="bold"
|
||||
android:layout_marginBottom="8dp"/>
|
||||
|
||||
<LinearLayout
|
||||
android:id="@+id/llOriginalItems"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:orientation="vertical"/>
|
||||
|
||||
</LinearLayout>
|
||||
|
||||
<!-- Refund Items Card -->
|
||||
<LinearLayout
|
||||
android:id="@+id/cardRefundItems"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:orientation="vertical"
|
||||
android:background="@drawable/rounded_card"
|
||||
android:padding="16dp"
|
||||
android:layout_marginBottom="16dp"
|
||||
android:visibility="gone">
|
||||
|
||||
<TextView
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content"
|
||||
android:text="Items to Refund"
|
||||
android:textColor="@color/text_dark"
|
||||
android:textSize="14sp"
|
||||
android:textStyle="bold"
|
||||
android:layout_marginBottom="8dp"/>
|
||||
|
||||
<LinearLayout
|
||||
android:id="@+id/llRefundItems"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:orientation="vertical"/>
|
||||
|
||||
<TextView
|
||||
android:id="@+id/tvRefundTotal"
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content"
|
||||
android:text="Refund Total: $0.00"
|
||||
android:textColor="@color/accent_coral"
|
||||
android:textSize="16sp"
|
||||
android:textStyle="bold"
|
||||
android:layout_gravity="end"
|
||||
android:layout_marginTop="12dp"/>
|
||||
|
||||
</LinearLayout>
|
||||
|
||||
<!-- Payment Method Card -->
|
||||
<LinearLayout
|
||||
android:id="@+id/cardPayment"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:orientation="vertical"
|
||||
android:background="@drawable/rounded_card"
|
||||
android:padding="16dp"
|
||||
android:layout_marginBottom="16dp"
|
||||
android:visibility="gone">
|
||||
|
||||
<TextView
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content"
|
||||
android:text="Refund Payment Method"
|
||||
android:textColor="@color/text_dark"
|
||||
android:textSize="14sp"
|
||||
android:textStyle="bold"
|
||||
android:layout_marginBottom="8dp"/>
|
||||
|
||||
<Spinner
|
||||
android:id="@+id/spinnerRefundPayment"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"/>
|
||||
|
||||
</LinearLayout>
|
||||
android:layout_weight="1"
|
||||
android:text="Process Refund"
|
||||
android:textColor="@color/white"
|
||||
android:textSize="20sp"
|
||||
android:textStyle="bold"/>
|
||||
|
||||
</LinearLayout>
|
||||
|
||||
</ScrollView>
|
||||
<ScrollView
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="0dp"
|
||||
android:layout_weight="1">
|
||||
|
||||
<!-- Bottom Buttons -->
|
||||
<LinearLayout
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:orientation="horizontal"
|
||||
android:background="@color/white"
|
||||
android:padding="16dp">
|
||||
<LinearLayout
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:orientation="vertical"
|
||||
android:padding="16dp">
|
||||
|
||||
<Button
|
||||
android:id="@+id/btnRefundBack"
|
||||
android:layout_width="0dp"
|
||||
<!-- Load Sale Card -->
|
||||
<LinearLayout
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:orientation="vertical"
|
||||
android:background="@drawable/rounded_card"
|
||||
android:padding="16dp"
|
||||
android:layout_marginBottom="16dp">
|
||||
|
||||
<TextView
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content"
|
||||
android:text="Load Original Sale"
|
||||
android:textColor="@color/text_dark"
|
||||
android:textSize="14sp"
|
||||
android:textStyle="bold"
|
||||
android:layout_marginBottom="8dp"/>
|
||||
|
||||
<LinearLayout
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:orientation="horizontal"
|
||||
android:gravity="center_vertical">
|
||||
|
||||
<EditText
|
||||
android:id="@+id/etRefundSaleId"
|
||||
android:layout_width="0dp"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_weight="1"
|
||||
android:hint="Enter Sale ID"
|
||||
android:inputType="number"
|
||||
android:layout_marginEnd="8dp"/>
|
||||
|
||||
<Button
|
||||
android:id="@+id/btnLoadSale"
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content"
|
||||
android:text="Load"
|
||||
android:backgroundTint="@color/primary_medium"
|
||||
android:textColor="@color/white"/>
|
||||
|
||||
</LinearLayout>
|
||||
|
||||
<TextView
|
||||
android:id="@+id/tvSaleInfo"
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content"
|
||||
android:textColor="@color/text_light"
|
||||
android:textSize="12sp"
|
||||
android:layout_marginTop="8dp"
|
||||
android:visibility="gone"/>
|
||||
|
||||
</LinearLayout>
|
||||
|
||||
<!-- Original Sale Items Card -->
|
||||
<LinearLayout
|
||||
android:id="@+id/cardOriginalItems"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:orientation="vertical"
|
||||
android:background="@drawable/rounded_card"
|
||||
android:padding="16dp"
|
||||
android:layout_marginBottom="16dp"
|
||||
android:visibility="gone">
|
||||
|
||||
<TextView
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content"
|
||||
android:text="Original Sale Items"
|
||||
android:textColor="@color/text_dark"
|
||||
android:textSize="14sp"
|
||||
android:textStyle="bold"
|
||||
android:layout_marginBottom="8dp"/>
|
||||
|
||||
<LinearLayout
|
||||
android:id="@+id/llOriginalItems"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:orientation="vertical"/>
|
||||
|
||||
</LinearLayout>
|
||||
|
||||
<!-- Refund Items Card -->
|
||||
<LinearLayout
|
||||
android:id="@+id/cardRefundItems"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:orientation="vertical"
|
||||
android:background="@drawable/rounded_card"
|
||||
android:padding="16dp"
|
||||
android:layout_marginBottom="16dp"
|
||||
android:visibility="gone">
|
||||
|
||||
<TextView
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content"
|
||||
android:text="Items to Refund"
|
||||
android:textColor="@color/text_dark"
|
||||
android:textSize="14sp"
|
||||
android:textStyle="bold"
|
||||
android:layout_marginBottom="8dp"/>
|
||||
|
||||
<LinearLayout
|
||||
android:id="@+id/llRefundItems"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:orientation="vertical"/>
|
||||
|
||||
<TextView
|
||||
android:id="@+id/tvRefundTotal"
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content"
|
||||
android:text="Refund Total: $0.00"
|
||||
android:textColor="@color/accent_coral"
|
||||
android:textSize="16sp"
|
||||
android:textStyle="bold"
|
||||
android:layout_gravity="end"
|
||||
android:layout_marginTop="12dp"/>
|
||||
|
||||
</LinearLayout>
|
||||
|
||||
<!-- Payment Method Card -->
|
||||
<LinearLayout
|
||||
android:id="@+id/cardPayment"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:orientation="vertical"
|
||||
android:background="@drawable/rounded_card"
|
||||
android:padding="16dp"
|
||||
android:layout_marginBottom="16dp"
|
||||
android:visibility="gone">
|
||||
|
||||
<TextView
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content"
|
||||
android:text="Refund Payment Method"
|
||||
android:textColor="@color/text_dark"
|
||||
android:textSize="14sp"
|
||||
android:textStyle="bold"
|
||||
android:layout_marginBottom="8dp"/>
|
||||
|
||||
<Spinner
|
||||
android:id="@+id/spinnerRefundPayment"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"/>
|
||||
|
||||
</LinearLayout>
|
||||
|
||||
</LinearLayout>
|
||||
|
||||
</ScrollView>
|
||||
|
||||
<!-- Bottom Buttons -->
|
||||
<LinearLayout
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_weight="1"
|
||||
android:layout_marginEnd="8dp"
|
||||
android:text="Back"
|
||||
android:backgroundTint="@color/primary_medium"
|
||||
android:textColor="@color/white"/>
|
||||
android:orientation="horizontal"
|
||||
android:background="@color/white"
|
||||
android:padding="16dp">
|
||||
|
||||
<Button
|
||||
android:id="@+id/btnProcessRefund"
|
||||
android:layout_width="0dp"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_weight="1"
|
||||
android:layout_marginStart="8dp"
|
||||
android:text="Process Refund"
|
||||
android:backgroundTint="@color/accent_coral"
|
||||
android:textColor="@color/white"
|
||||
android:visibility="gone"/>
|
||||
<Button
|
||||
android:id="@+id/btnRefundBack"
|
||||
android:layout_width="0dp"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_weight="1"
|
||||
android:layout_marginEnd="8dp"
|
||||
android:text="Back"
|
||||
android:backgroundTint="@color/primary_medium"
|
||||
android:textColor="@color/white"/>
|
||||
|
||||
<Button
|
||||
android:id="@+id/btnProcessRefund"
|
||||
android:layout_width="0dp"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_weight="1"
|
||||
android:layout_marginStart="8dp"
|
||||
android:text="Process Refund"
|
||||
android:backgroundTint="@color/accent_coral"
|
||||
android:textColor="@color/white"
|
||||
android:visibility="gone"/>
|
||||
|
||||
</LinearLayout>
|
||||
|
||||
</LinearLayout>
|
||||
|
||||
</LinearLayout>
|
||||
<ProgressBar
|
||||
android:id="@+id/progressBar"
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_gravity="center"
|
||||
android:visibility="gone"
|
||||
android:indeterminateTint="@color/accent_coral"/>
|
||||
|
||||
</FrameLayout>
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user